mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
style: 运行 prettier 格式化代码
修复了 26 个文件的代码格式问题,确保符合项目的 prettier 配置。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.5
parent
83a839d72d
commit
eb6e06c589
@@ -123,7 +123,9 @@ export function Dashboard() {
|
||||
<Server className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">默认 Provider</span>
|
||||
</div>
|
||||
<div className="mt-2 font-medium">{getProviderName(defaultProvider)}</div>
|
||||
<div className="mt-2 font-medium">
|
||||
{getProviderName(defaultProvider)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -169,7 +171,9 @@ export function Dashboard() {
|
||||
className="flex items-center justify-between rounded-lg border bg-background p-3"
|
||||
>
|
||||
<div>
|
||||
<div className="font-medium">{getProviderName(cred.provider)}</div>
|
||||
<div className="font-medium">
|
||||
{getProviderName(cred.provider)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{cred.has_access_token ? "Token 已加载" : "未配置"}
|
||||
</div>
|
||||
@@ -195,7 +199,9 @@ export function Dashboard() {
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">API 地址:</span>
|
||||
<code className="ml-2 rounded bg-muted px-2 py-1">{serverUrl}</code>
|
||||
<code className="ml-2 rounded bg-muted px-2 py-1">
|
||||
{serverUrl}
|
||||
</code>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">API Key:</span>
|
||||
@@ -245,7 +251,9 @@ function QuickLinkCard({
|
||||
<p className="text-xs text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex items-center gap-1 text-xs ${statusColors[status]}`}>
|
||||
<div
|
||||
className={`flex items-center gap-1 text-xs ${statusColors[status]}`}
|
||||
>
|
||||
<StatusIcon className="h-3 w-3" />
|
||||
{statusText}
|
||||
</div>
|
||||
|
||||
@@ -197,7 +197,10 @@ export function ApiServerPage() {
|
||||
try {
|
||||
await setDefaultProvider(providerId);
|
||||
setDefaultProviderState(providerId);
|
||||
setMessage({ type: "success", text: `默认 Provider 已切换为: ${providerId}` });
|
||||
setMessage({
|
||||
type: "success",
|
||||
text: `默认 Provider 已切换为: ${providerId}`,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
const errMsg = e instanceof Error ? e.message : String(e);
|
||||
setMessage({ type: "error", text: `切换失败: ${errMsg}` });
|
||||
@@ -288,7 +291,12 @@ export function ApiServerPage() {
|
||||
body: JSON.stringify({
|
||||
model: "claude-sonnet-4-5",
|
||||
max_tokens: 100,
|
||||
messages: [{ role: "user", content: "What is 1+1? Answer with just the number." }],
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "What is 1+1? Answer with just the number.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
@@ -501,7 +509,9 @@ export function ApiServerPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-muted-foreground mb-1">API Key</label>
|
||||
<label className="block text-muted-foreground mb-1">
|
||||
API Key
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editApiKey}
|
||||
@@ -647,7 +657,8 @@ export function ApiServerPage() {
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-medium text-muted-foreground">
|
||||
响应{" "}
|
||||
{result.httpStatus && `(HTTP ${result.httpStatus})`}
|
||||
{result.httpStatus &&
|
||||
`(HTTP ${result.httpStatus})`}
|
||||
</p>
|
||||
<pre
|
||||
className={`rounded p-2 text-xs overflow-x-auto max-h-40 ${
|
||||
|
||||
@@ -23,7 +23,7 @@ export function AppTabs({ activeApp, onAppChange }: AppTabsProps) {
|
||||
"px-4 py-2 rounded-t-lg text-sm font-medium transition-colors",
|
||||
activeApp === app.id
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "hover:bg-muted text-muted-foreground"
|
||||
: "hover:bg-muted text-muted-foreground",
|
||||
)}
|
||||
title={app.description}
|
||||
>
|
||||
|
||||
@@ -51,7 +51,9 @@ export function LiveConfigModal({ appType, onClose }: LiveConfigModalProps) {
|
||||
className="p-1.5 rounded hover:bg-muted"
|
||||
title="刷新"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
|
||||
<RefreshCw
|
||||
className={`h-4 w-4 ${loading ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
<button onClick={onClose} className="p-1.5 rounded hover:bg-muted">
|
||||
<X className="h-5 w-5" />
|
||||
@@ -73,15 +75,16 @@ export function LiveConfigModal({ appType, onClose }: LiveConfigModalProps) {
|
||||
{JSON.stringify(config, null, 2)}
|
||||
</pre>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-center py-8">
|
||||
无配置数据
|
||||
</p>
|
||||
<p className="text-muted-foreground text-center py-8">无配置数据</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
配置文件路径: <code className="px-1 py-0.5 rounded bg-muted">{configPaths[appType]}</code>
|
||||
配置文件路径:{" "}
|
||||
<code className="px-1 py-0.5 rounded bg-muted">
|
||||
{configPaths[appType]}
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ export function ProviderCard({
|
||||
"relative rounded-lg border p-4 transition-all",
|
||||
isCurrent
|
||||
? "border-primary bg-primary/5 ring-1 ring-primary"
|
||||
: "hover:border-muted-foreground/50"
|
||||
: "hover:border-muted-foreground/50",
|
||||
)}
|
||||
>
|
||||
{isCurrent && (
|
||||
@@ -70,7 +70,7 @@ export function ProviderCard({
|
||||
"p-1.5 rounded text-destructive",
|
||||
isCurrent
|
||||
? "opacity-30 cursor-not-allowed"
|
||||
: "hover:bg-destructive/10"
|
||||
: "hover:bg-destructive/10",
|
||||
)}
|
||||
title={isCurrent ? "无法删除当前使用中的配置" : "删除"}
|
||||
>
|
||||
|
||||
@@ -7,12 +7,20 @@ import { cn } from "@/lib/utils";
|
||||
interface ProviderFormProps {
|
||||
appType: AppType;
|
||||
provider: Provider | null;
|
||||
onSave: (data: Omit<Provider, "id" | "is_current" | "created_at">) => Promise<void>;
|
||||
onSave: (
|
||||
data: Omit<Provider, "id" | "is_current" | "created_at">,
|
||||
) => Promise<void>;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
// 供应商分类
|
||||
type ProviderCategory = "official" | "cn_official" | "aggregator" | "third_party" | "proxy" | "custom";
|
||||
type ProviderCategory =
|
||||
| "official"
|
||||
| "cn_official"
|
||||
| "aggregator"
|
||||
| "third_party"
|
||||
| "proxy"
|
||||
| "custom";
|
||||
|
||||
// 预设供应商接口
|
||||
interface ProviderPreset {
|
||||
@@ -70,7 +78,8 @@ const presets: Record<AppType, ProviderPreset[]> = {
|
||||
iconColor: "#6B4FBB",
|
||||
websiteUrl: "https://bailian.console.aliyun.com",
|
||||
apiKeyUrl: "https://bailian.console.aliyun.com/?apiKey=1#/api-key",
|
||||
defaultBaseUrl: "https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
|
||||
defaultBaseUrl:
|
||||
"https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
|
||||
},
|
||||
{
|
||||
id: "kimi",
|
||||
@@ -96,7 +105,8 @@ const presets: Record<AppType, ProviderPreset[]> = {
|
||||
category: "cn_official",
|
||||
iconColor: "#F97316",
|
||||
websiteUrl: "https://platform.minimaxi.com",
|
||||
apiKeyUrl: "https://platform.minimaxi.com/user-center/basic-information/interface-key",
|
||||
apiKeyUrl:
|
||||
"https://platform.minimaxi.com/user-center/basic-information/interface-key",
|
||||
defaultBaseUrl: "https://api.minimaxi.com/anthropic",
|
||||
},
|
||||
{
|
||||
@@ -105,7 +115,8 @@ const presets: Record<AppType, ProviderPreset[]> = {
|
||||
category: "cn_official",
|
||||
iconColor: "#5DADEC",
|
||||
websiteUrl: "https://www.volcengine.com/product/doubao",
|
||||
apiKeyUrl: "https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey",
|
||||
apiKeyUrl:
|
||||
"https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey",
|
||||
defaultBaseUrl: "https://ark.cn-beijing.volces.com/api/coding",
|
||||
},
|
||||
// 聚合服务
|
||||
@@ -221,10 +232,14 @@ const categoryLabels: Record<ProviderCategory, string> = {
|
||||
};
|
||||
|
||||
// 默认 Codex auth.json 模板
|
||||
const defaultCodexAuth = JSON.stringify({
|
||||
api_key: "",
|
||||
api_base_url: ""
|
||||
}, null, 2);
|
||||
const defaultCodexAuth = JSON.stringify(
|
||||
{
|
||||
api_key: "",
|
||||
api_base_url: "",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
|
||||
// 默认 Codex config.toml 模板
|
||||
const defaultCodexConfig = `# Codex 配置文件
|
||||
@@ -237,17 +252,26 @@ GOOGLE_GEMINI_BASE_URL=
|
||||
GEMINI_MODEL=gemini-2.0-flash`;
|
||||
|
||||
// 默认 Gemini settings.json 模板
|
||||
const defaultGeminiSettings = JSON.stringify({
|
||||
mcpServers: {}
|
||||
}, null, 2);
|
||||
const defaultGeminiSettings = JSON.stringify(
|
||||
{
|
||||
mcpServers: {},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
|
||||
export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFormProps) {
|
||||
export function ProviderForm({
|
||||
appType,
|
||||
provider,
|
||||
onSave,
|
||||
onCancel,
|
||||
}: ProviderFormProps) {
|
||||
const isEditMode = Boolean(provider);
|
||||
const appPresets = useMemo(() => presets[appType] || [], [appType]);
|
||||
|
||||
// 基础字段
|
||||
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(
|
||||
isEditMode ? null : "custom"
|
||||
isEditMode ? null : "custom",
|
||||
);
|
||||
const [name, setName] = useState(provider?.name || "");
|
||||
const [notes, setNotes] = useState(provider?.notes || "");
|
||||
@@ -255,12 +279,31 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
|
||||
// 从 provider.settings_config 中提取 Claude 配置
|
||||
const extractClaudeConfig = () => {
|
||||
if (!provider?.settings_config || typeof provider.settings_config !== 'object') {
|
||||
return { apiKey: "", baseUrl: "", primaryModel: "claude-sonnet-4-20250514", haikuModel: "", sonnetModel: "", opusModel: "" };
|
||||
if (
|
||||
!provider?.settings_config ||
|
||||
typeof provider.settings_config !== "object"
|
||||
) {
|
||||
return {
|
||||
apiKey: "",
|
||||
baseUrl: "",
|
||||
primaryModel: "claude-sonnet-4-20250514",
|
||||
haikuModel: "",
|
||||
sonnetModel: "",
|
||||
opusModel: "",
|
||||
};
|
||||
}
|
||||
const env = (provider.settings_config as Record<string, unknown>).env as Record<string, string> | undefined;
|
||||
const env = (provider.settings_config as Record<string, unknown>).env as
|
||||
| Record<string, string>
|
||||
| undefined;
|
||||
if (!env) {
|
||||
return { apiKey: "", baseUrl: "", primaryModel: "claude-sonnet-4-20250514", haikuModel: "", sonnetModel: "", opusModel: "" };
|
||||
return {
|
||||
apiKey: "",
|
||||
baseUrl: "",
|
||||
primaryModel: "claude-sonnet-4-20250514",
|
||||
haikuModel: "",
|
||||
sonnetModel: "",
|
||||
opusModel: "",
|
||||
};
|
||||
}
|
||||
return {
|
||||
apiKey: env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN || "",
|
||||
@@ -284,7 +327,10 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
|
||||
// Claude 配置 JSON(可编辑)
|
||||
const [claudeConfigJson, setClaudeConfigJson] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
return JSON.stringify(provider.settings_config, null, 2);
|
||||
}
|
||||
return JSON.stringify({ env: {} }, null, 2);
|
||||
@@ -350,36 +396,50 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
|
||||
// Codex 专属字段 - 使用代码编辑器
|
||||
const [codexAuth, setCodexAuth] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
const auth = (provider.settings_config as Record<string, unknown>).auth;
|
||||
if (auth) return JSON.stringify(auth, null, 2);
|
||||
}
|
||||
return defaultCodexAuth;
|
||||
});
|
||||
const [codexConfig, setCodexConfig] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
const config = (provider.settings_config as Record<string, unknown>).config;
|
||||
if (typeof config === 'string') return config;
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
const config = (provider.settings_config as Record<string, unknown>)
|
||||
.config;
|
||||
if (typeof config === "string") return config;
|
||||
}
|
||||
return defaultCodexConfig;
|
||||
});
|
||||
|
||||
// Gemini 专属字段 - 使用代码编辑器
|
||||
const [geminiEnv, setGeminiEnv] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
const env = (provider.settings_config as Record<string, unknown>).env;
|
||||
if (env && typeof env === 'object') {
|
||||
if (env && typeof env === "object") {
|
||||
return Object.entries(env as Record<string, string>)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join('\n');
|
||||
.join("\n");
|
||||
}
|
||||
}
|
||||
return defaultGeminiEnv;
|
||||
});
|
||||
const [geminiSettings, setGeminiSettings] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
const config = (provider.settings_config as Record<string, unknown>).config;
|
||||
if (config && typeof config === 'object') {
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
const config = (provider.settings_config as Record<string, unknown>)
|
||||
.config;
|
||||
if (config && typeof config === "object") {
|
||||
return JSON.stringify(config, null, 2);
|
||||
}
|
||||
}
|
||||
@@ -431,12 +491,20 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
// 重置 JSON 手动编辑标记,让表单同步到 JSON
|
||||
setJsonManuallyEdited(false);
|
||||
} else if (appType === "codex") {
|
||||
setCodexAuth(JSON.stringify({
|
||||
api_key: proxyApiKey,
|
||||
api_base_url: `${proxyBaseUrl}/v1`,
|
||||
}, null, 2));
|
||||
setCodexAuth(
|
||||
JSON.stringify(
|
||||
{
|
||||
api_key: proxyApiKey,
|
||||
api_base_url: `${proxyBaseUrl}/v1`,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
} else if (appType === "gemini") {
|
||||
setGeminiEnv(`GEMINI_API_KEY=${proxyApiKey}\nGOOGLE_GEMINI_BASE_URL=${proxyBaseUrl}\nGEMINI_MODEL=gemini-2.0-flash`);
|
||||
setGeminiEnv(
|
||||
`GEMINI_API_KEY=${proxyApiKey}\nGOOGLE_GEMINI_BASE_URL=${proxyBaseUrl}\nGEMINI_MODEL=gemini-2.0-flash`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
} catch (e) {
|
||||
@@ -474,7 +542,7 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
if (preset.defaultGeminiEnv) {
|
||||
const envLines = Object.entries(preset.defaultGeminiEnv)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join('\n');
|
||||
.join("\n");
|
||||
setGeminiEnv(envLines);
|
||||
} else {
|
||||
setGeminiEnv(defaultGeminiEnv);
|
||||
@@ -504,10 +572,10 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
|
||||
if (appType === "gemini") {
|
||||
const env: Record<string, string> = {};
|
||||
for (const line of geminiEnv.split('\n')) {
|
||||
for (const line of geminiEnv.split("\n")) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const idx = trimmed.indexOf('=');
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
const idx = trimmed.indexOf("=");
|
||||
if (idx > 0) {
|
||||
const key = trimmed.slice(0, idx).trim();
|
||||
const value = trimmed.slice(idx + 1).trim();
|
||||
@@ -535,7 +603,12 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
}
|
||||
|
||||
// 非官方供应商必填校验(新增模式必填,编辑模式可留空保持原值)
|
||||
if (!isEditMode && showApiFields && appType === "claude" && !apiKey.trim()) {
|
||||
if (
|
||||
!isEditMode &&
|
||||
showApiFields &&
|
||||
appType === "claude" &&
|
||||
!apiKey.trim()
|
||||
) {
|
||||
setError("请填写 API Key");
|
||||
return;
|
||||
}
|
||||
@@ -590,7 +663,7 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
"flex items-center gap-2 px-3 py-2 rounded-lg border text-sm transition-all",
|
||||
selectedPresetId === preset.id
|
||||
? "border-primary bg-primary/10 text-primary"
|
||||
: "border-border hover:border-muted-foreground/50"
|
||||
: "border-border hover:border-muted-foreground/50",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
@@ -643,9 +716,12 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
<label className="text-sm font-medium">
|
||||
API Key <span className="text-destructive">*</span>
|
||||
</label>
|
||||
{(selectedPreset?.apiKeyUrl || selectedPreset?.websiteUrl) && (
|
||||
{(selectedPreset?.apiKeyUrl ||
|
||||
selectedPreset?.websiteUrl) && (
|
||||
<a
|
||||
href={selectedPreset?.apiKeyUrl || selectedPreset?.websiteUrl}
|
||||
href={
|
||||
selectedPreset?.apiKeyUrl || selectedPreset?.websiteUrl
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-primary hover:underline flex items-center gap-1"
|
||||
@@ -679,7 +755,9 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">API 端点</label>
|
||||
<label className="block text-sm font-medium mb-1.5">
|
||||
API 端点
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={baseUrl}
|
||||
@@ -687,14 +765,18 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
className="w-full px-3 py-2 rounded-lg border bg-background focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none font-mono"
|
||||
placeholder="https://api.example.com/v1"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">留空使用默认端点</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
留空使用默认端点
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<label className="block text-sm font-medium">模型配置</label>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs text-muted-foreground mb-1">主模型</label>
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
主模型
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={primaryModel}
|
||||
@@ -704,7 +786,9 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-muted-foreground mb-1">Haiku 默认模型</label>
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
Haiku 默认模型
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={haikuModel}
|
||||
@@ -713,7 +797,9 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-muted-foreground mb-1">Sonnet 默认模型</label>
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
Sonnet 默认模型
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={sonnetModel}
|
||||
@@ -722,7 +808,9 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-muted-foreground mb-1">Opus 默认模型</label>
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
Opus 默认模型
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={opusModel}
|
||||
@@ -754,7 +842,8 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
onChange={(e) => handleJsonChange(e.target.value)}
|
||||
className={cn(
|
||||
"w-full px-3 py-2 rounded-lg border bg-muted/50 font-mono text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none resize-none",
|
||||
jsonError && "border-destructive focus:ring-destructive/20 focus:border-destructive"
|
||||
jsonError &&
|
||||
"border-destructive focus:ring-destructive/20 focus:border-destructive",
|
||||
)}
|
||||
rows={10}
|
||||
placeholder='{"env": {}}'
|
||||
|
||||
@@ -24,7 +24,9 @@ export function ProviderList({ appType }: ProviderListProps) {
|
||||
} = useSwitch(appType);
|
||||
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editingProvider, setEditingProvider] = useState<typeof providers[0] | null>(null);
|
||||
const [editingProvider, setEditingProvider] = useState<
|
||||
(typeof providers)[0] | null
|
||||
>(null);
|
||||
const [showLiveConfig, setShowLiveConfig] = useState(false);
|
||||
|
||||
const handleAdd = () => {
|
||||
@@ -32,7 +34,7 @@ export function ProviderList({ appType }: ProviderListProps) {
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
const handleEdit = (provider: typeof providers[0]) => {
|
||||
const handleEdit = (provider: (typeof providers)[0]) => {
|
||||
setEditingProvider(provider);
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
@@ -225,7 +225,9 @@ function CredentialPanel({ provider }: { provider: OAuthProvider }) {
|
||||
disabled={isLoading || !credentials?.has_refresh_token}
|
||||
className="flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${refreshing ? "animate-spin" : ""}`} />
|
||||
<RefreshCw
|
||||
className={`h-4 w-4 ${refreshing ? "animate-spin" : ""}`}
|
||||
/>
|
||||
刷新 Token
|
||||
</button>
|
||||
<button
|
||||
@@ -340,13 +342,21 @@ function CredentialOverview() {
|
||||
<div className="text-xs space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Access Token</span>
|
||||
<span className={cred.has_access_token ? "text-green-600" : "text-red-500"}>
|
||||
<span
|
||||
className={
|
||||
cred.has_access_token ? "text-green-600" : "text-red-500"
|
||||
}
|
||||
>
|
||||
{cred.has_access_token ? "已加载" : "未加载"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Refresh Token</span>
|
||||
<span className={cred.has_refresh_token ? "text-green-600" : "text-red-500"}>
|
||||
<span
|
||||
className={
|
||||
cred.has_refresh_token ? "text-green-600" : "text-red-500"
|
||||
}
|
||||
>
|
||||
{cred.has_refresh_token ? "已加载" : "未加载"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@ const defaultServerConfig = JSON.stringify(
|
||||
env: {},
|
||||
},
|
||||
null,
|
||||
2
|
||||
2,
|
||||
);
|
||||
|
||||
export function McpPage() {
|
||||
@@ -243,7 +243,9 @@ export function McpPage() {
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border hover:bg-muted text-sm"
|
||||
title="从外部应用导入 MCP 配置"
|
||||
>
|
||||
<Download className={cn("h-4 w-4", importing && "animate-pulse")} />
|
||||
<Download
|
||||
className={cn("h-4 w-4", importing && "animate-pulse")}
|
||||
/>
|
||||
{importing ? "导入中..." : "导入"}
|
||||
</button>
|
||||
{showImportMenu && (
|
||||
@@ -305,7 +307,9 @@ export function McpPage() {
|
||||
className="p-1.5 rounded hover:bg-muted"
|
||||
title="刷新"
|
||||
>
|
||||
<RefreshCw className={cn("h-4 w-4", loading && "animate-spin")} />
|
||||
<RefreshCw
|
||||
className={cn("h-4 w-4", loading && "animate-spin")}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCreateNew}
|
||||
@@ -341,7 +345,7 @@ export function McpPage() {
|
||||
"p-2.5 rounded-lg cursor-pointer transition-colors",
|
||||
selectedServer?.id === server.id
|
||||
? "bg-primary/10 border border-primary"
|
||||
: "hover:bg-muted border border-transparent"
|
||||
: "hover:bg-muted border border-transparent",
|
||||
)}
|
||||
>
|
||||
<span className="font-medium text-sm truncate block">
|
||||
@@ -399,7 +403,9 @@ export function McpPage() {
|
||||
{/* 预设选择器(仅新建时显示) */}
|
||||
{isCreating && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-muted-foreground">预设:</span>
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
预设:
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{mcpPresets.map((preset) => (
|
||||
<button
|
||||
@@ -410,7 +416,7 @@ export function McpPage() {
|
||||
"px-2.5 py-1 rounded text-xs transition-colors",
|
||||
selectedPreset === preset.id
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted hover:bg-muted/80 text-muted-foreground"
|
||||
: "bg-muted hover:bg-muted/80 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{preset.name}
|
||||
@@ -435,7 +441,9 @@ export function McpPage() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs font-medium mb-1 text-muted-foreground">描述</label>
|
||||
<label className="block text-xs font-medium mb-1 text-muted-foreground">
|
||||
描述
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editDescription}
|
||||
@@ -448,7 +456,9 @@ export function McpPage() {
|
||||
|
||||
{/* 同步到哪些应用 - 横排 */}
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs font-medium text-muted-foreground">同步到:</span>
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
同步到:
|
||||
</span>
|
||||
<label className="flex items-center gap-1.5 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -489,7 +499,7 @@ export function McpPage() {
|
||||
onChange={(e) => handleConfigChange(e.target.value)}
|
||||
className={cn(
|
||||
"flex-1 w-full px-3 py-2 rounded-lg border bg-muted/50 focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none font-mono text-sm resize-none",
|
||||
configError && "border-destructive"
|
||||
configError && "border-destructive",
|
||||
)}
|
||||
placeholder='{"command": "npx", "args": [...], "env": {...}}'
|
||||
/>
|
||||
|
||||
@@ -32,13 +32,13 @@ function ToggleSwitch({
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary/20",
|
||||
enabled ? "bg-primary" : "bg-muted-foreground/30",
|
||||
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
|
||||
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform",
|
||||
enabled ? "translate-x-5" : "translate-x-1"
|
||||
enabled ? "translate-x-5" : "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
@@ -57,7 +57,7 @@ export function PromptCard({
|
||||
"relative rounded-lg border p-4 transition-all",
|
||||
prompt.enabled
|
||||
? "border-primary bg-primary/5 ring-1 ring-primary"
|
||||
: "hover:border-muted-foreground/50"
|
||||
: "hover:border-muted-foreground/50",
|
||||
)}
|
||||
>
|
||||
{prompt.enabled && (
|
||||
@@ -68,10 +68,7 @@ export function PromptCard({
|
||||
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<ToggleSwitch
|
||||
enabled={prompt.enabled}
|
||||
onChange={onToggle}
|
||||
/>
|
||||
<ToggleSwitch enabled={prompt.enabled} onChange={onToggle} />
|
||||
<div className="h-8 w-8 rounded-lg bg-muted flex items-center justify-center">
|
||||
<FileText className="h-4 w-4" />
|
||||
</div>
|
||||
@@ -100,7 +97,7 @@ export function PromptCard({
|
||||
"p-1.5 rounded",
|
||||
prompt.enabled
|
||||
? "opacity-30 cursor-not-allowed"
|
||||
: "hover:bg-destructive/10 text-destructive"
|
||||
: "hover:bg-destructive/10 text-destructive",
|
||||
)}
|
||||
title={prompt.enabled ? "无法删除已启用的提示词" : "删除"}
|
||||
>
|
||||
@@ -118,7 +115,9 @@ export function PromptCard({
|
||||
|
||||
<div className="mt-3">
|
||||
{prompt.enabled ? (
|
||||
<span className="text-sm text-primary font-medium">已启用 (同步到配置文件)</span>
|
||||
<span className="text-sm text-primary font-medium">
|
||||
已启用 (同步到配置文件)
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">未启用</span>
|
||||
)}
|
||||
|
||||
@@ -9,7 +9,12 @@ interface PromptFormProps {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function PromptForm({ appType, prompt, onSave, onCancel }: PromptFormProps) {
|
||||
export function PromptForm({
|
||||
appType,
|
||||
prompt,
|
||||
onSave,
|
||||
onCancel,
|
||||
}: PromptFormProps) {
|
||||
const [name, setName] = useState(prompt?.name || "");
|
||||
const [content, setContent] = useState(prompt?.content || "");
|
||||
const [description, setDescription] = useState(prompt?.description || "");
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { Plus, RefreshCw, Check, Trash2, Download, FileText } from "lucide-react";
|
||||
import {
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Check,
|
||||
Trash2,
|
||||
Download,
|
||||
FileText,
|
||||
} from "lucide-react";
|
||||
import { AppType, Prompt } from "@/lib/api/prompts";
|
||||
import { usePrompts } from "@/hooks/usePrompts";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -33,13 +40,13 @@ function ToggleSwitch({
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary/20",
|
||||
enabled ? "bg-primary" : "bg-muted-foreground/30",
|
||||
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
|
||||
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform",
|
||||
enabled ? "translate-x-5" : "translate-x-1"
|
||||
enabled ? "translate-x-5" : "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
@@ -148,7 +155,9 @@ export function PromptsPage() {
|
||||
await importFromFile();
|
||||
} catch (error) {
|
||||
console.error("Failed to import:", error);
|
||||
alert("导入失败:" + (error instanceof Error ? error.message : String(error)));
|
||||
alert(
|
||||
"导入失败:" + (error instanceof Error ? error.message : String(error)),
|
||||
);
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
@@ -180,7 +189,7 @@ export function PromptsPage() {
|
||||
"px-4 py-2 rounded-t-lg text-sm font-medium transition-colors",
|
||||
activeApp === app.id
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "hover:bg-muted text-muted-foreground"
|
||||
: "hover:bg-muted text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{app.label}
|
||||
@@ -190,7 +199,8 @@ export function PromptsPage() {
|
||||
|
||||
{/* Status bar */}
|
||||
<div className="mb-4 p-3 rounded-lg bg-muted/50 border text-sm text-muted-foreground">
|
||||
共 {promptList.length} 个提示词 · {enabledPrompt
|
||||
共 {promptList.length} 个提示词 ·{" "}
|
||||
{enabledPrompt
|
||||
? `当前启用: ${enabledPrompt[1].name}`
|
||||
: "暂无启用的提示词"}
|
||||
</div>
|
||||
@@ -208,14 +218,18 @@ export function PromptsPage() {
|
||||
className="p-1.5 rounded hover:bg-muted"
|
||||
title={`从 ${currentApp.filename} 导入`}
|
||||
>
|
||||
<Download className={cn("h-4 w-4", importing && "animate-pulse")} />
|
||||
<Download
|
||||
className={cn("h-4 w-4", importing && "animate-pulse")}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={reload}
|
||||
className="p-1.5 rounded hover:bg-muted"
|
||||
title="刷新"
|
||||
>
|
||||
<RefreshCw className={cn("h-4 w-4", loading && "animate-spin")} />
|
||||
<RefreshCw
|
||||
className={cn("h-4 w-4", loading && "animate-spin")}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCreateNew}
|
||||
@@ -252,7 +266,7 @@ export function PromptsPage() {
|
||||
"p-3 rounded-lg cursor-pointer transition-colors relative",
|
||||
selectedPromptId === id
|
||||
? "bg-primary/10 border border-primary"
|
||||
: "hover:bg-muted border border-transparent"
|
||||
: "hover:bg-muted border border-transparent",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -309,7 +323,9 @@ export function PromptsPage() {
|
||||
<span className="text-muted-foreground">启用</span>
|
||||
<ToggleSwitch
|
||||
enabled={selectedPrompt.enabled}
|
||||
onChange={(enabled) => handleToggle(selectedPromptId!, enabled)}
|
||||
onChange={(enabled) =>
|
||||
handleToggle(selectedPromptId!, enabled)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@@ -319,9 +335,13 @@ export function PromptsPage() {
|
||||
"p-1.5 rounded",
|
||||
selectedPrompt.enabled
|
||||
? "opacity-30 cursor-not-allowed"
|
||||
: "hover:bg-destructive/10 text-destructive"
|
||||
: "hover:bg-destructive/10 text-destructive",
|
||||
)}
|
||||
title={selectedPrompt.enabled ? "无法删除已启用的提示词" : "删除"}
|
||||
title={
|
||||
selectedPrompt.enabled
|
||||
? "无法删除已启用的提示词"
|
||||
: "删除"
|
||||
}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -331,7 +351,9 @@ export function PromptsPage() {
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">名称</label>
|
||||
<label className="block text-sm font-medium mb-1">
|
||||
名称
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
@@ -341,7 +363,9 @@ export function PromptsPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">描述</label>
|
||||
<label className="block text-sm font-medium mb-1">
|
||||
描述
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editDescription}
|
||||
@@ -355,7 +379,8 @@ export function PromptsPage() {
|
||||
|
||||
<div className="flex-1 p-4 flex flex-col min-h-0">
|
||||
<label className="block text-sm font-medium mb-2">
|
||||
内容 <span className="text-xs text-muted-foreground font-normal">
|
||||
内容{" "}
|
||||
<span className="text-xs text-muted-foreground font-normal">
|
||||
(启用后将同步到 {currentApp.filename})
|
||||
</span>
|
||||
</label>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { useState } from "react";
|
||||
import { ExternalLink, RefreshCw, CheckCircle2, AlertCircle } from "lucide-react";
|
||||
import {
|
||||
ExternalLink,
|
||||
RefreshCw,
|
||||
CheckCircle2,
|
||||
AlertCircle,
|
||||
} from "lucide-react";
|
||||
|
||||
interface VersionInfo {
|
||||
current: string;
|
||||
@@ -124,7 +129,9 @@ function ToolVersionItem({ name, version }: { name: string; version: string }) {
|
||||
) : (
|
||||
<AlertCircle className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span className="text-sm text-muted-foreground font-mono">{version}</span>
|
||||
<span className="text-sm text-muted-foreground font-mono">
|
||||
{version}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -61,7 +61,9 @@ export function DirectorySettings() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<label className="text-sm font-medium">{item.label}</label>
|
||||
<p className="text-xs text-muted-foreground">{item.description}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleReset(item.key)}
|
||||
|
||||
@@ -24,7 +24,9 @@ export function GeneralSettings() {
|
||||
// 应用主题
|
||||
const root = document.documentElement;
|
||||
if (newTheme === "system") {
|
||||
const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const systemDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
).matches;
|
||||
root.classList.toggle("dark", systemDark);
|
||||
} else {
|
||||
root.classList.toggle("dark", newTheme === "dark");
|
||||
@@ -54,7 +56,7 @@ export function GeneralSettings() {
|
||||
"flex items-center gap-2 px-4 py-2 rounded-lg border transition-colors",
|
||||
theme === option.id
|
||||
? "border-primary bg-primary/10 text-primary"
|
||||
: "border-border hover:border-muted-foreground/50"
|
||||
: "border-border hover:border-muted-foreground/50",
|
||||
)}
|
||||
>
|
||||
<option.icon className="h-4 w-4" />
|
||||
@@ -68,14 +70,18 @@ export function GeneralSettings() {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium">启动行为</h3>
|
||||
<p className="text-xs text-muted-foreground">配置应用启动和关闭行为</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
配置应用启动和关闭行为
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-center justify-between p-3 rounded-lg border cursor-pointer hover:bg-muted/50">
|
||||
<div>
|
||||
<span className="text-sm font-medium">开机自启动</span>
|
||||
<p className="text-xs text-muted-foreground">系统启动时自动运行 ProxyCast</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
系统启动时自动运行 ProxyCast
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -88,7 +94,9 @@ export function GeneralSettings() {
|
||||
<label className="flex items-center justify-between p-3 rounded-lg border cursor-pointer hover:bg-muted/50">
|
||||
<div>
|
||||
<span className="text-sm font-medium">关闭时最小化到托盘</span>
|
||||
<p className="text-xs text-muted-foreground">点击关闭按钮时最小化而不是退出</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
点击关闭按钮时最小化而不是退出
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -24,11 +24,16 @@ export function ProxySettings() {
|
||||
const [showApiKey, setShowApiKey] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
|
||||
const [message, setMessage] = useState<{
|
||||
type: "success" | "error";
|
||||
text: string;
|
||||
} | null>(null);
|
||||
|
||||
// API Compatibility Check
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [checkResult, setCheckResult] = useState<ApiCompatibilityResult | null>(null);
|
||||
const [checkResult, setCheckResult] = useState<ApiCompatibilityResult | null>(
|
||||
null,
|
||||
);
|
||||
const [lastCheckTime, setLastCheckTime] = useState<Date | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -134,13 +139,17 @@ export function ProxySettings() {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium">代理服务配置</h3>
|
||||
<p className="text-xs text-muted-foreground">配置本地代理服务器参数</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
配置本地代理服务器参数
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 p-4 rounded-lg border">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">监听地址</label>
|
||||
<label className="block text-sm font-medium mb-1.5">
|
||||
监听地址
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={config.server.host}
|
||||
@@ -194,7 +203,11 @@ export function ProxySettings() {
|
||||
className="p-1.5 rounded hover:bg-muted"
|
||||
title={showApiKey ? "隐藏" : "显示"}
|
||||
>
|
||||
{showApiKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
{showApiKey ? (
|
||||
<EyeOff className="h-4 w-4" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -239,7 +252,9 @@ export function ProxySettings() {
|
||||
|
||||
<div className="p-4 rounded-lg border space-y-4">
|
||||
<div className="rounded-lg bg-purple-50 dark:bg-purple-900/20 p-3 text-sm">
|
||||
<p className="font-medium text-purple-700 dark:text-purple-300">检测项目:</p>
|
||||
<p className="font-medium text-purple-700 dark:text-purple-300">
|
||||
检测项目:
|
||||
</p>
|
||||
<ul className="mt-1 list-inside list-disc text-purple-600 dark:text-purple-400 text-xs">
|
||||
<li>基础对话能力 (basic)</li>
|
||||
<li>Tool Calls 支持 (tool_call) - Claude Code 核心功能</li>
|
||||
@@ -252,7 +267,11 @@ export function ProxySettings() {
|
||||
disabled={checking}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-purple-600 text-white text-sm font-medium hover:bg-purple-700 disabled:opacity-50"
|
||||
>
|
||||
{checking ? <Loader2 className="h-4 w-4 animate-spin" /> : <Shield className="h-4 w-4" />}
|
||||
{checking ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Shield className="h-4 w-4" />
|
||||
)}
|
||||
检测 Kiro
|
||||
</button>
|
||||
<button
|
||||
@@ -260,7 +279,11 @@ export function ProxySettings() {
|
||||
disabled={checking}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{checking ? <Loader2 className="h-4 w-4 animate-spin" /> : <Shield className="h-4 w-4" />}
|
||||
{checking ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Shield className="h-4 w-4" />
|
||||
)}
|
||||
检测 Gemini
|
||||
</button>
|
||||
<button
|
||||
@@ -268,7 +291,11 @@ export function ProxySettings() {
|
||||
disabled={checking}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-green-600 text-white text-sm font-medium hover:bg-green-700 disabled:opacity-50"
|
||||
>
|
||||
{checking ? <Loader2 className="h-4 w-4 animate-spin" /> : <Shield className="h-4 w-4" />}
|
||||
{checking ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Shield className="h-4 w-4" />
|
||||
)}
|
||||
检测 Qwen
|
||||
</button>
|
||||
</div>
|
||||
@@ -285,7 +312,8 @@ export function ProxySettings() {
|
||||
<div className="flex items-center gap-2">
|
||||
{getStatusIcon(checkResult.overall_status)}
|
||||
<span className="font-medium">
|
||||
{checkResult.provider.toUpperCase()} - {getStatusText(checkResult.overall_status)}
|
||||
{checkResult.provider.toUpperCase()} -{" "}
|
||||
{getStatusText(checkResult.overall_status)}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
@@ -310,7 +338,13 @@ export function ProxySettings() {
|
||||
) : (
|
||||
<XCircle className="h-4 w-4 text-red-500" />
|
||||
)}
|
||||
<span className={r.model.includes("tool_call") ? "font-medium text-purple-600" : ""}>
|
||||
<span
|
||||
className={
|
||||
r.model.includes("tool_call")
|
||||
? "font-medium text-purple-600"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{r.model}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@ export function SettingsPage() {
|
||||
"px-4 py-2 text-sm font-medium transition-colors relative",
|
||||
activeTab === tab.id
|
||||
? "text-primary"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
|
||||
@@ -23,7 +23,7 @@ export function AppTabs({ activeApp, onAppChange }: AppTabsProps) {
|
||||
"px-4 py-2 rounded-t-lg text-sm font-medium transition-colors",
|
||||
activeApp === app.id
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "hover:bg-muted text-muted-foreground"
|
||||
: "hover:bg-muted text-muted-foreground",
|
||||
)}
|
||||
title={app.description}
|
||||
>
|
||||
|
||||
@@ -51,7 +51,9 @@ export function LiveConfigModal({ appType, onClose }: LiveConfigModalProps) {
|
||||
className="p-1.5 rounded hover:bg-muted"
|
||||
title="刷新"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
|
||||
<RefreshCw
|
||||
className={`h-4 w-4 ${loading ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
<button onClick={onClose} className="p-1.5 rounded hover:bg-muted">
|
||||
<X className="h-5 w-5" />
|
||||
@@ -73,15 +75,16 @@ export function LiveConfigModal({ appType, onClose }: LiveConfigModalProps) {
|
||||
{JSON.stringify(config, null, 2)}
|
||||
</pre>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-center py-8">
|
||||
无配置数据
|
||||
</p>
|
||||
<p className="text-muted-foreground text-center py-8">无配置数据</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
配置文件路径: <code className="px-1 py-0.5 rounded bg-muted">{configPaths[appType]}</code>
|
||||
配置文件路径:{" "}
|
||||
<code className="px-1 py-0.5 rounded bg-muted">
|
||||
{configPaths[appType]}
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ export function ProviderCard({
|
||||
"relative rounded-lg border p-4 transition-all",
|
||||
isCurrent
|
||||
? "border-primary bg-primary/5 ring-1 ring-primary"
|
||||
: "hover:border-muted-foreground/50"
|
||||
: "hover:border-muted-foreground/50",
|
||||
)}
|
||||
>
|
||||
{isCurrent && (
|
||||
@@ -70,7 +70,7 @@ export function ProviderCard({
|
||||
"p-1.5 rounded text-destructive",
|
||||
isCurrent
|
||||
? "opacity-30 cursor-not-allowed"
|
||||
: "hover:bg-destructive/10"
|
||||
: "hover:bg-destructive/10",
|
||||
)}
|
||||
title={isCurrent ? "无法删除当前使用中的配置" : "删除"}
|
||||
>
|
||||
|
||||
@@ -7,12 +7,20 @@ import { cn } from "@/lib/utils";
|
||||
interface ProviderFormProps {
|
||||
appType: AppType;
|
||||
provider: Provider | null;
|
||||
onSave: (data: Omit<Provider, "id" | "is_current" | "created_at">) => Promise<void>;
|
||||
onSave: (
|
||||
data: Omit<Provider, "id" | "is_current" | "created_at">,
|
||||
) => Promise<void>;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
// 供应商分类
|
||||
type ProviderCategory = "official" | "cn_official" | "aggregator" | "third_party" | "proxy" | "custom";
|
||||
type ProviderCategory =
|
||||
| "official"
|
||||
| "cn_official"
|
||||
| "aggregator"
|
||||
| "third_party"
|
||||
| "proxy"
|
||||
| "custom";
|
||||
|
||||
// 预设供应商接口
|
||||
interface ProviderPreset {
|
||||
@@ -70,7 +78,8 @@ const presets: Record<AppType, ProviderPreset[]> = {
|
||||
iconColor: "#6B4FBB",
|
||||
websiteUrl: "https://bailian.console.aliyun.com",
|
||||
apiKeyUrl: "https://bailian.console.aliyun.com/?apiKey=1#/api-key",
|
||||
defaultBaseUrl: "https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
|
||||
defaultBaseUrl:
|
||||
"https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
|
||||
},
|
||||
{
|
||||
id: "kimi",
|
||||
@@ -96,7 +105,8 @@ const presets: Record<AppType, ProviderPreset[]> = {
|
||||
category: "cn_official",
|
||||
iconColor: "#F97316",
|
||||
websiteUrl: "https://platform.minimaxi.com",
|
||||
apiKeyUrl: "https://platform.minimaxi.com/user-center/basic-information/interface-key",
|
||||
apiKeyUrl:
|
||||
"https://platform.minimaxi.com/user-center/basic-information/interface-key",
|
||||
defaultBaseUrl: "https://api.minimaxi.com/anthropic",
|
||||
},
|
||||
{
|
||||
@@ -105,7 +115,8 @@ const presets: Record<AppType, ProviderPreset[]> = {
|
||||
category: "cn_official",
|
||||
iconColor: "#5DADEC",
|
||||
websiteUrl: "https://www.volcengine.com/product/doubao",
|
||||
apiKeyUrl: "https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey",
|
||||
apiKeyUrl:
|
||||
"https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey",
|
||||
defaultBaseUrl: "https://ark.cn-beijing.volces.com/api/coding",
|
||||
},
|
||||
// 聚合服务
|
||||
@@ -221,10 +232,14 @@ const categoryLabels: Record<ProviderCategory, string> = {
|
||||
};
|
||||
|
||||
// 默认 Codex auth.json 模板
|
||||
const defaultCodexAuth = JSON.stringify({
|
||||
api_key: "",
|
||||
api_base_url: ""
|
||||
}, null, 2);
|
||||
const defaultCodexAuth = JSON.stringify(
|
||||
{
|
||||
api_key: "",
|
||||
api_base_url: "",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
|
||||
// 默认 Codex config.toml 模板
|
||||
const defaultCodexConfig = `# Codex 配置文件
|
||||
@@ -237,17 +252,26 @@ GOOGLE_GEMINI_BASE_URL=
|
||||
GEMINI_MODEL=gemini-2.0-flash`;
|
||||
|
||||
// 默认 Gemini settings.json 模板
|
||||
const defaultGeminiSettings = JSON.stringify({
|
||||
mcpServers: {}
|
||||
}, null, 2);
|
||||
const defaultGeminiSettings = JSON.stringify(
|
||||
{
|
||||
mcpServers: {},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
|
||||
export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFormProps) {
|
||||
export function ProviderForm({
|
||||
appType,
|
||||
provider,
|
||||
onSave,
|
||||
onCancel,
|
||||
}: ProviderFormProps) {
|
||||
const isEditMode = Boolean(provider);
|
||||
const appPresets = useMemo(() => presets[appType] || [], [appType]);
|
||||
|
||||
// 基础字段
|
||||
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(
|
||||
isEditMode ? null : "custom"
|
||||
isEditMode ? null : "custom",
|
||||
);
|
||||
const [name, setName] = useState(provider?.name || "");
|
||||
const [notes, setNotes] = useState(provider?.notes || "");
|
||||
@@ -255,12 +279,31 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
|
||||
// 从 provider.settings_config 中提取 Claude 配置
|
||||
const extractClaudeConfig = () => {
|
||||
if (!provider?.settings_config || typeof provider.settings_config !== 'object') {
|
||||
return { apiKey: "", baseUrl: "", primaryModel: "claude-sonnet-4-20250514", haikuModel: "", sonnetModel: "", opusModel: "" };
|
||||
if (
|
||||
!provider?.settings_config ||
|
||||
typeof provider.settings_config !== "object"
|
||||
) {
|
||||
return {
|
||||
apiKey: "",
|
||||
baseUrl: "",
|
||||
primaryModel: "claude-sonnet-4-20250514",
|
||||
haikuModel: "",
|
||||
sonnetModel: "",
|
||||
opusModel: "",
|
||||
};
|
||||
}
|
||||
const env = (provider.settings_config as Record<string, unknown>).env as Record<string, string> | undefined;
|
||||
const env = (provider.settings_config as Record<string, unknown>).env as
|
||||
| Record<string, string>
|
||||
| undefined;
|
||||
if (!env) {
|
||||
return { apiKey: "", baseUrl: "", primaryModel: "claude-sonnet-4-20250514", haikuModel: "", sonnetModel: "", opusModel: "" };
|
||||
return {
|
||||
apiKey: "",
|
||||
baseUrl: "",
|
||||
primaryModel: "claude-sonnet-4-20250514",
|
||||
haikuModel: "",
|
||||
sonnetModel: "",
|
||||
opusModel: "",
|
||||
};
|
||||
}
|
||||
return {
|
||||
apiKey: env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN || "",
|
||||
@@ -284,7 +327,10 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
|
||||
// Claude 配置 JSON(可编辑)
|
||||
const [claudeConfigJson, setClaudeConfigJson] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
return JSON.stringify(provider.settings_config, null, 2);
|
||||
}
|
||||
return JSON.stringify({ env: {} }, null, 2);
|
||||
@@ -350,36 +396,50 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
|
||||
// Codex 专属字段 - 使用代码编辑器
|
||||
const [codexAuth, setCodexAuth] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
const auth = (provider.settings_config as Record<string, unknown>).auth;
|
||||
if (auth) return JSON.stringify(auth, null, 2);
|
||||
}
|
||||
return defaultCodexAuth;
|
||||
});
|
||||
const [codexConfig, setCodexConfig] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
const config = (provider.settings_config as Record<string, unknown>).config;
|
||||
if (typeof config === 'string') return config;
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
const config = (provider.settings_config as Record<string, unknown>)
|
||||
.config;
|
||||
if (typeof config === "string") return config;
|
||||
}
|
||||
return defaultCodexConfig;
|
||||
});
|
||||
|
||||
// Gemini 专属字段 - 使用代码编辑器
|
||||
const [geminiEnv, setGeminiEnv] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
const env = (provider.settings_config as Record<string, unknown>).env;
|
||||
if (env && typeof env === 'object') {
|
||||
if (env && typeof env === "object") {
|
||||
return Object.entries(env as Record<string, string>)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join('\n');
|
||||
.join("\n");
|
||||
}
|
||||
}
|
||||
return defaultGeminiEnv;
|
||||
});
|
||||
const [geminiSettings, setGeminiSettings] = useState(() => {
|
||||
if (provider?.settings_config && typeof provider.settings_config === 'object') {
|
||||
const config = (provider.settings_config as Record<string, unknown>).config;
|
||||
if (config && typeof config === 'object') {
|
||||
if (
|
||||
provider?.settings_config &&
|
||||
typeof provider.settings_config === "object"
|
||||
) {
|
||||
const config = (provider.settings_config as Record<string, unknown>)
|
||||
.config;
|
||||
if (config && typeof config === "object") {
|
||||
return JSON.stringify(config, null, 2);
|
||||
}
|
||||
}
|
||||
@@ -431,12 +491,20 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
// 重置 JSON 手动编辑标记,让表单同步到 JSON
|
||||
setJsonManuallyEdited(false);
|
||||
} else if (appType === "codex") {
|
||||
setCodexAuth(JSON.stringify({
|
||||
api_key: proxyApiKey,
|
||||
api_base_url: `${proxyBaseUrl}/v1`,
|
||||
}, null, 2));
|
||||
setCodexAuth(
|
||||
JSON.stringify(
|
||||
{
|
||||
api_key: proxyApiKey,
|
||||
api_base_url: `${proxyBaseUrl}/v1`,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
} else if (appType === "gemini") {
|
||||
setGeminiEnv(`GEMINI_API_KEY=${proxyApiKey}\nGOOGLE_GEMINI_BASE_URL=${proxyBaseUrl}\nGEMINI_MODEL=gemini-2.0-flash`);
|
||||
setGeminiEnv(
|
||||
`GEMINI_API_KEY=${proxyApiKey}\nGOOGLE_GEMINI_BASE_URL=${proxyBaseUrl}\nGEMINI_MODEL=gemini-2.0-flash`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
} catch (e) {
|
||||
@@ -474,7 +542,7 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
if (preset.defaultGeminiEnv) {
|
||||
const envLines = Object.entries(preset.defaultGeminiEnv)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join('\n');
|
||||
.join("\n");
|
||||
setGeminiEnv(envLines);
|
||||
} else {
|
||||
setGeminiEnv(defaultGeminiEnv);
|
||||
@@ -504,10 +572,10 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
|
||||
if (appType === "gemini") {
|
||||
const env: Record<string, string> = {};
|
||||
for (const line of geminiEnv.split('\n')) {
|
||||
for (const line of geminiEnv.split("\n")) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const idx = trimmed.indexOf('=');
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
const idx = trimmed.indexOf("=");
|
||||
if (idx > 0) {
|
||||
const key = trimmed.slice(0, idx).trim();
|
||||
const value = trimmed.slice(idx + 1).trim();
|
||||
@@ -535,7 +603,12 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
}
|
||||
|
||||
// 非官方供应商必填校验(新增模式必填,编辑模式可留空保持原值)
|
||||
if (!isEditMode && showApiFields && appType === "claude" && !apiKey.trim()) {
|
||||
if (
|
||||
!isEditMode &&
|
||||
showApiFields &&
|
||||
appType === "claude" &&
|
||||
!apiKey.trim()
|
||||
) {
|
||||
setError("请填写 API Key");
|
||||
return;
|
||||
}
|
||||
@@ -590,7 +663,7 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
"flex items-center gap-2 px-3 py-2 rounded-lg border text-sm transition-all",
|
||||
selectedPresetId === preset.id
|
||||
? "border-primary bg-primary/10 text-primary"
|
||||
: "border-border hover:border-muted-foreground/50"
|
||||
: "border-border hover:border-muted-foreground/50",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
@@ -643,9 +716,12 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
<label className="text-sm font-medium">
|
||||
API Key <span className="text-destructive">*</span>
|
||||
</label>
|
||||
{(selectedPreset?.apiKeyUrl || selectedPreset?.websiteUrl) && (
|
||||
{(selectedPreset?.apiKeyUrl ||
|
||||
selectedPreset?.websiteUrl) && (
|
||||
<a
|
||||
href={selectedPreset?.apiKeyUrl || selectedPreset?.websiteUrl}
|
||||
href={
|
||||
selectedPreset?.apiKeyUrl || selectedPreset?.websiteUrl
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-primary hover:underline flex items-center gap-1"
|
||||
@@ -679,7 +755,9 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">API 端点</label>
|
||||
<label className="block text-sm font-medium mb-1.5">
|
||||
API 端点
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={baseUrl}
|
||||
@@ -687,14 +765,18 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
className="w-full px-3 py-2 rounded-lg border bg-background focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none font-mono"
|
||||
placeholder="https://api.example.com/v1"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">留空使用默认端点</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
留空使用默认端点
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<label className="block text-sm font-medium">模型配置</label>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs text-muted-foreground mb-1">主模型</label>
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
主模型
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={primaryModel}
|
||||
@@ -704,7 +786,9 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-muted-foreground mb-1">Haiku 默认模型</label>
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
Haiku 默认模型
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={haikuModel}
|
||||
@@ -713,7 +797,9 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-muted-foreground mb-1">Sonnet 默认模型</label>
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
Sonnet 默认模型
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={sonnetModel}
|
||||
@@ -722,7 +808,9 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-muted-foreground mb-1">Opus 默认模型</label>
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
Opus 默认模型
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={opusModel}
|
||||
@@ -754,7 +842,8 @@ export function ProviderForm({ appType, provider, onSave, onCancel }: ProviderFo
|
||||
onChange={(e) => handleJsonChange(e.target.value)}
|
||||
className={cn(
|
||||
"w-full px-3 py-2 rounded-lg border bg-muted/50 font-mono text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none resize-none",
|
||||
jsonError && "border-destructive focus:ring-destructive/20 focus:border-destructive"
|
||||
jsonError &&
|
||||
"border-destructive focus:ring-destructive/20 focus:border-destructive",
|
||||
)}
|
||||
rows={10}
|
||||
placeholder='{"env": {}}'
|
||||
|
||||
@@ -24,7 +24,9 @@ export function ProviderList({ appType }: ProviderListProps) {
|
||||
} = useSwitch(appType);
|
||||
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editingProvider, setEditingProvider] = useState<typeof providers[0] | null>(null);
|
||||
const [editingProvider, setEditingProvider] = useState<
|
||||
(typeof providers)[0] | null
|
||||
>(null);
|
||||
const [showLiveConfig, setShowLiveConfig] = useState(false);
|
||||
|
||||
const handleAdd = () => {
|
||||
@@ -32,7 +34,7 @@ export function ProviderList({ appType }: ProviderListProps) {
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
const handleEdit = (provider: typeof providers[0]) => {
|
||||
const handleEdit = (provider: (typeof providers)[0]) => {
|
||||
setEditingProvider(provider);
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
@@ -89,7 +89,11 @@ export function useMcpServers() {
|
||||
await fetchServers();
|
||||
};
|
||||
|
||||
const toggleServer = async (id: string, appType: string, enabled: boolean) => {
|
||||
const toggleServer = async (
|
||||
id: string,
|
||||
appType: string,
|
||||
enabled: boolean,
|
||||
) => {
|
||||
await mcpApi.toggleServer(id, appType, enabled);
|
||||
await fetchServers();
|
||||
};
|
||||
|
||||
@@ -3,7 +3,9 @@ import { promptsApi, Prompt, AppType } from "@/lib/api/prompts";
|
||||
|
||||
export function usePrompts(appType: AppType) {
|
||||
const [prompts, setPrompts] = useState<Record<string, Prompt>>({});
|
||||
const [currentFileContent, setCurrentFileContent] = useState<string | null>(null);
|
||||
const [currentFileContent, setCurrentFileContent] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const hasAutoImported = useRef<Record<string, boolean>>({});
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ export function useSwitch(appType: AppType) {
|
||||
fetchProviders();
|
||||
}, [fetchProviders]);
|
||||
|
||||
const addProvider = async (provider: Omit<Provider, "id" | "is_current" | "created_at">) => {
|
||||
const addProvider = async (
|
||||
provider: Omit<Provider, "id" | "is_current" | "created_at">,
|
||||
) => {
|
||||
const newProvider: Provider = {
|
||||
...provider,
|
||||
id: crypto.randomUUID(),
|
||||
|
||||
+7
-6
@@ -17,8 +17,7 @@ export interface McpServer {
|
||||
}
|
||||
|
||||
export const mcpApi = {
|
||||
getServers: (): Promise<McpServer[]> =>
|
||||
invoke("get_mcp_servers"),
|
||||
getServers: (): Promise<McpServer[]> => invoke("get_mcp_servers"),
|
||||
|
||||
addServer: (server: McpServer): Promise<void> =>
|
||||
invoke("add_mcp_server", { server }),
|
||||
@@ -29,14 +28,16 @@ export const mcpApi = {
|
||||
deleteServer: (id: string): Promise<void> =>
|
||||
invoke("delete_mcp_server", { id }),
|
||||
|
||||
toggleServer: (id: string, appType: string, enabled: boolean): Promise<void> =>
|
||||
invoke("toggle_mcp_server", { id, appType, enabled }),
|
||||
toggleServer: (
|
||||
id: string,
|
||||
appType: string,
|
||||
enabled: boolean,
|
||||
): Promise<void> => invoke("toggle_mcp_server", { id, appType, enabled }),
|
||||
|
||||
/** 从外部应用导入 MCP 配置 */
|
||||
importFromApp: (appType: string): Promise<number> =>
|
||||
invoke("import_mcp_from_app", { appType }),
|
||||
|
||||
/** 同步所有 MCP 配置到实际配置文件 */
|
||||
syncAllToLive: (): Promise<void> =>
|
||||
invoke("sync_all_mcp_to_live"),
|
||||
syncAllToLive: (): Promise<void> => invoke("sync_all_mcp_to_live"),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user