feat: 向量语义搜索 — Cloudflare Vectorize 集成#228
Conversation
- Add EmbeddingClient supporting 6 providers: OpenAI, OpenAI-compatible, SiliconFlow, Gemini, Cohere, Ollama - Add VectorSearchService for Cloudflare Worker proxy communication - Add VectorSearchSettings UI with embedding config, worker config, status display, index management, and deploy guide - Add Cloudflare Worker (TS + JS) as pure Vectorize proxy - Add embedding_configs and vector_search_configs DB tables - Add CRUD routes for embedding and vector search configs - Add autoSync integration for new config types - Integrate vector search into SearchBar with auto-fallback - Refactor config routes into shared factory (registerEncryptedConfigRoutes) - Extract isRepoCustomized helper to eliminate 3x duplication Co-Authored-By: Claude <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds vector-search state, backend persistence and routes, a Cloudflare Worker proxy, client vector services, a settings tab, auto-sync wiring, and SearchBar vector-search integration. ChangesSemantic Vector Search
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a vector semantic search feature powered by Cloudflare Vectorize, including a Cloudflare Worker proxy, database schema updates, backend API routes, and frontend settings and search integration. The code review feedback highlights a critical security vulnerability where empty authentication tokens could bypass verification in the Worker, and suggests high-value improvements to optimize embedding quality for Cohere and Gemini by distinguishing between document indexing and query tasks. It also recommends adding default API URL placeholders for these providers in the settings UI.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const token = request.headers.get('Authorization')?.replace('Bearer ', ''); | ||
| if (token !== env.AUTH_TOKEN) { | ||
| return jsonResponse({ success: false, error: 'Unauthorized' }, 401); | ||
| } |
There was a problem hiding this comment.
如果 env.AUTH_TOKEN 未配置或为空字符串,当客户端发送空 Token(例如 Authorization: Bearer )或未发送 Authorization 头部时,token !== env.AUTH_TOKEN 的判断可能会被绕过(例如 "" !== "" 为 false)。这会导致未授权的用户可以直接访问您的向量数据库。建议增加对 env.AUTH_TOKEN 是否为空的校验。
| const token = request.headers.get('Authorization')?.replace('Bearer ', ''); | |
| if (token !== env.AUTH_TOKEN) { | |
| return jsonResponse({ success: false, error: 'Unauthorized' }, 401); | |
| } | |
| const token = request.headers.get('Authorization')?.replace('Bearer ', ''); | |
| if (!env.AUTH_TOKEN || token !== env.AUTH_TOKEN) { | |
| return jsonResponse({ success: false, error: 'Unauthorized' }, 401); | |
| } |
| const token = request.headers.get('Authorization')?.replace('Bearer ', ''); | ||
| if (token !== env.AUTH_TOKEN) { | ||
| return jsonResponse({ success: false, error: 'Unauthorized' }, 401); | ||
| } |
There was a problem hiding this comment.
同 src/index.ts,如果 env.AUTH_TOKEN 未配置或为空字符串,当客户端发送空 Token 时,安全校验可能会被绕过。建议增加对 env.AUTH_TOKEN 是否为空的校验。
| const token = request.headers.get('Authorization')?.replace('Bearer ', ''); | |
| if (token !== env.AUTH_TOKEN) { | |
| return jsonResponse({ success: false, error: 'Unauthorized' }, 401); | |
| } | |
| const token = request.headers.get('Authorization')?.replace('Bearer ', ''); | |
| if (!env.AUTH_TOKEN || token !== env.AUTH_TOKEN) { | |
| return jsonResponse({ success: false, error: 'Unauthorized' }, 401); | |
| } |
| async embed(texts: string[]): Promise<number[][]> { | ||
| switch (this.config.apiType) { | ||
| case 'openai': | ||
| case 'openai-compatible': | ||
| case 'siliconflow': | ||
| return this.embedOpenAICompatible(texts); | ||
| case 'ollama': | ||
| return this.embedOllama(texts); | ||
| case 'gemini': | ||
| return this.embedGemini(texts); | ||
| case 'cohere': | ||
| return this.embedCohere(texts); | ||
| default: | ||
| throw new Error(`Unsupported embedding API type: ${this.config.apiType}`); | ||
| } |
There was a problem hiding this comment.
对于 Cohere 和 Gemini 等 Embedding 模型,索引文档和查询问题时推荐使用不同的任务类型(Task Type / Input Type)。例如,Cohere 推荐在索引时使用 search_document,在查询时使用 search_query;Gemini 推荐使用 RETRIEVAL_DOCUMENT 和 RETRIEVAL_QUERY。如果统一使用 search_document 进行查询,会显著降低搜索的相关性和准确度。建议在 embed 方法中增加 purpose 参数以区分用途。
| async embed(texts: string[]): Promise<number[][]> { | |
| switch (this.config.apiType) { | |
| case 'openai': | |
| case 'openai-compatible': | |
| case 'siliconflow': | |
| return this.embedOpenAICompatible(texts); | |
| case 'ollama': | |
| return this.embedOllama(texts); | |
| case 'gemini': | |
| return this.embedGemini(texts); | |
| case 'cohere': | |
| return this.embedCohere(texts); | |
| default: | |
| throw new Error(`Unsupported embedding API type: ${this.config.apiType}`); | |
| } | |
| async embed(texts: string[], purpose: 'document' | 'query' = 'document'): Promise<number[][]> { | |
| switch (this.config.apiType) { | |
| case 'openai': | |
| case 'openai-compatible': | |
| case 'siliconflow': | |
| return this.embedOpenAICompatible(texts); | |
| case 'ollama': | |
| return this.embedOllama(texts); | |
| case 'gemini': | |
| return this.embedGemini(texts, purpose); | |
| case 'cohere': | |
| return this.embedCohere(texts, purpose); | |
| default: | |
| throw new Error("Unsupported embedding API type: " + this.config.apiType); | |
| } | |
| } |
| private async embedCohere(texts: string[]): Promise<number[][]> { | ||
| const url = `${this.config.baseUrl.replace(/\/+$/, '')}/v1/embed`; | ||
|
|
||
| const response = await fetch(url, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${this.config.apiKey}`, | ||
| }, | ||
| body: JSON.stringify({ | ||
| model: this.config.model, | ||
| texts, | ||
| input_type: 'search_document', | ||
| }), | ||
| }); |
There was a problem hiding this comment.
配合 embed 方法的 purpose 参数,为 Cohere 的 embed 请求指定对应的 input_type(search_query 或 search_document),以避免查询时使用 search_document 导致搜索质量下降。
| private async embedCohere(texts: string[]): Promise<number[][]> { | |
| const url = `${this.config.baseUrl.replace(/\/+$/, '')}/v1/embed`; | |
| const response = await fetch(url, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${this.config.apiKey}`, | |
| }, | |
| body: JSON.stringify({ | |
| model: this.config.model, | |
| texts, | |
| input_type: 'search_document', | |
| }), | |
| }); | |
| private async embedCohere(texts: string[], purpose: 'document' | 'query'): Promise<number[][]> { | |
| const url = `${this.config.baseUrl.replace(/\/+$/, '')}/v1/embed`; | |
| const response = await fetch(url, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| Authorization: `Bearer ${this.config.apiKey}`, | |
| }, | |
| body: JSON.stringify({ | |
| model: this.config.model, | |
| texts, | |
| input_type: purpose === 'query' ? 'search_query' : 'search_document', | |
| }), | |
| }); |
| // 1. 前端调用 Embedding API 生成查询向量 | ||
| const queryVectors = await embeddingClient.embed([searchQuery]); |
There was a problem hiding this comment.
调用 embeddingClient.embed 时,传入 'query' 参数,以便底层模型(如 Cohere/Gemini)使用针对查询优化的任务类型,从而大幅提升语义搜索的召回质量。
| // 1. 前端调用 Embedding API 生成查询向量 | |
| const queryVectors = await embeddingClient.embed([searchQuery]); | |
| // 1. 前端调用 Embedding API 生成查询向量 | |
| const queryVectors = await embeddingClient.embed([searchQuery], 'query'); |
| private async embedGemini(texts: string[]): Promise<number[][]> { | ||
| const baseUrl = this.config.baseUrl.replace(/\/+$/, ''); | ||
| const url = `${baseUrl}/v1beta/models/${this.config.model}:batchEmbedContents?key=${this.config.apiKey}`; | ||
|
|
||
| const response = await fetch(url, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ | ||
| requests: texts.map((text) => ({ | ||
| model: `models/${this.config.model}`, | ||
| content: { parts: [{ text }] }, | ||
| })), | ||
| }), | ||
| }); |
There was a problem hiding this comment.
配合 embed 方法的 purpose 参数,为 Gemini 的 batchEmbedContents 请求指定对应的 taskType(RETRIEVAL_QUERY 或 RETRIEVAL_DOCUMENT),以提升语义搜索的准确度。
| private async embedGemini(texts: string[]): Promise<number[][]> { | |
| const baseUrl = this.config.baseUrl.replace(/\/+$/, ''); | |
| const url = `${baseUrl}/v1beta/models/${this.config.model}:batchEmbedContents?key=${this.config.apiKey}`; | |
| const response = await fetch(url, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| requests: texts.map((text) => ({ | |
| model: `models/${this.config.model}`, | |
| content: { parts: [{ text }] }, | |
| })), | |
| }), | |
| }); | |
| private async embedGemini(texts: string[], purpose: 'document' | 'query'): Promise<number[][]> { | |
| const baseUrl = this.config.baseUrl.replace(/\/+$/, ''); | |
| const url = `${baseUrl}/v1beta/models/${this.config.model}:batchEmbedContents?key=${this.config.apiKey}`; | |
| const response = await fetch(url, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| requests: texts.map((text) => ({ | |
| model: `models/${this.config.model}`, | |
| content: { parts: [{ text }] }, | |
| taskType: purpose === 'query' ? 'RETRIEVAL_QUERY' : 'RETRIEVAL_DOCUMENT', | |
| })), | |
| }), | |
| }); |
| placeholder={ | ||
| formApiType === 'openai' | ||
| ? 'https://api.openai.com' | ||
| : formApiType === 'siliconflow' | ||
| ? 'https://api.siliconflow.cn' | ||
| : formApiType === 'ollama' | ||
| ? 'http://localhost:11434' | ||
| : 'https://api.example.com/v1/embeddings' | ||
| } |
There was a problem hiding this comment.
在模型来源选择 Gemini 或 Cohere 时,输入框缺少对应的官方默认 API 地址占位符。建议补充 Gemini (https://generativelanguage.googleapis.com) 和 Cohere (https://api.cohere.com) 的默认地址,以提升用户配置体验。
placeholder={
formApiType === 'openai'
? 'https://api.openai.com'
: formApiType === 'siliconflow'
? 'https://api.siliconflow.cn'
: formApiType === 'ollama'
? 'http://localhost:11434'
: formApiType === 'gemini'
? 'https://generativelanguage.googleapis.com'
: formApiType === 'cohere'
? 'https://api.cohere.com'
: 'https://api.example.com/v1/embeddings'
}
There was a problem hiding this comment.
Actionable comments posted: 15
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cloudflare-worker/README.md`:
- Around line 54-56: The fenced URL example in the README is missing a language
tag, which triggers markdownlint. Update the fenced block containing the Worker
URL example to use a text code fence, keeping the example content unchanged, and
make sure the fix is applied in the README section with the Worker URL
placeholder.
In `@cloudflare-worker/src/index.ts`:
- Around line 47-51: The auth check in the request handler allows a bypass when
AUTH_TOKEN is empty or unset, so update the logic around the existing token
validation in index.ts to reject requests if env.AUTH_TOKEN is missing before
comparing anything. Use a constant-time comparison in the same auth path instead
of direct string equality, and ensure the Authorization parsing keeps an
explicit fallback so an empty bearer token cannot authenticate when the secret
is not configured.
In `@cloudflare-worker/wrangler.toml`:
- Around line 5-6: Remove the plaintext AUTH_TOKEN entry from the [vars] section
in wrangler.toml so it is only managed as a secret. Update the Cloudflare worker
config to stop defining AUTH_TOKEN there, and keep using wrangler secret put
AUTH_TOKEN for the actual value to avoid the name collision and empty-token
deploy path.
In `@server/src/routes/configs.ts`:
- Around line 106-115: The bulk config sync logic is incorrectly skipping every
config with no secret, which breaks keyless embedding providers such as Ollama.
Update the shared config factory and bulk route handling around the
encryptedKey/skip check so keyless embedding configs are allowed through, either
by adding a requiresSecret option or by special-casing providers used by
EmbeddingClient.embedOllama(). Make sure the same fix is applied in both
occurrences of the skip logic so configs without apiKey are not dropped.
- Around line 620-625: The create flow in the configs route is returning the
wrong identifier because the INSERT into embedding_configs does not persist the
TEXT primary key id and instead uses lastInsertRowid. Update the create logic in
the config handler around the INSERT and response so it generates and stores an
explicit id value for embedding_configs.id, includes that id in the INSERT
columns/values, and returns that same stored text id in the JSON response
instead of lastInsertRowid.
In `@src/components/SearchBar.tsx`:
- Around line 535-536: The SearchBar filter effect is overwriting vector search
results because updating searchFilters.query retriggers the basic text search
path and replaces the results set by setSearchResults. Adjust the SearchBar flow
so semantic/vector results are preserved when searchFilters.query changes, or
split the effect logic so the recomputation in the effect at the
searchFilters/query path does not run after vector searches. Use the
setSearchResults, setSearchFilters, and the effect that depends on
searchFilters.query to locate and update the behavior.
- Around line 522-535: The vector search path in SearchBar is losing similarity
order because applyFilters re-sorts the repos by UI settings after the
score-based ordering is created. In the scoredRepos flow, keep the vector
ranking from scoreMap by re-sorting the filtered results using the same scores
after applyFilters, then pass that reordered list to setSearchResults so the
rendered results preserve similarity order.
In `@src/components/settings/VectorSearchSettings.tsx`:
- Around line 202-215: The rebuild flow in VectorSearchSettings.tsx only calls
indexAllRepos(), which upserts current repositories but never removes vectors
for repos that are no longer present or indexable. Update the rebuild path
around the indexAllRepos call and subsequent setVectorSearchConfig handling so
the Vectorize index is first reconciled against the current repositories list by
deleting stale entries before or during indexing. Use the existing indexAllRepos
/ setIndexResult / setVectorSearchConfig flow to ensure the final indexed count
reflects only current repos.
- Around line 188-195: The embedding test flow is still using the saved config
instead of the current unsaved form values, which can make rebuild/reindex use
stale credentials after a successful test. Update handleTestEmbedding in
VectorSearchSettings so EmbeddingClient is constructed from the same form state
used for VectorSearchService (for example, the current form URL/token and
selected embedding config/model), not from activeConfig. Keep the embedding
client instantiation aligned with the existing form-driven logic so the tested
settings are the ones actually used afterward.
In `@src/components/SettingsPanel.tsx`:
- Line 34: The SettingsPanel tab type now includes vectorSearch, but the runtime
allowlist still blocks it, so update the tab validation used by the
gsm:pending-settings-tab and gsm:navigate-to-settings-tab handlers. Modify the
VALID_TABS definition in SettingsPanel to include vectorSearch, and make sure
any tab-checking logic in the same component uses that updated allowlist so the
new panel can be reached at runtime.
In `@src/services/autoSync.ts`:
- Around line 229-231: The vector-search sync path in autoSync should not
overwrite a valid local token when `/api/configs/vector-search` returns an empty
authToken or authTokenStatus is decrypt_failed. Update the
`changed.vectorSearch` handling in `src/services/autoSync.ts` to merge the
incoming `vectorSearchResult.value` with the existing state from
`state.setVectorSearchConfig`, preserving the current `authToken` in the same
way the AI/WebDAV/embedding sync paths do. Use the `vectorSearchResult` response
fields to detect decrypt failures before applying the config.
In `@src/services/vectorSearchService.ts`:
- Around line 71-75: The current request path in VectorSearchService and related
embedding/provider calls ignores the AbortSignal, so cancellation only happens
between batches. Thread the signal from indexAllRepos through
EmbeddingClient.embed(...), VectorSearchService.request(...), and every provider
fetch so in-flight requests can be aborted promptly. Update the fetch call sites
in VectorSearchService and the referenced provider/worker methods to accept and
forward the signal consistently.
- Around line 153-157: The embedding request in vectorSearchService is hardcoded
to use search_document, but the same path is also used for user search queries.
Update the VectorSearchService embedding flow so the caller passes the embedding
purpose into the request builder, and switch query-side calls to use
search_query while keeping stored-content embeddings on search_document. Use the
existing vectorSearchService method(s) that build the Cohere request body to
thread this purpose through cleanly.
In `@src/store/useAppStore.ts`:
- Around line 1323-1328: `deleteEmbeddingConfig` and `setEmbeddingConfigs` in
`useAppStore` only update `embeddingConfigs`, leaving
`vectorSearchConfig.embeddingConfigId` pointing to a missing config and breaking
the vector path used by `SearchBar`. Update these store actions to reconcile
`vectorSearchConfig` whenever an embedding config is removed or the full list is
replaced: clear or reset the stale `embeddingConfigId`, and disable vector
search if the selected config no longer exists. Keep the logic localized around
`deleteEmbeddingConfig`, `setEmbeddingConfigs`, and the `vectorSearchConfig`
state so the UI can’t stay enabled with an invalid provider reference.
In `@src/types/index.ts`:
- Around line 227-233: `VectorSearchConfig` currently mixes persisted config
with transient runtime state by including `status`, which can leak UI-only
telemetry into synced backend data. Move `status` out of the shared config
contract in `src/types/index.ts` and create a separate runtime/store type for
it, then update `useAppStore` and `backendAdapter` serialization to persist or
sync only the config fields (`enabled`, `workerUrl`, `authToken`,
`embeddingConfigId`) while explicitly excluding `status`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 02af5f95-118c-4bd7-9b40-01902c81c1f0
⛔ Files ignored due to path filters (1)
cloudflare-worker/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (18)
cloudflare-worker/README.mdcloudflare-worker/package.jsoncloudflare-worker/src/index.tscloudflare-worker/tsconfig.jsoncloudflare-worker/worker.jscloudflare-worker/wrangler.tomlserver/src/db/schema.tsserver/src/routes/configs.tssrc/components/SearchBar.tsxsrc/components/SettingsPanel.tsxsrc/components/settings/VectorSearchSettings.tsxsrc/components/settings/index.tssrc/services/autoSync.tssrc/services/backendAdapter.tssrc/services/vectorSearchService.tssrc/store/useAppStore.tssrc/types/index.tssrc/utils/repoUtils.ts
Security: - Worker: reject requests when AUTH_TOKEN is empty/unset - Remove plaintext AUTH_TOKEN from wrangler.toml [vars] Functional: - Add purpose parameter for Cohere/Gemini (search_document vs search_query) - Pass 'query' purpose in SearchBar vector search - Preserve vector similarity ranking after applyFilters - Generate proper UUID for embedding config POST (TEXT PK) - Allow keyless embedding configs in factory (Ollama) - Clear vectorSearchConfig when embedding config is deleted - Preserve authToken on decrypt failure in autoSync - Use form state for EmbeddingClient in handleRebuildIndex - Add vectorSearch to VALID_TABS runtime allowlist - Add Gemini/Cohere URL placeholders in settings UI - Add language tag to README code fence Co-Authored-By: Claude <noreply@anthropic.com>
- Fix filter effect overwriting vector results (skipNextTextSearchRef) - Add /cleanup endpoint to Worker for stale vector removal - Thread AbortSignal through all fetch calls (embed + worker requests) - Split VectorSearchStatus out of VectorSearchConfig (runtime-only) - Cleanup stale vectors before rebuild in handleRebuildIndex Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
cloudflare-worker/worker.js (1)
85-103: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy liftSame
/cleanupdefects as the TypeScript source.This is the JS mirror of
cloudflare-worker/src/index.ts; it has the identicaltopK-exceeds-Vectorize-max and hardcoded-1536-dimension issues. Apply the same fix here so both builds stay in sync.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cloudflare-worker/worker.js` around lines 85 - 103, The /cleanup logic in worker.js mirrors the TypeScript implementation and still has the same Vectorize issues: it hardcodes a 1536-length zero vector and uses sampleSize directly as topK, which can exceed Vectorize’s maximum. Update the cleanup flow in the same block that builds zeroVector, calls env.VECTORIZE.describe(), and runs env.VECTORIZE.query() so the vector dimension comes from the index metadata and topK is capped to Vectorize’s allowed limit, keeping this JS mirror aligned with src/index.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cloudflare-worker/src/index.ts`:
- Around line 100-119: The /cleanup flow in the vector cleanup handler is using
a hardcoded 1536-dimension zero vector and an oversized query window, which
breaks on indexes with different dimensions or when sampleSize exceeds Vectorize
limits. Update the cleanup logic in the handler that calls
env.VECTORIZE.describe() and env.VECTORIZE.query() to build the zero vector from
info.dimensions instead of a fixed size, and cap topK to 100 (or less) rather
than sampleSize. Keep the existing staleIds filtering and deleteByIds flow
unchanged.
---
Duplicate comments:
In `@cloudflare-worker/worker.js`:
- Around line 85-103: The /cleanup logic in worker.js mirrors the TypeScript
implementation and still has the same Vectorize issues: it hardcodes a
1536-length zero vector and uses sampleSize directly as topK, which can exceed
Vectorize’s maximum. Update the cleanup flow in the same block that builds
zeroVector, calls env.VECTORIZE.describe(), and runs env.VECTORIZE.query() so
the vector dimension comes from the index metadata and topK is capped to
Vectorize’s allowed limit, keeping this JS mirror aligned with src/index.ts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2e290d75-77b5-4fa9-89b6-bf0541077c15
📒 Files selected for processing (7)
cloudflare-worker/src/index.tscloudflare-worker/worker.jssrc/components/SearchBar.tsxsrc/components/settings/VectorSearchSettings.tsxsrc/services/vectorSearchService.tssrc/store/useAppStore.tssrc/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- src/types/index.ts
- src/services/vectorSearchService.ts
- src/store/useAppStore.ts
- src/components/SearchBar.tsx
- src/components/settings/VectorSearchSettings.tsx
Search quality: - Fetch README content during indexing (first 2000 chars) - buildEmbeddingText now includes README for richer semantic context - indexAllRepos accepts optional readmeFetcher callback Settings UI: - Save buttons show green 'Saved' feedback for 2 seconds - Auto-detect dimensions focuses the input to show the value Deploy guide: - Remove Cloudflare Dashboard deployment method (no Vectorize UI) - Add update/redeploy instructions - Add prominent warning about model change requiring index rebuild Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/services/vectorSearchService.ts (1)
368-381: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winFetch READMEs per batch instead of preloading every repo.
The current prefetch phase performs all README network calls before the first embedding batch, so large libraries can look stuck and consume API quota even if later batches fail or are aborted. Keep README fetching scoped to the current batch.
Proposed refactor
- // 预先批量获取 README 内容(如果提供了 fetcher) - const readmeCache = new Map<string, string>(); - if (readmeFetcher) { - for (const repo of indexable) { - if (signal?.aborted) throw new Error('Aborted'); - try { - const [owner, name] = repo.full_name.split('/'); - const readme = await readmeFetcher(owner, name, signal); - if (readme) readmeCache.set(repo.full_name, readme); - } catch { - // README 获取失败不影响索引 - } - } - } - for (let i = 0; i < indexable.length; i += batchSize) { if (signal?.aborted) { throw new Error('Aborted'); } const batch = indexable.slice(i, i + batchSize); + const readmeCache = new Map<string, string>(); + if (readmeFetcher) { + for (const repo of batch) { + if (signal?.aborted) throw new Error('Aborted'); + try { + const [owner, name] = repo.full_name.split('/'); + const readme = await readmeFetcher(owner, name, signal); + if (readme) readmeCache.set(repo.full_name, readme); + } catch (err) { + if (signal?.aborted || (err instanceof Error && err.message === 'Aborted')) { + throw new Error('Aborted'); + } + // README 获取失败不影响索引 + } + } + } const texts = batch.map(repo => buildEmbeddingText(repo, readmeCache.get(repo.full_name)));Also applies to: 388-389
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/services/vectorSearchService.ts` around lines 368 - 381, The README prefetch in vectorSearchService is doing all network fetches up front instead of per embedding batch, which can stall large runs and waste quota; move the readmeFetcher usage into the current batch processing flow so each batch fetches only its own READMEs. Update the logic around the readmeCache handling in vectorSearchService to scope caching/fetching to the batch loop, and make sure abort checks still short-circuit batch work cleanly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/services/vectorSearchService.ts`:
- Line 361: Validate the caller-provided batchSize in vectorSearchService before
it is used by the indexing loop that increments i by batchSize; reject or
normalize zero, negative, or non-finite values in the code path that reads
options in the relevant indexing function, so the loop stride always makes
forward progress. Update both places where batchSize is consumed to use the
validated value, and keep the behavior centered around the existing options
destructuring and the i += batchSize loop.
---
Nitpick comments:
In `@src/services/vectorSearchService.ts`:
- Around line 368-381: The README prefetch in vectorSearchService is doing all
network fetches up front instead of per embedding batch, which can stall large
runs and waste quota; move the readmeFetcher usage into the current batch
processing flow so each batch fetches only its own READMEs. Update the logic
around the readmeCache handling in vectorSearchService to scope caching/fetching
to the batch loop, and make sure abort checks still short-circuit batch work
cleanly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 70b91d87-1e92-47a5-898a-b967ca4cb701
📒 Files selected for processing (3)
cloudflare-worker/README.mdsrc/components/settings/VectorSearchSettings.tsxsrc/services/vectorSearchService.ts
✅ Files skipped from review due to trivial changes (1)
- cloudflare-worker/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/settings/VectorSearchSettings.tsx
Search quality: - Increase README content from 2000 to 6000 chars - Strip decorative badges/images/HTML from README before embedding - Add AI reranking step after vector search (uses existing AI config) Worker /cleanup fix: - Cap topK to 100 (Vectorize limit) - Use info.dimensions for zero vector instead of hardcoded 1536 Validation: - Add batchSize validation in indexAllRepos (must be positive integer) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cloudflare-worker/src/index.ts (1)
105-113: 🗄️ Data Integrity & Integration | 🟠 MajorUse paginated vector listing for
/cleanup(cloudflare-worker/src/index.ts:107-115).env.VECTORIZE.query(..., { topK: 100 })only returns the 100 nearest matches, so stale vectors outside that window can survive cleanup runs. Enumerate IDs withlist-vectorspagination or pass explicit delete candidates instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cloudflare-worker/src/index.ts` around lines 105 - 113, The `/cleanup` logic in `index.ts` currently uses `env.VECTORIZE.query` with a fixed `topK: 100`, which can miss stale vectors beyond the returned window. Update the cleanup flow around the `zeroVector`, `existing.matches`, and `staleIds` handling to enumerate vector IDs with paginated listing or otherwise gather explicit delete candidates before deleting. Keep the `keepSet` filtering, but ensure all stale vectors are discovered across pages rather than relying on a single query result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@cloudflare-worker/src/index.ts`:
- Around line 105-113: The `/cleanup` logic in `index.ts` currently uses
`env.VECTORIZE.query` with a fixed `topK: 100`, which can miss stale vectors
beyond the returned window. Update the cleanup flow around the `zeroVector`,
`existing.matches`, and `staleIds` handling to enumerate vector IDs with
paginated listing or otherwise gather explicit delete candidates before
deleting. Keep the `keepSet` filtering, but ensure all stale vectors are
discovered across pages rather than relying on a single query result.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c733483f-01f3-4856-ae08-3a7f448c8d30
📒 Files selected for processing (4)
cloudflare-worker/src/index.tscloudflare-worker/worker.jssrc/components/SearchBar.tsxsrc/services/vectorSearchService.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/SearchBar.tsx
- src/services/vectorSearchService.ts
The previous /cleanup used query(topK: 100) which only returns the 100 nearest matches, missing stale vectors beyond that window. Now uses listVectors with cursor-based pagination to enumerate ALL vector IDs and delete those not in the keepIds list. Co-Authored-By: Claude <noreply@anthropic.com>
- Worker test success now updates store vectorSearchStatus - Add delete index section with copy-to-clipboard CLI commands - README.md reference is now a clickable GitHub link - Renumber deploy guide section to ⑥ Co-Authored-By: Claude <noreply@anthropic.com>
- indexAllRepos now reports phase (readme/embedding/uploading) + progress - Progress bar shows phase emoji and label (📖/🧠/☁️) - Indexing state moved from component local state to Zustand store - State persists across page navigation (切页不丢失) Co-Authored-By: Claude <noreply@anthropic.com>
1. Remove redundant AI reranking for vector search results (speed up) 2. Add search phase display below AI search button 3. Fix sort changing loses vector results (use vectorScoreMapRef) 4. Clear vector state when disabling vector search 5. Auto-index new repos after star sync when vector search enabled 6. Add 'Incremental Index' button (upsert only, no cleanup) Co-Authored-By: Claude <noreply@anthropic.com>
Restored the LLM-based reranking step that was incorrectly removed. Now shows 'AI 校验排序...' progress during reranking so users know what's happening. Falls back to vector order if reranking fails. Co-Authored-By: Claude <noreply@anthropic.com>
Primary buttons: bg-purple-500 → bg-brand-indigo Hover: bg-purple-600 → bg-brand-hover Toggle: bg-purple-500 → bg-brand-indigo Rounded: rounded-md → rounded-lg Co-Authored-By: Claude <noreply@anthropic.com>
- Add indexMode ('description' | 'readme') to VectorSearchConfig
- Add readmeMaxChars (default 6000) for configurable README truncation
- Description mode: fast, lower precision (metadata only)
- README mode: high precision, slower (fetches and indexes README)
- Index mode selector with visual cards in settings UI
- README max chars input (500-20000, step 1000)
- Consolidated save button in index management section
- All index operations respect the selected mode
Co-Authored-By: Claude <noreply@anthropic.com>
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 18
🧹 Nitpick comments (1)
cloudflare-worker/worker.js (1)
1-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffTwo parallel worker implementations will drift.
worker.jsduplicatessrc/index.ts(the past auth/cleanup fixes had to be applied twice, and these files can silently diverge). Consider shipping a single source of truth — e.g. generateworker.jsfrom the TS build, or document one as canonical and the other as a generated artifact.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cloudflare-worker/worker.js` around lines 1 - 122, The Worker logic is duplicated between the standalone worker implementation and the TypeScript entrypoint, which will cause the two versions to drift over time. Make one source of truth for the request handling in fetch and the VECTORIZE operations (upsert, query, delete, cleanup, status), then have the other file be generated from it or reduced to a build artifact/entry wrapper. Update the build or repo convention so future changes only need to be made in the canonical implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cloudflare-worker/package.json`:
- Line 12: Update the wrangler dependency in the package.json manifest to a
V2-compatible minimum version by changing the current wrangler range from the
existing broad 3.0.x allowance to a 3.71.0+ range; this ensures CI and lockfile
resolution cannot pick an incompatible release. Use the wrangler dependency
entry in the package manifest as the target for the version bump.
In `@cloudflare-worker/src/index.ts`:
- Line 106: The `staleIds` declaration should use `const` instead of `let`
because the array reference is never reassigned, only mutated with `push`.
Update the `staleIds` variable in `index.ts` to `const` and keep the existing
push-based usage unchanged.
- Around line 75-78: The Vectorize full-metadata query in the query path uses
topK directly, but returnMetadata: true only supports up to 50 results. Update
the query logic around env.VECTORIZE.query in the index.ts flow to clamp topK to
a maximum of 50 before invoking query(), while keeping the existing metadata
option unchanged.
- Around line 95-119: The cleanup handler in the POST /cleanup branch of
index.ts is calling env.VECTORIZE.listVectors, which is not supported by the
Workers Vectorize binding surface and will fail at runtime. Update this path to
use a supported listing approach via the Vectorize REST/CLI-backed flow, then
keep the existing staleIds collection and deleteByIds call unchanged once IDs
are obtained. Use the request handling logic around jsonResponse, describe, and
deleteByIds as the anchor points when refactoring.
In `@cloudflare-worker/worker.js`:
- Line 91: Change the staleIds declaration in the worker logic to use const
instead of let, matching the pattern used in src/index.ts. Locate the variable
initialization for staleIds and update it to an immutable binding since the
array reference is not reassigned; keep the surrounding stale-id handling
unchanged.
In `@server/src/db/schema.ts`:
- Around line 124-134: The vector search config table is missing persistence for
the README indexing settings used by VectorSearchConfig. Update the
vector_search_configs schema and any related migration/create-table logic in
schema.ts to store indexMode and readmeMaxChars alongside the existing fields,
and ensure the backend config save/load paths use those columns so auto-sync
does not drop the values.
In `@server/src/routes/configs.ts`:
- Around line 160-168: The secret-handling logic in the configs route is
treating an explicit empty string the same as an omitted or masked value, so
fields like apiKey/password/authToken cannot be cleared. Update the branch
around the existing secret lookup and the updateSql/updateParams flow so only
omitted values or masked values reuse the stored secret; if the request
explicitly sends an empty string, persist a cleared value instead, and for
fields marked by requiresSecret return 422 rather than silently keeping the old
secret. Apply the same fix in both affected secret update paths.
- Around line 124-145: The bulk sync flow in the configs route currently commits
partial writes even when some configs are skipped, because the deletion and
inserts inside bulkSync only abort on ALL_CONFIGS_SKIPPED. Update the bulk sync
logic in the route handler around bulkSync/res.json so any non-empty
syncResult.skipped causes the whole transaction to roll back before commit,
rather than returning success with partial replacement. Use the existing
bulkSync, syncResult.skipped, and logger.errorFromError flow to locate the fix
and ensure the response is only sent after a fully successful sync.
In `@src/components/SearchBar.tsx`:
- Around line 169-170: The vector score cache in SearchBar is not tied to the
query that produced it, so stale vector hits can be reused after filters or
subsequent searches change. Update the vector search flow around SearchBar,
vectorScoreMapRef, and the query-handling logic so the stored scores are
associated with the current searchFilters.query and cleared or ignored when that
query changes. Make sure the code paths that read and write vectorScoreMapRef
only apply scores when they match the active query, including the affected
search/update logic near the referenced sections.
- Around line 555-572: The final sorting in SearchBar’s reranking flow is
overriding the order returned by AIService.searchRepositoriesWithReranking(), so
successful AI reranks never affect the displayed results. Update the rerank
branch in SearchBar to preserve the reranked array order when reranking
succeeds, and only fall back to the existing vector-score sort when reranking is
unavailable or throws. Keep applyFilters from reordering the AI result, and use
the rerankService/searchRepositoriesWithReranking path as the source of truth
for the final list.
- Around line 871-887: The auto-index path in SearchBar currently triggers on
newRepoCount > 0 but passes mergedRepositories to indexAllRepos, which causes
the whole set to be reprocessed instead of only the new additions. Update this
flow to build and pass only the newly added repository list to indexAllRepos,
keeping the existing vectorSearchConfig, EmbeddingClient, VectorSearchService,
and readmeFetcher wiring unchanged.
In `@src/components/settings/VectorSearchSettings.tsx`:
- Around line 261-265: The vector search status is recording the wrong dimension
value after a rebuild: the indexing run uses the edited form value, but
setVectorSearchStatus in VectorSearchSettings still წერს
activeConfig.dimensions. Update the status payload to report the dimensions
actually used for the current indexing run, using the same formDimensions value
passed into the embedding client/build path so the UI reflects the newly rebuilt
index correctly.
- Around line 285-291: `isConfigComplete` in `VectorSearchSettings` is currently
driven by unsaved form values, so the rebuild actions can appear enabled even
when `runIndexAll()` would no-op because there is no active embedding config.
Update the enablement logic for the indexing/rebuild controls to depend on the
persisted config state used by `runIndexAll()` (or explicitly require
`activeConfig`/saved config existence), and keep the form-based checks only for
validating edits rather than enabling indexing.
- Around line 233-239: Fail the rebuild when vector cleanup throws instead of
swallowing the error in VectorSearchSettings’ rebuild flow. In the cleanup block
that calls vectorService.cleanup, pass the abort signal through to cleanup so it
can be cancelled, and remove the local catch/console.warn so the exception
bubbles to the outer rebuild catch. This keeps the rebuild from continuing with
stale vectors when cleanup fails.
- Around line 140-150: The memoized handleSaveWorkerConfig callback is missing
dependencies for formIndexMode and formReadmeMaxChars, so it can capture stale
values when saving the vector search worker config. Update the useCallback
dependency list in VectorSearchSettings so it includes both fields along with
the existing formWorkerUrl, formAuthToken, activeEmbeddingConfig, and
setVectorSearchConfig references. This will ensure the save action always
persists the current index mode and readme limit values.
In `@src/services/backendAdapter.ts`:
- Around line 631-649: The sync/pull flow for vector-search config is treating
VectorSearchConfig as complete, but indexMode and readmeMaxChars are not
preserved by the backend contract. Update syncVectorSearchConfig and
fetchVectorSearchConfig in backendAdapter to stop round-tripping a partial
object, and either extend the backend configs route/schema to persist and return
those fields or move them into the settings payload that autoSync consumes. Make
sure the VectorSearchConfig shape and the backend /configs/vector-search
contract stay aligned so these README indexing settings are not clobbered.
In `@src/services/vectorSearchService.ts`:
- Around line 341-344: The README cleanup in vectorSearchService’s markdown
stripping currently removes plain image syntax before linked badges, so linked
badges like [](...) can leak into the cleaned text. Update the
replacement order in the readmeContent normalization logic so the linked-badge
pattern is stripped first, then the plain image pattern, while keeping the rest
of the cleaning chain in place.
In `@src/store/useAppStore.ts`:
- Around line 640-651: Normalize the persisted vector search config so it cannot
reference a missing embedding provider. In both normalizePersistedState and
setVectorSearchConfig, validate vectorSearchConfig.embeddingConfigId against the
current embeddingConfigs, and if the id is absent, clear embeddingConfigId and
disable vector search instead of keeping the stale selection. Make sure the
hydration path and the runtime setter use the same guard so setEmbeddingConfigs
cleanup cannot be overwritten by an invalid persisted vector config.
---
Nitpick comments:
In `@cloudflare-worker/worker.js`:
- Around line 1-122: The Worker logic is duplicated between the standalone
worker implementation and the TypeScript entrypoint, which will cause the two
versions to drift over time. Make one source of truth for the request handling
in fetch and the VECTORIZE operations (upsert, query, delete, cleanup, status),
then have the other file be generated from it or reduced to a build
artifact/entry wrapper. Update the build or repo convention so future changes
only need to be made in the canonical implementation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 79361008-fc6f-46e5-899d-a711710315a5
⛔ Files ignored due to path filters (1)
cloudflare-worker/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (18)
cloudflare-worker/README.mdcloudflare-worker/package.jsoncloudflare-worker/src/index.tscloudflare-worker/tsconfig.jsoncloudflare-worker/worker.jscloudflare-worker/wrangler.tomlserver/src/db/schema.tsserver/src/routes/configs.tssrc/components/SearchBar.tsxsrc/components/SettingsPanel.tsxsrc/components/settings/VectorSearchSettings.tsxsrc/components/settings/index.tssrc/services/autoSync.tssrc/services/backendAdapter.tssrc/services/vectorSearchService.tssrc/store/useAppStore.tssrc/types/index.tssrc/utils/repoUtils.ts
| vectorSearchConfig: safePersisted.vectorSearchConfig && typeof safePersisted.vectorSearchConfig === 'object' | ||
| ? { | ||
| enabled: !!safePersisted.vectorSearchConfig.enabled, | ||
| workerUrl: typeof safePersisted.vectorSearchConfig.workerUrl === 'string' ? safePersisted.vectorSearchConfig.workerUrl : '', | ||
| authToken: typeof safePersisted.vectorSearchConfig.authToken === 'string' ? safePersisted.vectorSearchConfig.authToken : '', | ||
| embeddingConfigId: typeof safePersisted.vectorSearchConfig.embeddingConfigId === 'string' ? safePersisted.vectorSearchConfig.embeddingConfigId : '', | ||
| indexMode: safePersisted.vectorSearchConfig.indexMode === 'description' ? 'description' : 'readme', | ||
| readmeMaxChars: typeof safePersisted.vectorSearchConfig.readmeMaxChars === 'number' && safePersisted.vectorSearchConfig.readmeMaxChars > 0 | ||
| ? safePersisted.vectorSearchConfig.readmeMaxChars | ||
| : 6000, | ||
| } | ||
| : { enabled: false, workerUrl: '', authToken: '', embeddingConfigId: '', indexMode: 'readme' as const, readmeMaxChars: 6000 }, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Normalize vector config against available embedding configs.
normalizePersistedState and setVectorSearchConfig accept any embeddingConfigId. Since backend sync pulls embedding configs and vector config independently, a stale vector config can overwrite the cleanup done by setEmbeddingConfigs, leaving vector search enabled with no usable provider. Clear the id and disable vector search when the merged id is not present in embeddingConfigs.
Suggested localized guard
- setVectorSearchConfig: (config) => set((state) => ({
- vectorSearchConfig: { ...state.vectorSearchConfig, ...config }
- })),
+ setVectorSearchConfig: (config) => set((state) => {
+ const vectorSearchConfig = { ...state.vectorSearchConfig, ...config };
+ const hasEmbeddingConfig =
+ !vectorSearchConfig.embeddingConfigId ||
+ state.embeddingConfigs.some(({ id }) => id === vectorSearchConfig.embeddingConfigId);
+
+ return {
+ vectorSearchConfig: hasEmbeddingConfig
+ ? vectorSearchConfig
+ : { ...vectorSearchConfig, embeddingConfigId: '', enabled: false },
+ };
+ }),Apply the same validity check in hydration normalization.
Also applies to: 1352-1354
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/store/useAppStore.ts` around lines 640 - 651, Normalize the persisted
vector search config so it cannot reference a missing embedding provider. In
both normalizePersistedState and setVectorSearchConfig, validate
vectorSearchConfig.embeddingConfigId against the current embeddingConfigs, and
if the id is absent, clear embeddingConfigId and disable vector search instead
of keeping the stale selection. Make sure the hydration path and the runtime
setter use the same guard so setEmbeddingConfigs cleanup cannot be overwritten
by an invalid persisted vector config.
- Add index_mode and readme_max_chars columns to vector_search_configs - Update GET/PUT routes to handle new fields - Fix returnMetadata: true → 'all' for Vectorize V2 - Replace listVectors (doesn't exist in binding) with query-based cleanup - Change let staleIds → const staleIds in both TS and JS worker - Update wrangler version to ^3.71.0 for Vectorize V2 Co-Authored-By: Claude <noreply@anthropic.com>
- Bulk sync: rollback when ANY config skipped (not just all) - Distinguish omitted secrets from explicit empty string clears - Bind vector scores to the query that produced them - Preserve AI rerank order (don't re-sort by vector score after rerank) - Add formIndexMode/formReadmeMaxChars to handleSaveWorkerConfig deps - Fail rebuild when cleanup fails (don't silently continue) - Report formDimensions in status (not stale activeConfig.dimensions) - Disable indexing buttons until embedding config exists - Remove linked badges before plain images in README cleanup Co-Authored-By: Claude <noreply@anthropic.com>
Bug fixes: - Make cleanup non-blocking so rebuild continues even if cleanup fails - Clear vectorScoreMapRef when vector search is disabled - Check vsEnabled in effect before reusing cached vector scores - Show error message in indexing result (red for all-failed) Audit fixes: - r3467643178: Single update route also distinguishes empty vs omitted - r3467643227: Auto-index only new repos (not entire library) - r3467643354: normalizePersistedState validates embeddingConfigId exists - r3467643081: returnMetadata already 'all' (confirmed) Co-Authored-By: Claude <noreply@anthropic.com>
…mpty secrets - Clamp topK to 50 in both TS and JS workers (Vectorize caps at 50 with returnMetadata:'all') - Fix worker.js returnMetadata: true → 'all' (was using deprecated format) - Vector search config PUT: authToken='' now clears token, omitted/masked reuses existing Audit fixes: r3467643081, r3467643178 Co-Authored-By: Claude <noreply@anthropic.com>
概述
引入基于 Cloudflare Vectorize 的向量语义搜索作为可选增强功能。用户可以用自然语言搜索星标仓库(如"能帮我管理 docker 容器的工具"),系统会理解语义意图而非仅做关键词匹配。
架构
新增文件
src/services/vectorSearchService.tssrc/components/settings/VectorSearchSettings.tsxsrc/utils/repoUtils.tsisRepoCustomized工具函数(消除 3 处重复)cloudflare-worker/src/index.tscloudflare-worker/worker.jscloudflare-worker/wrangler.tomlcloudflare-worker/README.md修改文件
src/types/index.tssrc/store/useAppStore.tssrc/components/SearchBar.tsxsrc/components/SettingsPanel.tsxsrc/services/autoSync.tssrc/services/backendAdapter.tsserver/src/db/schema.tsserver/src/routes/configs.tsEmbedding 渠道支持
openaiopenai-compatiblesiliconflowgeminicohereollama部署方式
支持两种 Cloudflare Worker 部署方式:
worker.jswrangler deploy审计修复
ALL_CONFIGS_SKIPPED安全守卫syncVectorSearchConfig改用fetchWithRetry(3 次重试)handleRebuildIndex使用表单状态而非 store 快照indexAllRepos添加向量数量校验isRepoCustomized提取为工具函数,消除 3 处重复🤖 Generated with Claude Code
Summary by CodeRabbit