Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/types/src/codebase-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const CODEBASE_INDEX_DEFAULTS = {
export const codebaseIndexConfigSchema = z.object({
codebaseIndexEnabled: z.boolean().optional(),
codebaseIndexQdrantUrl: z.string().optional(),
codebaseIndexQdrantCollectionName: z.string().optional(),
codebaseIndexEmbedderProvider: z
.enum(["openai", "ollama", "openai-compatible", "gemini", "mistral", "vercel-ai-gateway"])
.optional(),
Expand Down
1 change: 1 addition & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2270,6 +2270,7 @@ export const webviewMessageHandler = async (
...currentConfig,
codebaseIndexEnabled: settings.codebaseIndexEnabled,
codebaseIndexQdrantUrl: settings.codebaseIndexQdrantUrl,
codebaseIndexQdrantCollectionName: settings.codebaseIndexQdrantCollectionName,
codebaseIndexEmbedderProvider: settings.codebaseIndexEmbedderProvider,
codebaseIndexEmbedderBaseUrl: settings.codebaseIndexEmbedderBaseUrl,
codebaseIndexEmbedderModelId: settings.codebaseIndexEmbedderModelId,
Expand Down
6 changes: 6 additions & 0 deletions src/services/code-index/__tests__/config-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,14 @@ describe("CodeIndexConfigManager", () => {
modelId: undefined,
openAiOptions: { openAiNativeApiKey: "" },
ollamaOptions: { ollamaBaseUrl: "" },
geminiOptions: undefined,
mistralOptions: undefined,
openAiCompatibleOptions: undefined,
vercelAiGatewayOptions: undefined,
modelDimension: undefined,
qdrantUrl: "http://localhost:6333",
qdrantApiKey: "",
qdrantCollectionName: "",
searchMinScore: 0.4,
})
expect(result.requiresRestart).toBe(false)
Expand Down
9 changes: 9 additions & 0 deletions src/services/code-index/__tests__/service-factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ describe("CodeIndexServiceFactory", () => {
"http://localhost:6333",
3072,
"test-key",
undefined, // collectionName
)
})

Expand All @@ -392,6 +393,7 @@ describe("CodeIndexServiceFactory", () => {
"http://localhost:6333",
768,
"test-key",
undefined, // collectionName
)
})

Expand All @@ -417,6 +419,7 @@ describe("CodeIndexServiceFactory", () => {
"http://localhost:6333",
3072,
"test-key",
undefined, // collectionName
)
})

Expand Down Expand Up @@ -449,6 +452,7 @@ describe("CodeIndexServiceFactory", () => {
"http://localhost:6333",
modelDimension, // Should use model's built-in dimension, not manual
"test-key",
undefined, // collectionName
)
})

Expand Down Expand Up @@ -480,6 +484,7 @@ describe("CodeIndexServiceFactory", () => {
"http://localhost:6333",
manualDimension, // Should use manual dimension as fallback
"test-key",
undefined, // collectionName
)
})

Expand Down Expand Up @@ -509,6 +514,7 @@ describe("CodeIndexServiceFactory", () => {
"http://localhost:6333",
768,
"test-key",
undefined, // collectionName
)
})

Expand Down Expand Up @@ -578,6 +584,7 @@ describe("CodeIndexServiceFactory", () => {
"http://localhost:6333",
3072,
"test-key",
undefined, // collectionName
)
})

Expand All @@ -603,6 +610,7 @@ describe("CodeIndexServiceFactory", () => {
"http://localhost:6333",
3072,
"test-key",
undefined, // collectionName
)
})

Expand All @@ -627,6 +635,7 @@ describe("CodeIndexServiceFactory", () => {
"http://localhost:6333",
1536,
"test-key",
undefined, // collectionName
)
})

