mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add API key support for Ollama Embedder Provider
- Add ollamaApiKey parameter to CodeIndexOllamaEmbedder constructor - Include Authorization header in all API requests when API key is provided - Update CodeIndexConfigManager to read and store Ollama API key from secrets - Add UI field for Ollama API key input in CodeIndexPopover component - Update type definitions to include codebaseIndexOllamaApiKey secret - Add comprehensive tests for API key authentication - Maintain backward compatibility for local Ollama instances without auth Closes RooCodeInc/Roo-Code#8737
This commit is contained in:
parent
a8f87d2b1d
commit
f3102f8134
9 changed files with 346 additions and 11 deletions
|
|
@ -68,6 +68,7 @@ export const codebaseIndexProviderSchema = z.object({
|
|||
codebaseIndexGeminiApiKey: z.string().optional(),
|
||||
codebaseIndexMistralApiKey: z.string().optional(),
|
||||
codebaseIndexVercelAiGatewayApiKey: z.string().optional(),
|
||||
codebaseIndexOllamaApiKey: z.string().optional(),
|
||||
})
|
||||
|
||||
export type CodebaseIndexProvider = z.infer<typeof codebaseIndexProviderSchema>
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ export const SECRET_STATE_KEYS = [
|
|||
"codebaseIndexGeminiApiKey",
|
||||
"codebaseIndexMistralApiKey",
|
||||
"codebaseIndexVercelAiGatewayApiKey",
|
||||
"codebaseIndexOllamaApiKey",
|
||||
"huggingFaceApiKey",
|
||||
"sambaNovaApiKey",
|
||||
"zaiApiKey",
|
||||
|
|
|
|||
|
|
@ -2493,6 +2493,12 @@ export const webviewMessageHandler = async (
|
|||
settings.codebaseIndexVercelAiGatewayApiKey,
|
||||
)
|
||||
}
|
||||
if (settings.codebaseIndexOllamaApiKey !== undefined) {
|
||||
await provider.contextProxy.storeSecret(
|
||||
"codebaseIndexOllamaApiKey",
|
||||
settings.codebaseIndexOllamaApiKey,
|
||||
)
|
||||
}
|
||||
|
||||
// Send success response first - settings are saved regardless of validation
|
||||
await provider.postMessageToWebview({
|
||||
|
|
@ -2630,6 +2636,7 @@ export const webviewMessageHandler = async (
|
|||
const hasVercelAiGatewayApiKey = !!(await provider.context.secrets.get(
|
||||
"codebaseIndexVercelAiGatewayApiKey",
|
||||
))
|
||||
const hasOllamaApiKey = !!(await provider.context.secrets.get("codebaseIndexOllamaApiKey"))
|
||||
|
||||
provider.postMessageToWebview({
|
||||
type: "codeIndexSecretStatus",
|
||||
|
|
@ -2640,6 +2647,7 @@ export const webviewMessageHandler = async (
|
|||
hasGeminiApiKey,
|
||||
hasMistralApiKey,
|
||||
hasVercelAiGatewayApiKey,
|
||||
hasOllamaApiKey,
|
||||
},
|
||||
})
|
||||
break
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export class CodeIndexConfigManager {
|
|||
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""
|
||||
const mistralApiKey = this.contextProxy?.getSecret("codebaseIndexMistralApiKey") ?? ""
|
||||
const vercelAiGatewayApiKey = this.contextProxy?.getSecret("codebaseIndexVercelAiGatewayApiKey") ?? ""
|
||||
const ollamaApiKey = this.contextProxy?.getSecret("codebaseIndexOllamaApiKey") ?? ""
|
||||
|
||||
// Update instance variables with configuration
|
||||
this.codebaseIndexEnabled = codebaseIndexEnabled ?? true
|
||||
|
|
@ -116,6 +117,7 @@ export class CodeIndexConfigManager {
|
|||
|
||||
this.ollamaOptions = {
|
||||
ollamaBaseUrl: codebaseIndexEmbedderBaseUrl,
|
||||
ollamaApiKey: ollamaApiKey || undefined,
|
||||
}
|
||||
|
||||
this.openAiCompatibleOptions =
|
||||
|
|
@ -162,6 +164,7 @@ export class CodeIndexConfigManager {
|
|||
modelDimension: this.modelDimension,
|
||||
openAiKey: this.openAiOptions?.openAiNativeApiKey ?? "",
|
||||
ollamaBaseUrl: this.ollamaOptions?.ollamaBaseUrl ?? "",
|
||||
ollamaApiKey: this.ollamaOptions?.ollamaApiKey ?? "",
|
||||
openAiCompatibleBaseUrl: this.openAiCompatibleOptions?.baseUrl ?? "",
|
||||
openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "",
|
||||
geminiApiKey: this.geminiOptions?.apiKey ?? "",
|
||||
|
|
@ -263,6 +266,7 @@ export class CodeIndexConfigManager {
|
|||
const prevProvider = prev?.embedderProvider ?? "openai"
|
||||
const prevOpenAiKey = prev?.openAiKey ?? ""
|
||||
const prevOllamaBaseUrl = prev?.ollamaBaseUrl ?? ""
|
||||
const prevOllamaApiKey = prev?.ollamaApiKey ?? ""
|
||||
const prevOpenAiCompatibleBaseUrl = prev?.openAiCompatibleBaseUrl ?? ""
|
||||
const prevOpenAiCompatibleApiKey = prev?.openAiCompatibleApiKey ?? ""
|
||||
const prevModelDimension = prev?.modelDimension
|
||||
|
|
@ -301,6 +305,7 @@ export class CodeIndexConfigManager {
|
|||
// Authentication changes (API keys)
|
||||
const currentOpenAiKey = this.openAiOptions?.openAiNativeApiKey ?? ""
|
||||
const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? ""
|
||||
const currentOllamaApiKey = this.ollamaOptions?.ollamaApiKey ?? ""
|
||||
const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? ""
|
||||
const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
|
||||
const currentModelDimension = this.modelDimension
|
||||
|
|
@ -314,7 +319,7 @@ export class CodeIndexConfigManager {
|
|||
return true
|
||||
}
|
||||
|
||||
if (prevOllamaBaseUrl !== currentOllamaBaseUrl) {
|
||||
if (prevOllamaBaseUrl !== currentOllamaBaseUrl || prevOllamaApiKey !== currentOllamaApiKey) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,15 @@ describe("CodeIndexOllamaEmbedder", () => {
|
|||
expect(embedderWithDefaults.embedderInfo.name).toBe("ollama")
|
||||
})
|
||||
|
||||
it("should initialize with API key when provided", () => {
|
||||
const embedderWithApiKey = new CodeIndexOllamaEmbedder({
|
||||
ollamaModelId: "nomic-embed-text",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
ollamaApiKey: "test-api-key-123",
|
||||
})
|
||||
expect(embedderWithApiKey.embedderInfo.name).toBe("ollama")
|
||||
})
|
||||
|
||||
it("should normalize URLs with trailing slashes", async () => {
|
||||
// Create embedder with URL that has a trailing slash
|
||||
const embedderWithTrailingSlash = new CodeIndexOllamaEmbedder({
|
||||
|
|
@ -166,6 +175,128 @@ describe("CodeIndexOllamaEmbedder", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("API Key Authentication", () => {
|
||||
it("should include Authorization header when API key is provided", async () => {
|
||||
const embedderWithApiKey = new CodeIndexOllamaEmbedder({
|
||||
ollamaModelId: "nomic-embed-text",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
ollamaApiKey: "test-api-key-123",
|
||||
})
|
||||
|
||||
// Mock successful /api/tags call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
models: [{ name: "nomic-embed-text" }],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
// Mock successful /api/embed test call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
embeddings: [[0.1, 0.2, 0.3]],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
await embedderWithApiKey.validateConfiguration()
|
||||
|
||||
// Check that Authorization header is included in both calls
|
||||
expect(mockFetch).toHaveBeenCalledTimes(2)
|
||||
|
||||
// First call to /api/tags
|
||||
expect(mockFetch.mock.calls[0][1]?.headers).toEqual({
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer test-api-key-123",
|
||||
})
|
||||
|
||||
// Second call to /api/embed
|
||||
expect(mockFetch.mock.calls[1][1]?.headers).toEqual({
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer test-api-key-123",
|
||||
})
|
||||
})
|
||||
|
||||
it("should not include Authorization header when API key is not provided", async () => {
|
||||
// Mock successful /api/tags call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
models: [{ name: "nomic-embed-text" }],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
// Mock successful /api/embed test call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
embeddings: [[0.1, 0.2, 0.3]],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
await embedder.validateConfiguration()
|
||||
|
||||
// Check that Authorization header is NOT included
|
||||
expect(mockFetch).toHaveBeenCalledTimes(2)
|
||||
|
||||
// First call to /api/tags
|
||||
expect(mockFetch.mock.calls[0][1]?.headers).toEqual({
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
|
||||
// Second call to /api/embed
|
||||
expect(mockFetch.mock.calls[1][1]?.headers).toEqual({
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle authentication errors with API key", async () => {
|
||||
const embedderWithApiKey = new CodeIndexOllamaEmbedder({
|
||||
ollamaModelId: "nomic-embed-text",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
ollamaApiKey: "invalid-api-key",
|
||||
})
|
||||
|
||||
// Mock 401 Unauthorized response
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 401,
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedderWithApiKey.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("embeddings:ollama.serviceUnavailable")
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:11434/api/tags",
|
||||
expect.objectContaining({
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer invalid-api-key",
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateConfiguration", () => {
|
||||
it("should validate successfully when service is available and model exists", async () => {
|
||||
// Mock successful /api/tags call
|
||||
|
|
@ -323,5 +454,142 @@ describe("CodeIndexOllamaEmbedder", () => {
|
|||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Network timeout")
|
||||
})
|
||||
|
||||
describe("createEmbeddings", () => {
|
||||
it("should create embeddings successfully without API key", async () => {
|
||||
const texts = ["Hello world", "Test embedding"]
|
||||
|
||||
// Mock successful /api/embed call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
embeddings: [
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedder.createEmbeddings(texts)
|
||||
|
||||
expect(result).toEqual({
|
||||
embeddings: [
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
})
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:11434/api/embed",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "nomic-embed-text",
|
||||
input: texts,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should create embeddings with API key in Authorization header", async () => {
|
||||
const embedderWithApiKey = new CodeIndexOllamaEmbedder({
|
||||
ollamaModelId: "nomic-embed-text",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
ollamaApiKey: "test-api-key-123",
|
||||
})
|
||||
|
||||
const texts = ["Hello world", "Test embedding"]
|
||||
|
||||
// Mock successful /api/embed call
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
embeddings: [
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
}),
|
||||
} as Response),
|
||||
)
|
||||
|
||||
const result = await embedderWithApiKey.createEmbeddings(texts)
|
||||
|
||||
expect(result).toEqual({
|
||||
embeddings: [
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.4, 0.5, 0.6],
|
||||
],
|
||||
})
|
||||
|
||||
// Verify Authorization header is included
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:11434/api/embed",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer test-api-key-123",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "nomic-embed-text",
|
||||
input: texts,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle authentication error when creating embeddings", async () => {
|
||||
const embedderWithApiKey = new CodeIndexOllamaEmbedder({
|
||||
ollamaModelId: "nomic-embed-text",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
ollamaApiKey: "invalid-api-key",
|
||||
})
|
||||
|
||||
const texts = ["Hello world"]
|
||||
|
||||
// Mock 401 Unauthorized response
|
||||
mockFetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 401,
|
||||
statusText: "Unauthorized",
|
||||
} as Response),
|
||||
)
|
||||
|
||||
await expect(embedderWithApiKey.createEmbeddings(texts)).rejects.toThrow(
|
||||
"embeddings:ollama.embeddingFailed",
|
||||
)
|
||||
|
||||
// Verify request included the API key
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:11434/api/embed",
|
||||
expect.objectContaining({
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer invalid-api-key",
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle network errors when creating embeddings", async () => {
|
||||
const texts = ["Hello world"]
|
||||
|
||||
// Mock network error
|
||||
mockFetch.mockRejectedValueOnce(new Error("Network error"))
|
||||
|
||||
await expect(embedder.createEmbeddings(texts)).rejects.toThrow("embeddings:ollama.embeddingFailed")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const OLLAMA_VALIDATION_TIMEOUT_MS = 30000 // 30 seconds for validation requests
|
|||
export class CodeIndexOllamaEmbedder implements IEmbedder {
|
||||
private readonly baseUrl: string
|
||||
private readonly defaultModelId: string
|
||||
private readonly apiKey?: string
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
// Ensure ollamaBaseUrl and ollamaModelId exist on ApiHandlerOptions or add defaults
|
||||
|
|
@ -27,6 +28,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
|
|||
|
||||
this.baseUrl = baseUrl
|
||||
this.defaultModelId = options.ollamaModelId || "nomic-embed-text:latest"
|
||||
this.apiKey = options.ollamaApiKey
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -72,11 +74,17 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
|
|||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), OLLAMA_EMBEDDING_TIMEOUT_MS)
|
||||
|
||||
// Build headers with optional API key
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
if (this.apiKey) {
|
||||
headers["Authorization"] = `Bearer ${this.apiKey}`
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model: modelToUse,
|
||||
input: processedTexts, // Using 'input' as requested
|
||||
|
|
@ -151,11 +159,17 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
|
|||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), OLLAMA_VALIDATION_TIMEOUT_MS)
|
||||
|
||||
// Build headers with optional API key
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
if (this.apiKey) {
|
||||
headers["Authorization"] = `Bearer ${this.apiKey}`
|
||||
}
|
||||
|
||||
const modelsResponse = await fetch(modelsUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers,
|
||||
signal: controller.signal,
|
||||
})
|
||||
clearTimeout(timeoutId)
|
||||
|
|
@ -208,11 +222,17 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
|
|||
const testController = new AbortController()
|
||||
const testTimeoutId = setTimeout(() => testController.abort(), OLLAMA_VALIDATION_TIMEOUT_MS)
|
||||
|
||||
// Build headers with optional API key for test request
|
||||
const testHeaders: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
if (this.apiKey) {
|
||||
testHeaders["Authorization"] = `Bearer ${this.apiKey}`
|
||||
}
|
||||
|
||||
const testResponse = await fetch(testUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: testHeaders,
|
||||
body: JSON.stringify({
|
||||
model: this.defaultModelId,
|
||||
input: ["test"],
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export type PreviousConfigSnapshot = {
|
|||
modelDimension?: number // Generic dimension property
|
||||
openAiKey?: string
|
||||
ollamaBaseUrl?: string
|
||||
ollamaApiKey?: string
|
||||
openAiCompatibleBaseUrl?: string
|
||||
openAiCompatibleApiKey?: string
|
||||
geminiApiKey?: string
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ export interface WebviewMessage {
|
|||
codebaseIndexGeminiApiKey?: string
|
||||
codebaseIndexMistralApiKey?: string
|
||||
codebaseIndexVercelAiGatewayApiKey?: string
|
||||
codebaseIndexOllamaApiKey?: string
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ interface LocalCodeIndexSettings {
|
|||
codebaseIndexGeminiApiKey?: string
|
||||
codebaseIndexMistralApiKey?: string
|
||||
codebaseIndexVercelAiGatewayApiKey?: string
|
||||
codebaseIndexOllamaApiKey?: string
|
||||
}
|
||||
|
||||
// Validation schema for codebase index settings
|
||||
|
|
@ -101,6 +102,7 @@ const createValidationSchema = (provider: EmbedderProvider, t: any) => {
|
|||
.string()
|
||||
.min(1, t("settings:codeIndex.validation.ollamaBaseUrlRequired"))
|
||||
.url(t("settings:codeIndex.validation.invalidOllamaUrl")),
|
||||
codebaseIndexOllamaApiKey: z.string().optional(), // API key is optional for Ollama
|
||||
codebaseIndexEmbedderModelId: z.string().min(1, t("settings:codeIndex.validation.modelIdRequired")),
|
||||
codebaseIndexEmbedderModelDimension: z
|
||||
.number()
|
||||
|
|
@ -194,6 +196,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
codebaseIndexGeminiApiKey: "",
|
||||
codebaseIndexMistralApiKey: "",
|
||||
codebaseIndexVercelAiGatewayApiKey: "",
|
||||
codebaseIndexOllamaApiKey: "",
|
||||
})
|
||||
|
||||
// Initial settings state - stores the settings when popover opens
|
||||
|
|
@ -229,6 +232,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
codebaseIndexGeminiApiKey: "",
|
||||
codebaseIndexMistralApiKey: "",
|
||||
codebaseIndexVercelAiGatewayApiKey: "",
|
||||
codebaseIndexOllamaApiKey: "",
|
||||
}
|
||||
setInitialSettings(settings)
|
||||
setCurrentSettings(settings)
|
||||
|
|
@ -345,6 +349,9 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
? SECRET_PLACEHOLDER
|
||||
: ""
|
||||
}
|
||||
if (!prev.codebaseIndexOllamaApiKey || prev.codebaseIndexOllamaApiKey === SECRET_PLACEHOLDER) {
|
||||
updated.codebaseIndexOllamaApiKey = secretStatus.hasOllamaApiKey ? SECRET_PLACEHOLDER : ""
|
||||
}
|
||||
|
||||
return updated
|
||||
}
|
||||
|
|
@ -418,7 +425,8 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
key === "codebaseIndexOpenAiCompatibleApiKey" ||
|
||||
key === "codebaseIndexGeminiApiKey" ||
|
||||
key === "codebaseIndexMistralApiKey" ||
|
||||
key === "codebaseIndexVercelAiGatewayApiKey"
|
||||
key === "codebaseIndexVercelAiGatewayApiKey" ||
|
||||
key === "codebaseIndexOllamaApiKey"
|
||||
) {
|
||||
dataToValidate[key] = "placeholder-valid"
|
||||
}
|
||||
|
|
@ -772,6 +780,28 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{t("settings:codeIndex.ollamaApiKeyLabel")}
|
||||
</label>
|
||||
<VSCodeTextField
|
||||
type="password"
|
||||
value={currentSettings.codebaseIndexOllamaApiKey || ""}
|
||||
onInput={(e: any) =>
|
||||
updateSetting("codebaseIndexOllamaApiKey", e.target.value)
|
||||
}
|
||||
placeholder={t("settings:codeIndex.ollamaApiKeyPlaceholder")}
|
||||
className={cn("w-full", {
|
||||
"border-red-500": formErrors.codebaseIndexOllamaApiKey,
|
||||
})}
|
||||
/>
|
||||
{formErrors.codebaseIndexOllamaApiKey && (
|
||||
<p className="text-xs text-vscode-errorForeground mt-1 mb-0">
|
||||
{formErrors.codebaseIndexOllamaApiKey}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">
|
||||
{t("settings:codeIndex.modelLabel")}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue