Skip to content

Commit 8d5d4f8

Browse files
committed
First cut on different UI
Signed-off-by: Geoff Wilson <[email protected]>
1 parent 4ea7562 commit 8d5d4f8

File tree

5 files changed

+85
-3
lines changed

5 files changed

+85
-3
lines changed

packages/types/src/provider-settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const apiModelIdProviderModelSchema = baseProviderSettingsSchema.extend({
7373

7474
const anthropicSchema = apiModelIdProviderModelSchema.extend({
7575
apiKey: z.string().optional(),
76+
anthropicApiKeyUseEnvVar: z.boolean().optional(),
7677
anthropicBaseUrl: z.string().optional(),
7778
anthropicUseAuthToken: z.boolean().optional(),
7879
})
@@ -262,6 +263,7 @@ export const PROVIDER_SETTINGS_KEYS = keysOf<ProviderSettings>()([
262263
// Anthropic
263264
"apiModelId",
264265
"apiKey",
266+
"anthropicApiKeyUseEnvVar",
265267
"anthropicBaseUrl",
266268
"anthropicUseAuthToken",
267269
// Glama

scripts/find-missing-translations.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
* --locale=<locale> Only check a specific locale (e.g. --locale=fr)
99
* --file=<file> Only check a specific file (e.g. --file=chat.json)
1010
* --area=<area> Only check a specific area (core, webview, or both)
11+
* --create-missing Creates missing keys in the other locales with the EN value
1112
* --help Show this help message
1213
*/
1314

15+
const { set } = require("@dotenvx/dotenvx")
1416
const fs = require("fs")
1517
const path = require("path")
18+
let createMissing = false
1619

1720
// Process command line arguments
1821
const args = process.argv.slice(2).reduce(
@@ -30,6 +33,8 @@ const args = process.argv.slice(2).reduce(
3033
console.error(`Error: Invalid area '${acc.area}'. Must be 'core', 'webview', or 'both'.`)
3134
process.exit(1)
3235
}
36+
} else if (arg.startsWith("--create-missing")) {
37+
createMissing = true
3338
}
3439
return acc
3540
},
@@ -54,6 +59,7 @@ Options:
5459
'core' = Backend (src/i18n/locales)
5560
'webview' = Frontend UI (webview-ui/src/i18n/locales)
5661
'both' = Check both areas (default)
62+
--create-missing Creates missing keys in the other locales with the EN value
5763
--help Show this help message
5864
5965
Output:
@@ -105,6 +111,27 @@ function getValueAtPath(obj, path) {
105111
return current
106112
}
107113

114+
// Set value at a dotted path in an object
115+
function setValueAtPath(obj, path, value) {
116+
const parts = path.split(".")
117+
let current = obj
118+
119+
for (let i = 0; i < parts.length; i++) {
120+
const part = parts[i]
121+
122+
// If it's the last part, set the value
123+
if (i === parts.length - 1) {
124+
current[part] = value
125+
} else {
126+
// If the key doesn't exist or isn't an object, create an empty object
127+
if (current[part] === undefined || typeof current[part] !== "object") {
128+
current[part] = {}
129+
}
130+
current = current[part]
131+
}
132+
}
133+
}
134+
108135
// Function to check translations for a specific area
109136
function checkAreaTranslations(area) {
110137
const LOCALES_DIR = LOCALES_DIRS[area]
@@ -198,11 +225,17 @@ function checkAreaTranslations(area) {
198225
key,
199226
englishValue,
200227
})
228+
if (createMissing === true) {
229+
setValueAtPath(localeContent, key, englishValue) // Set the missing key in the locale content
230+
}
201231
}
202232
}
203233

204234
if (missingKeys.length > 0) {
205235
missingTranslations[locale][name] = missingKeys
236+
if (createMissing === true) {
237+
fs.writeFileSync(localeFilePath, JSON.stringify(localeContent, null, "\t"))
238+
}
206239
}
207240
}
208241
}

src/api/index.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,33 @@ export interface ApiHandler {
5858
countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number>
5959
}
6060

61+
62+
/**
63+
* Read an environment variable value, returning a default value if not set. If neither key nor default is set,
64+
* returns undefined
65+
* @param key The environment variable key
66+
* @param defaultValue The default value to return if the variable is not set
67+
* @returns The value of the environment variable or the default value
68+
*/
69+
export function getEnvVar(key: string | undefined, defaultValue?: string | undefined): string | undefined {
70+
if (key === undefined) {
71+
return defaultValue
72+
}
73+
return process.env[key as string] ?? defaultValue
74+
}
75+
6176
export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
6277
const { apiProvider, ...options } = configuration
6378

6479
switch (apiProvider) {
6580
case "anthropic":
81+
options.apiKey = getEnvVar("ANTHROPIC_API_KEY", options.apiKey)
6682
return new AnthropicHandler(options)
6783
case "glama":
84+
options.glamaApiKey = getEnvVar("GLAMA_API_KEY", options.glamaApiKey)
6885
return new GlamaHandler(options)
6986
case "openrouter":
87+
options.openRouterApiKey = getEnvVar("OPEN_ROUTER_API_KEY", options.openRouterApiKey)
7088
return new OpenRouterHandler(options)
7189
case "bedrock":
7290
return new AwsBedrockHandler(options)
@@ -75,38 +93,53 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
7593
? new AnthropicVertexHandler(options)
7694
: new VertexHandler(options)
7795
case "openai":
96+
options.openAiApiKey = getEnvVar("OPEN_AI_API_KEY", options.openAiApiKey)
7897
return new OpenAiHandler(options)
7998
case "ollama":
8099
return new OllamaHandler(options)
81100
case "lmstudio":
82101
return new LmStudioHandler(options)
83102
case "gemini":
103+
options.geminiApiKey = getEnvVar("GEMINI_API_KEY", options.geminiApiKey)
84104
return new GeminiHandler(options)
85105
case "openai-native":
106+
options.openAiNativeApiKey = getEnvVar(
107+
"OPEN_AI_NATIVE_API_KEY",
108+
options.openAiNativeApiKey
109+
)
86110
return new OpenAiNativeHandler(options)
87111
case "deepseek":
112+
options.deepSeekApiKey = getEnvVar("DEEP_SEEK_API_KEY", options.deepSeekApiKey)
88113
return new DeepSeekHandler(options)
89114
case "vscode-lm":
90115
return new VsCodeLmHandler(options)
91116
case "mistral":
117+
options.mistralApiKey = getEnvVar("MISTRAL_API_KEY", options.mistralApiKey)
92118
return new MistralHandler(options)
93119
case "unbound":
120+
options.unboundApiKey = getEnvVar("UNBOUND_API_KEY", options.unboundApiKey)
94121
return new UnboundHandler(options)
95122
case "requesty":
123+
options.requestyApiKey = getEnvVar("REQUESTY_API_KEY", options.requestyApiKey)
96124
return new RequestyHandler(options)
97125
case "human-relay":
98126
return new HumanRelayHandler()
99127
case "fake-ai":
100128
return new FakeAIHandler(options)
101129
case "xai":
130+
options.xaiApiKey = getEnvVar("XAI_API_KEY", options.xaiApiKey)
102131
return new XAIHandler(options)
103132
case "groq":
133+
options.groqApiKey = getEnvVar("GROQ_API_KEY", options.groqApiKey)
104134
return new GroqHandler(options)
105135
case "chutes":
136+
options.chutesApiKey = getEnvVar("CHUTES_API_KEY", options.chutesApiKey)
106137
return new ChutesHandler(options)
107138
case "litellm":
139+
options.litellmApiKey = getEnvVar("LITELLM_API_KEY", options.litellmApiKey)
108140
return new LiteLLMHandler(options)
109141
default:
142+
options.apiKey = getEnvVar("ANTROPIC_API_KEY", options.apiKey)
110143
return new AnthropicHandler(options)
111144
}
112145
}

webview-ui/src/components/settings/providers/Anthropic.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: Anthro
1818
const { t } = useAppTranslation()
1919

2020
const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl)
21+
const [useApiKeyEnvVar, setUseApiKeyEnvVar] = useState(!!apiConfiguration?.anthropicApiKeyUseEnvVar)
2122

2223
const handleInputChange = useCallback(
2324
<K extends keyof ProviderSettings, E>(
@@ -30,20 +31,33 @@ export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: Anthro
3031
[setApiConfigurationField],
3132
)
3233

34+
const handleUseApiKeyEnvVarChange = (checked: boolean) => {
35+
setUseApiKeyEnvVar(checked)
36+
setApiConfigurationField("anthropicApiKeyUseEnvVar", checked)
37+
}
38+
3339
return (
3440
<>
3541
<VSCodeTextField
3642
value={apiConfiguration?.apiKey || ""}
3743
type="password"
3844
onInput={handleInputChange("apiKey")}
3945
placeholder={t("settings:placeholders.apiKey")}
40-
className="w-full">
46+
className="w-full"
47+
disabled={useApiKeyEnvVar}
48+
>
4149
<label className="block font-medium mb-1">{t("settings:providers.anthropicApiKey")}</label>
4250
</VSCodeTextField>
4351
<div className="text-sm text-vscode-descriptionForeground -mt-2">
4452
{t("settings:providers.apiKeyStorageNotice")}
4553
</div>
46-
{!apiConfiguration?.apiKey && (
54+
<Checkbox
55+
checked={useApiKeyEnvVar}
56+
onChange={handleUseApiKeyEnvVarChange}
57+
className="mb-2">
58+
{t("settings:providers.apiKeyUseEnvVar", { name: "ANTHROPIC_API_KEY"})}
59+
</Checkbox>
60+
{(!(apiConfiguration?.apiKey || apiConfiguration?.anthropicApiKeyUseEnvVar)) && (
4761
<VSCodeButtonLink href="https://console.anthropic.com/settings/keys" appearance="secondary">
4862
{t("settings:providers.getAnthropicApiKey")}
4963
</VSCodeButtonLink>

webview-ui/src/utils/validate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function validateApiConfiguration(apiConfiguration: ProviderSettings): st
3232
}
3333
break
3434
case "anthropic":
35-
if (!apiConfiguration.apiKey) {
35+
if (!(apiConfiguration.apiKey || apiConfiguration.anthropicApiKeyUseEnvVar)) {
3636
return i18next.t("settings:validation.apiKey")
3737
}
3838
break

0 commit comments

Comments
 (0)