Expand Down
19 changes: 17 additions & 2 deletions src/services/code-index/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class CodeIndexConfigManager {
private mistralOptions?: { apiKey: string }
private vercelAiGatewayOptions?: { apiKey: string }
private qdrantUrl?: string = "http://localhost:6333"
private qdrantCollectionName?: string
private qdrantApiKey?: string
private searchMinScore?: number
private searchMaxResults?: number
Expand All @@ -46,6 +47,7 @@ export class CodeIndexConfigManager {
const codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://localhost:6333",
codebaseIndexQdrantCollectionName: "",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "",
Expand All @@ -56,6 +58,7 @@ export class CodeIndexConfigManager {
const {
codebaseIndexEnabled,
codebaseIndexQdrantUrl,
codebaseIndexQdrantCollectionName,
codebaseIndexEmbedderProvider,
codebaseIndexEmbedderBaseUrl,
codebaseIndexEmbedderModelId,
Expand All @@ -75,6 +78,7 @@ export class CodeIndexConfigManager {
// Update instance variables with configuration
this.codebaseIndexEnabled = codebaseIndexEnabled ?? true
this.qdrantUrl = codebaseIndexQdrantUrl
this.qdrantCollectionName = codebaseIndexQdrantCollectionName
this.qdrantApiKey = qdrantApiKey ?? ""
this.searchMinScore = codebaseIndexSearchMinScore
this.searchMaxResults = codebaseIndexSearchMaxResults
Expand Down Expand Up @@ -148,6 +152,7 @@ export class CodeIndexConfigManager {
mistralOptions?: { apiKey: string }
vercelAiGatewayOptions?: { apiKey: string }
qdrantUrl?: string
qdrantCollectionName?: string
qdrantApiKey?: string
searchMinScore?: number
}
Expand All @@ -168,6 +173,7 @@ export class CodeIndexConfigManager {
mistralApiKey: this.mistralOptions?.apiKey ?? "",
vercelAiGatewayApiKey: this.vercelAiGatewayOptions?.apiKey ?? "",
qdrantUrl: this.qdrantUrl ?? "",
qdrantCollectionName: this.qdrantCollectionName ?? "",
qdrantApiKey: this.qdrantApiKey ?? "",
}

Expand All @@ -193,6 +199,7 @@ export class CodeIndexConfigManager {
mistralOptions: this.mistralOptions,
vercelAiGatewayOptions: this.vercelAiGatewayOptions,
qdrantUrl: this.qdrantUrl,
qdrantCollectionName: this.qdrantCollectionName,
qdrantApiKey: this.qdrantApiKey,
searchMinScore: this.currentSearchMinScore,
},
Expand Down Expand Up @@ -270,6 +277,7 @@ export class CodeIndexConfigManager {
const prevMistralApiKey = prev?.mistralApiKey ?? ""
const prevVercelAiGatewayApiKey = prev?.vercelAiGatewayApiKey ?? ""
const prevQdrantUrl = prev?.qdrantUrl ?? ""
const prevQdrantCollectionName = prev?.qdrantCollectionName ?? ""
const prevQdrantApiKey = prev?.qdrantApiKey ?? ""

// 1. Transition from disabled/unconfigured to enabled/configured
Expand Down Expand Up @@ -308,6 +316,7 @@ export class CodeIndexConfigManager {
const currentMistralApiKey = this.mistralOptions?.apiKey ?? ""
const currentVercelAiGatewayApiKey = this.vercelAiGatewayOptions?.apiKey ?? ""
const currentQdrantUrl = this.qdrantUrl ?? ""
const currentQdrantCollectionName = this.qdrantCollectionName ?? ""
const currentQdrantApiKey = this.qdrantApiKey ?? ""

if (prevOpenAiKey !== currentOpenAiKey) {
Expand Down Expand Up @@ -342,7 +351,11 @@ export class CodeIndexConfigManager {
return true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Issue: Missing restart detection for collection name changes.

The doesConfigChangeRequireRestart method doesn't check if qdrantCollectionName has changed. When users change the collection name, it requires a restart to take effect (since it affects which Qdrant collection is used), but this won't be detected.

Could we add this check around line 351?

Suggested change
return true
if (
prevQdrantUrl !== currentQdrantUrl ||
prevQdrantCollectionName !== currentQdrantCollectionName ||
prevQdrantApiKey !== currentQdrantApiKey
) {
return true
}

}

if (prevQdrantUrl !== currentQdrantUrl || prevQdrantApiKey !== currentQdrantApiKey) {
if (
prevQdrantUrl !== currentQdrantUrl ||
prevQdrantCollectionName !== currentQdrantCollectionName ||
prevQdrantApiKey !== currentQdrantApiKey
) {
return true
}

Expand Down Expand Up @@ -396,6 +409,7 @@ export class CodeIndexConfigManager {
mistralOptions: this.mistralOptions,
vercelAiGatewayOptions: this.vercelAiGatewayOptions,
qdrantUrl: this.qdrantUrl,
qdrantCollectionName: this.qdrantCollectionName,
qdrantApiKey: this.qdrantApiKey,
searchMinScore: this.currentSearchMinScore,
searchMaxResults: this.currentSearchMaxResults,
Expand Down Expand Up @@ -426,9 +440,10 @@ export class CodeIndexConfigManager {
/**
* Gets the current Qdrant configuration
*/
public get qdrantConfig(): { url?: string; apiKey?: string } {
public get qdrantConfig(): { url?: string; collectionName?: string; apiKey?: string } {
return {
url: this.qdrantUrl,
collectionName: this.qdrantCollectionName,
apiKey: this.qdrantApiKey,
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/services/code-index/interfaces/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface CodeIndexConfig {
mistralOptions?: { apiKey: string }
vercelAiGatewayOptions?: { apiKey: string }
qdrantUrl?: string
qdrantCollectionName?: string
qdrantApiKey?: string
searchMinScore?: number
searchMaxResults?: number
Expand All @@ -38,5 +39,6 @@ export type PreviousConfigSnapshot = {
mistralApiKey?: string
vercelAiGatewayApiKey?: string
qdrantUrl?: string
qdrantCollectionName?: string
qdrantApiKey?: string
}
10 changes: 8 additions & 2 deletions src/services/code-index/service-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,14 @@ export class CodeIndexServiceFactory {
throw new Error(t("embeddings:serviceFactory.qdrantUrlMissing"))
}

// Assuming constructor is updated: new QdrantVectorStore(workspacePath, url, vectorSize, apiKey?)
return new QdrantVectorStore(this.workspacePath, config.qdrantUrl, vectorSize, config.qdrantApiKey)
// Pass collection name if configured, otherwise let QdrantVectorStore generate it
return new QdrantVectorStore(
this.workspacePath,
config.qdrantUrl,
vectorSize,
config.qdrantApiKey,
config.qdrantCollectionName,
)
}

/**
Expand Down
15 changes: 11 additions & 4 deletions src/services/code-index/vector-store/qdrant-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class QdrantVectorStore implements IVectorStore {
* @param workspacePath Path to the workspace
* @param url Optional URL to the Qdrant server
*/
constructor(workspacePath: string, url: string, vectorSize: number, apiKey?: string) {
constructor(workspacePath: string, url: string, vectorSize: number, apiKey?: string, collectionName?: string) {
// Parse the URL to determine the appropriate QdrantClient configuration
const parsedUrl = this.parseQdrantUrl(url)

Expand Down Expand Up @@ -77,10 +77,17 @@ export class QdrantVectorStore implements IVectorStore {
})
}

// Generate collection name from workspace path
const hash = createHash("sha256").update(workspacePath).digest("hex")
// Use provided collection name or generate from workspace path
if (collectionName && collectionName.trim()) {
// Sanitize the collection name to ensure it's valid for Qdrant
// Qdrant collection names must match: ^[a-zA-Z0-9_-]+$
this.collectionName = collectionName.trim().replace(/[^a-zA-Z0-9_-]/g, "-")
} else {
// Generate collection name from workspace path (default behavior)
const hash = createHash("sha256").update(workspacePath).digest("hex")
this.collectionName = `ws-${hash.substring(0, 16)}`
}
this.vectorSize = vectorSize
this.collectionName = `ws-${hash.substring(0, 16)}`
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ export interface WebviewMessage {
// Global state settings
codebaseIndexEnabled: boolean
codebaseIndexQdrantUrl: string
codebaseIndexQdrantCollectionName?: string
codebaseIndexEmbedderProvider:
| "openai"
| "ollama"
Expand Down
27 changes: 27 additions & 0 deletions webview-ui/src/components/chat/CodeIndexPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ interface LocalCodeIndexSettings {
// Global state settings
codebaseIndexEnabled: boolean
codebaseIndexQdrantUrl: string
codebaseIndexQdrantCollectionName?: string
codebaseIndexEmbedderProvider: EmbedderProvider
codebaseIndexEmbedderBaseUrl?: string
codebaseIndexEmbedderModelId: string
Expand Down Expand Up @@ -181,6 +182,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
const getDefaultSettings = (): LocalCodeIndexSettings => ({
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "",
codebaseIndexQdrantCollectionName: "",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "",
Expand Down Expand Up @@ -213,6 +215,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
const settings = {
codebaseIndexEnabled: codebaseIndexConfig.codebaseIndexEnabled ?? true,
codebaseIndexQdrantUrl: codebaseIndexConfig.codebaseIndexQdrantUrl || "",
codebaseIndexQdrantCollectionName: codebaseIndexConfig.codebaseIndexQdrantCollectionName || "",
codebaseIndexEmbedderProvider: codebaseIndexConfig.codebaseIndexEmbedderProvider || "openai",
codebaseIndexEmbedderBaseUrl: codebaseIndexConfig.codebaseIndexEmbedderBaseUrl || "",
codebaseIndexEmbedderModelId: codebaseIndexConfig.codebaseIndexEmbedderModelId || "",
Expand Down Expand Up @@ -1160,6 +1163,30 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
)}
</div>

<div className="space-y-2">
<label className="text-sm font-medium">
{t("settings:codeIndex.qdrantCollectionNameLabel")}
</label>
<VSCodeTextField
value={currentSettings.codebaseIndexQdrantCollectionName || ""}
onInput={(e: any) =>
updateSetting("codebaseIndexQdrantCollectionName", e.target.value)
}
placeholder={t("settings:codeIndex.qdrantCollectionNamePlaceholder")}
className={cn("w-full", {
"border-red-500": formErrors.codebaseIndexQdrantCollectionName,
})}
/>
{formErrors.codebaseIndexQdrantCollectionName && (
<p className="text-xs text-vscode-errorForeground mt-1 mb-0">
{formErrors.codebaseIndexQdrantCollectionName}
</p>
)}
<p className="text-xs text-vscode-descriptionForeground mt-1">
{t("settings:codeIndex.qdrantCollectionNameDescription")}
</p>
</div>

<div className="space-y-2">
<label className="text-sm font-medium">
{t("settings:codeIndex.qdrantApiKeyLabel")}
Expand Down
5 changes: 4 additions & 1 deletion webview-ui/src/i18n/locales/ca/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion webview-ui/src/i18n/locales/de/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
"qdrantKeyLabel": "Qdrant Key:",
"qdrantApiKeyLabel": "Qdrant API Key",
"qdrantApiKeyPlaceholder": "Enter your Qdrant API key (optional)",
"qdrantCollectionNameLabel": "Qdrant Collection Name",
"qdrantCollectionNamePlaceholder": "Enter collection name (optional)",
"qdrantCollectionNameDescription": "Custom collection name for storing embeddings. If empty, a name will be auto-generated from your workspace path.",
"setupConfigLabel": "Setup",
"advancedConfigLabel": "Advanced Configuration",
"searchMinScoreLabel": "Search Score Threshold",
Expand Down
5 changes: 4 additions & 1 deletion webview-ui/src/i18n/locales/es/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion webview-ui/src/i18n/locales/fr/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading