Merge upstream/main into main

Resolved conflicts in src-tauri/src/lib.rs:
- Kept both: open_provider_terminal (my feature) and universal provider commands (upstream)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
w0x7ce
2025-12-29 09:14:42 +08:00
84 changed files with 6164 additions and 1498 deletions
+121 -35
View File
@@ -1,17 +1,23 @@
import { useCallback } from "react";
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { Plus } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { FullScreenPanel } from "@/components/common/FullScreenPanel";
import type { Provider, CustomEndpoint } from "@/types";
import type { Provider, CustomEndpoint, UniversalProvider } from "@/types";
import type { AppId } from "@/lib/api";
import { universalProvidersApi } from "@/lib/api";
import {
ProviderForm,
type ProviderFormValues,
} from "@/components/providers/forms/ProviderForm";
import { UniversalProviderFormModal } from "@/components/universal/UniversalProviderFormModal";
import { UniversalProviderPanel } from "@/components/universal";
import { providerPresets } from "@/config/claudeProviderPresets";
import { codexProviderPresets } from "@/config/codexProviderPresets";
import { geminiProviderPresets } from "@/config/geminiProviderPresets";
import type { UniversalProviderPreset } from "@/config/universalProviderPresets";
interface AddProviderDialogProps {
open: boolean;
@@ -27,6 +33,46 @@ export function AddProviderDialog({
onSubmit,
}: AddProviderDialogProps) {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState<"app-specific" | "universal">(
"app-specific",
);
const [universalFormOpen, setUniversalFormOpen] = useState(false);
const [selectedUniversalPreset, setSelectedUniversalPreset] =
useState<UniversalProviderPreset | null>(null);
// Handle universal provider save
const handleUniversalProviderSave = useCallback(
async (provider: UniversalProvider) => {
try {
await universalProvidersApi.upsert(provider);
toast.success(
t("universalProvider.addSuccess", {
defaultValue: "统一供应商添加成功",
}),
);
setUniversalFormOpen(false);
setSelectedUniversalPreset(null);
onOpenChange(false);
} catch (error) {
console.error(
"[AddProviderDialog] Failed to save universal provider",
error,
);
toast.error(
t("universalProvider.addFailed", {
defaultValue: "统一供应商添加失败",
}),
);
}
},
[t, onOpenChange],
);
// Close universal form and return to main dialog
const handleUniversalFormClose = useCallback(() => {
setUniversalFormOpen(false);
setSelectedUniversalPreset(null);
}, []);
const handleSubmit = useCallback(
async (values: ProviderFormValues) => {
@@ -156,46 +202,86 @@ export function AddProviderDialog({
[appId, onSubmit, onOpenChange],
);
const submitLabel =
appId === "claude"
? t("provider.addClaudeProvider")
: appId === "codex"
? t("provider.addCodexProvider")
: t("provider.addGeminiProvider");
const footer = (
<>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
className="border-border/20 hover:bg-accent hover:text-accent-foreground"
>
{t("common.cancel")}
</Button>
<Button
type="submit"
form="provider-form"
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
<Plus className="h-4 w-4 mr-2" />
{t("common.add")}
</Button>
</>
);
// 动态 footer:根据当前 Tab 显示不同按钮
const footer =
activeTab === "app-specific" ? (
<>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
className="border-border/20 hover:bg-accent hover:text-accent-foreground"
>
{t("common.cancel")}
</Button>
<Button
type="submit"
form="provider-form"
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
<Plus className="h-4 w-4 mr-2" />
{t("common.add")}
</Button>
</>
) : (
<>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
className="border-border/20 hover:bg-accent hover:text-accent-foreground"
>
{t("common.cancel")}
</Button>
<Button
onClick={() => setUniversalFormOpen(true)}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
<Plus className="h-4 w-4 mr-2" />
{t("universalProvider.add")}
</Button>
</>
);
return (
<FullScreenPanel
isOpen={open}
title={submitLabel}
title={t("provider.addNewProvider")}
onClose={() => onOpenChange(false)}
footer={footer}
>
<ProviderForm
appId={appId}
submitLabel={t("common.add")}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
showButtons={false}
<Tabs
value={activeTab}
onValueChange={(v) => setActiveTab(v as "app-specific" | "universal")}
>
<TabsList className="grid w-full grid-cols-2 mb-6">
<TabsTrigger value="app-specific">
{t(`apps.${appId}`)} {t("provider.tabProvider")}
</TabsTrigger>
<TabsTrigger value="universal">
{t("provider.tabUniversal")}
</TabsTrigger>
</TabsList>
<TabsContent value="app-specific" className="mt-0">
<ProviderForm
appId={appId}
submitLabel={t("common.add")}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}
showButtons={false}
/>
</TabsContent>
<TabsContent value="universal" className="mt-0">
<UniversalProviderPanel />
</TabsContent>
</Tabs>
{/* Universal Provider Form Modal */}
<UniversalProviderFormModal
isOpen={universalFormOpen}
onClose={handleUniversalFormClose}
onSave={handleUniversalProviderSave}
initialPreset={selectedUniversalPreset}
/>
</FullScreenPanel>
);
@@ -31,15 +31,12 @@ const ApiKeyInput: React.FC<ApiKeyInputProps> = ({
const inputClass = `w-full px-3 py-2 pr-10 border rounded-lg text-sm transition-colors ${
disabled
? "bg-muted border-border-default text-muted-foreground cursor-not-allowed"
: "border-border-default dark:bg-gray-800 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20"
: "border-border-default bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20"
}`;
return (
<div className="space-y-2">
<label
htmlFor={id}
className="block text-sm font-medium text-gray-900 dark:text-gray-100"
>
<label htmlFor={id} className="block text-sm font-medium text-foreground">
{label} {required && "*"}
</label>
<div className="relative">
@@ -58,7 +55,7 @@ const ApiKeyInput: React.FC<ApiKeyInputProps> = ({
<button
type="button"
onClick={toggleShowKey}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground transition-colors"
aria-label={showKey ? t("apiKeyInput.hide") : t("apiKeyInput.show")}
>
{showKey ? <EyeOff size={16} /> : <Eye size={16} />}
@@ -1,5 +1,6 @@
import { useTranslation } from "react-i18next";
import { FormLabel } from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
import { Input } from "@/components/ui/input";
import EndpointSpeedTest from "./EndpointSpeedTest";
import { ApiKeySection, EndpointField } from "./shared";
@@ -39,12 +40,14 @@ interface ClaudeFormFieldsProps {
// Model Selector
shouldShowModelSelector: boolean;
claudeModel: string;
reasoningModel: string;
defaultHaikuModel: string;
defaultSonnetModel: string;
defaultOpusModel: string;
onModelChange: (
field:
| "ANTHROPIC_MODEL"
| "ANTHROPIC_REASONING_MODEL"
| "ANTHROPIC_DEFAULT_HAIKU_MODEL"
| "ANTHROPIC_DEFAULT_SONNET_MODEL"
| "ANTHROPIC_DEFAULT_OPUS_MODEL",
@@ -53,6 +56,11 @@ interface ClaudeFormFieldsProps {
// Speed Test Endpoints
speedTestEndpoints: EndpointCandidate[];
// OpenRouter Compat
showOpenRouterCompatToggle: boolean;
openRouterCompatEnabled: boolean;
onOpenRouterCompatChange: (enabled: boolean) => void;
}
export function ClaudeFormFields({
@@ -77,11 +85,15 @@ export function ClaudeFormFields({
onCustomEndpointsChange,
shouldShowModelSelector,
claudeModel,
reasoningModel,
defaultHaikuModel,
defaultSonnetModel,
defaultOpusModel,
onModelChange,
speedTestEndpoints,
showOpenRouterCompatToggle,
openRouterCompatEnabled,
onOpenRouterCompatChange,
}: ClaudeFormFieldsProps) {
const { t } = useTranslation();
@@ -162,6 +174,28 @@ export function ClaudeFormFields({
/>
)}
{showOpenRouterCompatToggle && (
<div className="flex items-center justify-between rounded-lg border border-white/10 bg-background/60 p-4">
<div className="space-y-1">
<FormLabel>
{t("providerForm.openrouterCompatMode", {
defaultValue: "OpenRouter 兼容模式",
})}
</FormLabel>
<p className="text-xs text-muted-foreground">
{t("providerForm.openrouterCompatModeHint", {
defaultValue:
"使用 OpenAI Chat Completions 接口并转换为 Anthropic SSE。",
})}
</p>
</div>
<Switch
checked={openRouterCompatEnabled}
onCheckedChange={onOpenRouterCompatChange}
/>
</div>
)}
{/* 模型选择器 */}
{shouldShowModelSelector && (
<div className="space-y-3">
@@ -185,6 +219,27 @@ export function ClaudeFormFields({
/>
</div>
{/* 推理模型 */}
<div className="space-y-2">
<FormLabel htmlFor="reasoningModel">
{t("providerForm.anthropicReasoningModel", {
defaultValue: "推理模型 (Thinking)",
})}
</FormLabel>
<Input
id="reasoningModel"
type="text"
value={reasoningModel}
onChange={(e) =>
onModelChange("ANTHROPIC_REASONING_MODEL", e.target.value)
}
placeholder={t("providerForm.reasoningModelPlaceholder", {
defaultValue: "",
})}
autoComplete="off"
/>
</div>
{/* 默认 Haiku */}
<div className="space-y-2">
<FormLabel htmlFor="claudeDefaultHaikuModel">
@@ -47,7 +47,7 @@ export const CodexAuthSection: React.FC<CodexAuthSectionProps> = ({
<div className="space-y-2">
<label
htmlFor="codexAuth"
className="block text-sm font-medium text-gray-900 dark:text-gray-100"
className="block text-sm font-medium text-foreground"
>
{t("codexConfig.authJson")}
</label>
@@ -67,7 +67,7 @@ export const CodexAuthSection: React.FC<CodexAuthSectionProps> = ({
)}
{!error && (
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-muted-foreground">
{t("codexConfig.authJsonHint")}
</p>
)}
@@ -120,12 +120,12 @@ export const CodexConfigSection: React.FC<CodexConfigSectionProps> = ({
<div className="flex items-center justify-between">
<label
htmlFor="codexConfig"
className="block text-sm font-medium text-gray-900 dark:text-gray-100"
className="block text-sm font-medium text-foreground"
>
{t("codexConfig.configToml")}
</label>
<label className="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 cursor-pointer">
<label className="inline-flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">
<input
type="checkbox"
checked={useCommonConfig}
@@ -167,7 +167,7 @@ export const CodexConfigSection: React.FC<CodexConfigSectionProps> = ({
)}
{!configError && (
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-muted-foreground">
{t("codexConfig.configTomlHint")}
</p>
)}
@@ -98,7 +98,7 @@ export function CodexFormFields({
<div className="space-y-2">
<label
htmlFor="codexModelName"
className="block text-sm font-medium text-gray-900 dark:text-gray-100"
className="block text-sm font-medium text-foreground"
>
{t("codexConfig.modelName", { defaultValue: "模型名称" })}
</label>
@@ -110,9 +110,9 @@ export function CodexFormFields({
placeholder={t("codexConfig.modelNamePlaceholder", {
defaultValue: "例如: gpt-5-codex",
})}
className="w-full px-3 py-2 border border-border-default dark:bg-gray-800 dark:text-gray-100 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 transition-colors"
className="w-full px-3 py-2 border border-border-default bg-background text-foreground rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 transition-colors"
/>
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-muted-foreground">
{t("codexConfig.modelNameHint", {
defaultValue: "指定使用的模型,将自动更新到 config.toml 中",
})}
@@ -47,7 +47,7 @@ export const GeminiEnvSection: React.FC<GeminiEnvSectionProps> = ({
<div className="space-y-2">
<label
htmlFor="geminiEnv"
className="block text-sm font-medium text-gray-900 dark:text-gray-100"
className="block text-sm font-medium text-foreground"
>
{t("geminiConfig.envFile", { defaultValue: "环境变量 (.env)" })}
</label>
@@ -69,7 +69,7 @@ GEMINI_MODEL=gemini-3-pro-preview`}
)}
{!error && (
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-muted-foreground">
{t("geminiConfig.envFileHint", {
defaultValue: "使用 .env 格式配置 Gemini 环境变量",
})}
@@ -124,14 +124,14 @@ export const GeminiConfigSection: React.FC<GeminiConfigSectionProps> = ({
<div className="flex items-center justify-between">
<label
htmlFor="geminiConfig"
className="block text-sm font-medium text-gray-900 dark:text-gray-100"
className="block text-sm font-medium text-foreground"
>
{t("geminiConfig.configJson", {
defaultValue: "配置文件 (config.json)",
})}
</label>
<label className="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 cursor-pointer">
<label className="inline-flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">
<input
type="checkbox"
checked={useCommonConfig}
@@ -180,7 +180,7 @@ export const GeminiConfigSection: React.FC<GeminiConfigSectionProps> = ({
)}
{!configError && (
<p className="text-xs text-gray-500 dark:text-gray-400">
<p className="text-xs text-muted-foreground">
{t("geminiConfig.configJsonHint", {
defaultValue: "使用 JSON 格式配置 Gemini 扩展参数(可选)",
})}
@@ -20,6 +20,7 @@ import {
geminiProviderPresets,
type GeminiProviderPreset,
} from "@/config/geminiProviderPresets";
import type { UniversalProviderPreset } from "@/config/universalProviderPresets";
import { applyTemplateValues } from "@/utils/providerConfigUtils";
import { mergeProviderMeta } from "@/utils/providerMetaUtils";
import { getCodexCustomTemplate } from "@/config/codexTemplates";
@@ -72,6 +73,8 @@ interface ProviderFormProps {
submitLabel: string;
onSubmit: (values: ProviderFormValues) => void;
onCancel: () => void;
onUniversalPresetSelect?: (preset: UniversalProviderPreset) => void;
onManageUniversalProviders?: () => void;
initialData?: {
name?: string;
websiteUrl?: string;
@@ -91,6 +94,8 @@ export function ProviderForm({
submitLabel,
onSubmit,
onCancel,
onUniversalPresetSelect,
onManageUniversalProviders,
initialData,
showButtons = true,
}: ProviderFormProps) {
@@ -162,6 +167,8 @@ export function ProviderForm({
mode: "onSubmit",
});
const settingsConfigValue = form.watch("settingsConfig");
// 使用 API Key hook
const {
apiKey,
@@ -187,9 +194,10 @@ export function ProviderForm({
},
});
// 使用 Model hook(新:主模型 + Haiku/Sonnet/Opus 默认模型)
// 使用 Model hook(新:主模型 + 推理模型 + Haiku/Sonnet/Opus 默认模型)
const {
claudeModel,
reasoningModel,
defaultHaikuModel,
defaultSonnetModel,
defaultOpusModel,
@@ -199,6 +207,53 @@ export function ProviderForm({
onConfigChange: (config) => form.setValue("settingsConfig", config),
});
const isOpenRouterProvider = useMemo(() => {
if (appId !== "claude") return false;
const normalized = baseUrl.trim().toLowerCase();
if (normalized.includes("openrouter.ai")) {
return true;
}
try {
const config = JSON.parse(settingsConfigValue || "{}");
const envUrl = config?.env?.ANTHROPIC_BASE_URL;
return typeof envUrl === "string" && envUrl.includes("openrouter.ai");
} catch {
return false;
}
}, [appId, baseUrl, settingsConfigValue]);
const openRouterCompatEnabled = useMemo(() => {
if (!isOpenRouterProvider) return false;
try {
const config = JSON.parse(settingsConfigValue || "{}");
const raw = config?.openrouter_compat_mode;
if (typeof raw === "boolean") return raw;
if (typeof raw === "number") return raw !== 0;
if (typeof raw === "string") {
const normalized = raw.trim().toLowerCase();
return normalized === "true" || normalized === "1";
}
} catch {
// ignore
}
return true;
}, [isOpenRouterProvider, settingsConfigValue]);
const handleOpenRouterCompatChange = useCallback(
(enabled: boolean) => {
try {
const currentConfig = JSON.parse(
form.getValues("settingsConfig") || "{}",
);
currentConfig.openrouter_compat_mode = enabled;
form.setValue("settingsConfig", JSON.stringify(currentConfig, null, 2));
} catch {
// ignore
}
},
[form],
);
// 使用 Codex 配置 hook (仅 Codex 模式)
const {
codexAuth,
@@ -753,6 +808,8 @@ export function ProviderForm({
categoryKeys={categoryKeys}
presetCategoryLabels={presetCategoryLabels}
onPresetChange={handlePresetChange}
onUniversalPresetSelect={onUniversalPresetSelect}
onManageUniversalProviders={onManageUniversalProviders}
category={category}
/>
)}
@@ -789,11 +846,15 @@ export function ProviderForm({
}
shouldShowModelSelector={category !== "official"}
claudeModel={claudeModel}
reasoningModel={reasoningModel}
defaultHaikuModel={defaultHaikuModel}
defaultSonnetModel={defaultSonnetModel}
defaultOpusModel={defaultOpusModel}
onModelChange={handleModelChange}
speedTestEndpoints={speedTestEndpoints}
showOpenRouterCompatToggle={isOpenRouterProvider}
openRouterCompatEnabled={openRouterCompatEnabled}
onOpenRouterCompatChange={handleOpenRouterCompatChange}
/>
)}
@@ -1,11 +1,16 @@
import { useTranslation } from "react-i18next";
import { FormLabel } from "@/components/ui/form";
import { ClaudeIcon, CodexIcon, GeminiIcon } from "@/components/BrandIcons";
import { Zap, Star } from "lucide-react";
import { Zap, Star, Layers, Settings2 } from "lucide-react";
import type { ProviderPreset } from "@/config/claudeProviderPresets";
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
import type { GeminiProviderPreset } from "@/config/geminiProviderPresets";
import type { ProviderCategory } from "@/types";
import {
universalProviderPresets,
type UniversalProviderPreset,
} from "@/config/universalProviderPresets";
import { ProviderIcon } from "@/components/ProviderIcon";
type PresetEntry = {
id: string;
@@ -18,6 +23,8 @@ interface ProviderPresetSelectorProps {
categoryKeys: string[];
presetCategoryLabels: Record<string, string>;
onPresetChange: (value: string) => void;
onUniversalPresetSelect?: (preset: UniversalProviderPreset) => void;
onManageUniversalProviders?: () => void;
category?: ProviderCategory; // 当前选中的分类
}
@@ -27,6 +34,8 @@ export function ProviderPresetSelector({
categoryKeys,
presetCategoryLabels,
onPresetChange,
onUniversalPresetSelect,
onManageUniversalProviders,
category,
}: ProviderPresetSelectorProps) {
const { t } = useTranslation();
@@ -164,6 +173,49 @@ export function ProviderPresetSelector({
});
})}
</div>
{/* 统一供应商预设(新的一行) */}
{onUniversalPresetSelect && universalProviderPresets.length > 0 && (
<>
<div className="flex flex-wrap items-center gap-2">
{universalProviderPresets.map((preset) => (
<button
key={`universal-${preset.providerType}`}
type="button"
onClick={() => onUniversalPresetSelect(preset)}
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors bg-accent text-muted-foreground hover:bg-accent/80 relative"
title={t("universalProvider.hint", {
defaultValue:
"跨应用统一配置,自动同步到 Claude/Codex/Gemini",
})}
>
<ProviderIcon icon={preset.icon} name={preset.name} size={14} />
{preset.name}
<span className="absolute -top-1 -right-1 flex items-center gap-0.5 rounded-full bg-gradient-to-r from-indigo-500 to-purple-500 px-1.5 py-0.5 text-[10px] font-bold text-white shadow-md">
<Layers className="h-2.5 w-2.5" />
</span>
</button>
))}
{/* 管理统一供应商按钮 */}
{onManageUniversalProviders && (
<button
type="button"
onClick={onManageUniversalProviders}
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors bg-accent text-muted-foreground hover:bg-accent/80"
title={t("universalProvider.manage", {
defaultValue: "管理统一供应商",
})}
>
<Settings2 className="h-4 w-4" />
{t("universalProvider.manage", {
defaultValue: "管理",
})}
</button>
)}
</div>
</>
)}
<p className="text-xs text-muted-foreground">{getCategoryHint()}</p>
</div>
);
@@ -7,13 +7,14 @@ interface UseModelStateProps {
/**
* 管理模型选择状态
* 支持 ANTHROPIC_MODEL ANTHROPIC_SMALL_FAST_MODEL
* 支持 ANTHROPIC_MODEL, ANTHROPIC_REASONING_MODEL 和各类型默认模型
*/
export function useModelState({
settingsConfig,
onConfigChange,
}: UseModelStateProps) {
const [claudeModel, setClaudeModel] = useState("");
const [reasoningModel, setReasoningModel] = useState("");
const [defaultHaikuModel, setDefaultHaikuModel] = useState("");
const [defaultSonnetModel, setDefaultSonnetModel] = useState("");
const [defaultOpusModel, setDefaultOpusModel] = useState("");
@@ -29,6 +30,10 @@ export function useModelState({
const env = cfg?.env || {};
const model =
typeof env.ANTHROPIC_MODEL === "string" ? env.ANTHROPIC_MODEL : "";
const reasoning =
typeof env.ANTHROPIC_REASONING_MODEL === "string"
? env.ANTHROPIC_REASONING_MODEL
: "";
const small =
typeof env.ANTHROPIC_SMALL_FAST_MODEL === "string"
? env.ANTHROPIC_SMALL_FAST_MODEL
@@ -47,6 +52,7 @@ export function useModelState({
: model || small;
setClaudeModel(model || "");
setReasoningModel(reasoning || "");
setDefaultHaikuModel(haiku || "");
setDefaultSonnetModel(sonnet || "");
setDefaultOpusModel(opus || "");
@@ -59,12 +65,14 @@ export function useModelState({
(
field:
| "ANTHROPIC_MODEL"
| "ANTHROPIC_REASONING_MODEL"
| "ANTHROPIC_DEFAULT_HAIKU_MODEL"
| "ANTHROPIC_DEFAULT_SONNET_MODEL"
| "ANTHROPIC_DEFAULT_OPUS_MODEL",
value: string,
) => {
if (field === "ANTHROPIC_MODEL") setClaudeModel(value);
if (field === "ANTHROPIC_REASONING_MODEL") setReasoningModel(value);
if (field === "ANTHROPIC_DEFAULT_HAIKU_MODEL")
setDefaultHaikuModel(value);
if (field === "ANTHROPIC_DEFAULT_SONNET_MODEL")
@@ -98,6 +106,8 @@ export function useModelState({
return {
claudeModel,
setClaudeModel,
reasoningModel,
setReasoningModel,
defaultHaikuModel,
setDefaultHaikuModel,
defaultSonnetModel,
@@ -40,7 +40,7 @@ export function EndpointField({
<button
type="button"
onClick={onManageClick}
className="flex items-center gap-1 text-xs text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
<Zap className="h-3.5 w-3.5" />
{manageButtonLabel || defaultManageLabel}