2025-08-21 22:20:57 +08:00
|
|
|
|
import React, { useState, useEffect } from "react";
|
2025-08-23 23:11:39 +08:00
|
|
|
|
import { Provider } from "../types";
|
2025-08-30 21:54:11 +08:00
|
|
|
|
import { AppType } from "../lib/tauri-api";
|
2025-08-21 22:20:57 +08:00
|
|
|
|
import {
|
|
|
|
|
|
updateCoAuthoredSetting,
|
|
|
|
|
|
checkCoAuthoredSetting,
|
2025-08-26 10:37:44 +08:00
|
|
|
|
getApiKeyFromConfig,
|
|
|
|
|
|
hasApiKeyField,
|
|
|
|
|
|
setApiKeyInConfig,
|
2025-08-21 22:20:57 +08:00
|
|
|
|
} from "../utils/providerConfigUtils";
|
|
|
|
|
|
import { providerPresets } from "../config/providerPresets";
|
2025-08-30 22:08:41 +08:00
|
|
|
|
import { codexProviderPresets } from "../config/codexProviderPresets";
|
2025-09-06 09:30:09 +08:00
|
|
|
|
import JsonEditor from "./JsonEditor";
|
2025-09-06 16:21:21 +08:00
|
|
|
|
import { X, AlertCircle, Save, Zap } from "lucide-react";
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
|
|
|
|
|
interface ProviderFormProps {
|
2025-08-30 21:54:11 +08:00
|
|
|
|
appType?: AppType;
|
2025-08-21 22:20:57 +08:00
|
|
|
|
title: string;
|
|
|
|
|
|
submitText: string;
|
|
|
|
|
|
initialData?: Provider;
|
|
|
|
|
|
showPresets?: boolean;
|
|
|
|
|
|
onSubmit: (data: Omit<Provider, "id">) => void;
|
|
|
|
|
|
onClose: () => void;
|
2025-08-08 15:03:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const ProviderForm: React.FC<ProviderFormProps> = ({
|
2025-08-30 21:54:11 +08:00
|
|
|
|
appType = "claude",
|
2025-08-08 15:03:38 +08:00
|
|
|
|
title,
|
|
|
|
|
|
submitText,
|
|
|
|
|
|
initialData,
|
|
|
|
|
|
showPresets = false,
|
|
|
|
|
|
onSubmit,
|
2025-08-21 22:20:57 +08:00
|
|
|
|
onClose,
|
2025-08-08 15:03:38 +08:00
|
|
|
|
}) => {
|
2025-08-30 21:54:11 +08:00
|
|
|
|
// 对于 Codex,需要分离 auth 和 config
|
|
|
|
|
|
const isCodex = appType === "codex";
|
|
|
|
|
|
|
2025-08-08 15:03:38 +08:00
|
|
|
|
const [formData, setFormData] = useState({
|
2025-08-21 22:20:57 +08:00
|
|
|
|
name: initialData?.name || "",
|
|
|
|
|
|
websiteUrl: initialData?.websiteUrl || "",
|
|
|
|
|
|
settingsConfig: initialData
|
|
|
|
|
|
? JSON.stringify(initialData.settingsConfig, null, 2)
|
|
|
|
|
|
: "",
|
|
|
|
|
|
});
|
2025-08-30 21:54:11 +08:00
|
|
|
|
|
|
|
|
|
|
// Codex 特有的状态
|
|
|
|
|
|
const [codexAuth, setCodexAuth] = useState("");
|
|
|
|
|
|
const [codexConfig, setCodexConfig] = useState("");
|
2025-08-30 22:08:41 +08:00
|
|
|
|
const [codexApiKey, setCodexApiKey] = useState("");
|
2025-09-03 15:58:02 +08:00
|
|
|
|
// -1 表示自定义,null 表示未选择,>= 0 表示预设索引
|
2025-08-30 22:08:41 +08:00
|
|
|
|
const [selectedCodexPreset, setSelectedCodexPreset] = useState<number | null>(
|
2025-09-06 16:21:21 +08:00
|
|
|
|
showPresets && isCodex ? -1 : null
|
2025-08-30 22:08:41 +08:00
|
|
|
|
);
|
2025-08-30 21:54:11 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化 Codex 配置
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (isCodex && initialData) {
|
|
|
|
|
|
const config = initialData.settingsConfig;
|
|
|
|
|
|
if (typeof config === "object" && config !== null) {
|
|
|
|
|
|
setCodexAuth(JSON.stringify(config.auth || {}, null, 2));
|
|
|
|
|
|
setCodexConfig(config.config || "");
|
2025-08-30 22:08:41 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const auth = config.auth || {};
|
2025-08-31 16:39:38 +08:00
|
|
|
|
if (auth && typeof auth.OPENAI_API_KEY === "string") {
|
|
|
|
|
|
setCodexApiKey(auth.OPENAI_API_KEY);
|
2025-08-30 22:08:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// ignore
|
|
|
|
|
|
}
|
2025-08-30 21:54:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [isCodex, initialData]);
|
2025-08-21 22:20:57 +08:00
|
|
|
|
const [error, setError] = useState("");
|
|
|
|
|
|
const [disableCoAuthored, setDisableCoAuthored] = useState(false);
|
2025-09-03 15:58:02 +08:00
|
|
|
|
// -1 表示自定义,null 表示未选择,>= 0 表示预设索引
|
|
|
|
|
|
const [selectedPreset, setSelectedPreset] = useState<number | null>(
|
2025-09-06 16:21:21 +08:00
|
|
|
|
showPresets ? -1 : null
|
2025-09-03 15:58:02 +08:00
|
|
|
|
);
|
2025-08-21 22:20:57 +08:00
|
|
|
|
const [apiKey, setApiKey] = useState("");
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化时检查禁用签名状态
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (initialData) {
|
2025-08-21 22:20:57 +08:00
|
|
|
|
const configString = JSON.stringify(initialData.settingsConfig, null, 2);
|
|
|
|
|
|
const hasCoAuthoredDisabled = checkCoAuthoredSetting(configString);
|
|
|
|
|
|
setDisableCoAuthored(hasCoAuthoredDisabled);
|
2025-08-08 15:03:38 +08:00
|
|
|
|
}
|
2025-08-21 22:20:57 +08:00
|
|
|
|
}, [initialData]);
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
|
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
2025-08-21 22:20:57 +08:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
setError("");
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
|
|
|
|
|
if (!formData.name) {
|
2025-08-21 22:20:57 +08:00
|
|
|
|
setError("请填写供应商名称");
|
|
|
|
|
|
return;
|
2025-08-08 15:03:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 22:20:57 +08:00
|
|
|
|
let settingsConfig: Record<string, any>;
|
|
|
|
|
|
|
2025-08-30 21:54:11 +08:00
|
|
|
|
if (isCodex) {
|
2025-08-31 16:39:38 +08:00
|
|
|
|
// Codex: 仅要求 auth.json 必填;config.toml 可为空
|
|
|
|
|
|
if (!codexAuth.trim()) {
|
|
|
|
|
|
setError("请填写 auth.json 配置");
|
2025-08-30 21:54:11 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const authJson = JSON.parse(codexAuth);
|
2025-08-31 17:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 非官方预设强制要求 OPENAI_API_KEY
|
|
|
|
|
|
if (selectedCodexPreset !== null) {
|
|
|
|
|
|
const preset = codexProviderPresets[selectedCodexPreset];
|
|
|
|
|
|
const isOfficial = Boolean(preset?.isOfficial);
|
|
|
|
|
|
if (!isOfficial) {
|
|
|
|
|
|
const key =
|
|
|
|
|
|
typeof authJson.OPENAI_API_KEY === "string"
|
|
|
|
|
|
? authJson.OPENAI_API_KEY.trim()
|
|
|
|
|
|
: "";
|
|
|
|
|
|
if (!key) {
|
|
|
|
|
|
setError("请填写 OPENAI_API_KEY");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-30 21:54:11 +08:00
|
|
|
|
settingsConfig = {
|
|
|
|
|
|
auth: authJson,
|
2025-08-31 16:39:38 +08:00
|
|
|
|
config: codexConfig ?? "",
|
2025-08-30 21:54:11 +08:00
|
|
|
|
};
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
setError("auth.json 格式错误,请检查JSON语法");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Claude: 原有逻辑
|
|
|
|
|
|
if (!formData.settingsConfig.trim()) {
|
|
|
|
|
|
setError("请填写配置内容");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
settingsConfig = JSON.parse(formData.settingsConfig);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
setError("配置JSON格式错误,请检查语法");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-08 15:03:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onSubmit({
|
|
|
|
|
|
name: formData.name,
|
|
|
|
|
|
websiteUrl: formData.websiteUrl,
|
2025-08-21 22:20:57 +08:00
|
|
|
|
settingsConfig,
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
|
|
|
|
|
const handleChange = (
|
2025-09-06 16:21:21 +08:00
|
|
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
2025-08-08 15:03:38 +08:00
|
|
|
|
) => {
|
2025-08-21 22:20:57 +08:00
|
|
|
|
const { name, value } = e.target;
|
|
|
|
|
|
|
|
|
|
|
|
if (name === "settingsConfig") {
|
2025-08-08 15:03:38 +08:00
|
|
|
|
// 同时检查并同步选择框状态
|
2025-08-21 22:20:57 +08:00
|
|
|
|
const hasCoAuthoredDisabled = checkCoAuthoredSetting(value);
|
|
|
|
|
|
setDisableCoAuthored(hasCoAuthoredDisabled);
|
|
|
|
|
|
|
2025-08-26 10:37:44 +08:00
|
|
|
|
// 同步 API Key 输入框显示与值
|
|
|
|
|
|
const parsedKey = getApiKeyFromConfig(value);
|
|
|
|
|
|
setApiKey(parsedKey);
|
|
|
|
|
|
|
2025-09-06 09:30:09 +08:00
|
|
|
|
// 不再从 JSON 自动提取或覆盖官网地址,只更新配置内容
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
2025-08-08 15:03:38 +08:00
|
|
|
|
[name]: value,
|
2025-09-06 09:30:09 +08:00
|
|
|
|
}));
|
2025-08-08 15:03:38 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
setFormData({
|
|
|
|
|
|
...formData,
|
|
|
|
|
|
[name]: value,
|
2025-08-21 22:20:57 +08:00
|
|
|
|
});
|
2025-08-08 15:03:38 +08:00
|
|
|
|
}
|
2025-08-21 22:20:57 +08:00
|
|
|
|
};
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理选择框变化
|
|
|
|
|
|
const handleCoAuthoredToggle = (checked: boolean) => {
|
2025-08-21 22:20:57 +08:00
|
|
|
|
setDisableCoAuthored(checked);
|
|
|
|
|
|
|
2025-08-08 15:03:38 +08:00
|
|
|
|
// 更新JSON配置
|
2025-08-21 22:20:57 +08:00
|
|
|
|
const updatedConfig = updateCoAuthoredSetting(
|
|
|
|
|
|
formData.settingsConfig,
|
2025-09-06 16:21:21 +08:00
|
|
|
|
checked
|
2025-08-21 22:20:57 +08:00
|
|
|
|
);
|
2025-08-08 15:03:38 +08:00
|
|
|
|
setFormData({
|
|
|
|
|
|
...formData,
|
|
|
|
|
|
settingsConfig: updatedConfig,
|
2025-08-21 22:20:57 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const applyPreset = (preset: (typeof providerPresets)[0], index: number) => {
|
|
|
|
|
|
const configString = JSON.stringify(preset.settingsConfig, null, 2);
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
|
|
|
|
|
setFormData({
|
|
|
|
|
|
name: preset.name,
|
|
|
|
|
|
websiteUrl: preset.websiteUrl,
|
2025-08-21 22:20:57 +08:00
|
|
|
|
settingsConfig: configString,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 21:46:48 +08:00
|
|
|
|
// 设置选中的预设
|
2025-08-21 22:20:57 +08:00
|
|
|
|
setSelectedPreset(index);
|
|
|
|
|
|
|
|
|
|
|
|
// 清空 API Key 输入框,让用户重新输入
|
|
|
|
|
|
setApiKey("");
|
|
|
|
|
|
|
2025-08-08 15:03:38 +08:00
|
|
|
|
// 同步选择框状态
|
2025-08-21 22:20:57 +08:00
|
|
|
|
const hasCoAuthoredDisabled = checkCoAuthoredSetting(configString);
|
|
|
|
|
|
setDisableCoAuthored(hasCoAuthoredDisabled);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-03 15:58:02 +08:00
|
|
|
|
// 处理点击自定义按钮
|
|
|
|
|
|
const handleCustomClick = () => {
|
|
|
|
|
|
setSelectedPreset(-1);
|
|
|
|
|
|
setFormData({
|
|
|
|
|
|
name: "",
|
|
|
|
|
|
websiteUrl: "",
|
|
|
|
|
|
settingsConfig: "",
|
|
|
|
|
|
});
|
|
|
|
|
|
setApiKey("");
|
|
|
|
|
|
setDisableCoAuthored(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-30 22:08:41 +08:00
|
|
|
|
// Codex: 应用预设
|
|
|
|
|
|
const applyCodexPreset = (
|
|
|
|
|
|
preset: (typeof codexProviderPresets)[0],
|
2025-09-06 16:21:21 +08:00
|
|
|
|
index: number
|
2025-08-30 22:08:41 +08:00
|
|
|
|
) => {
|
|
|
|
|
|
const authString = JSON.stringify(preset.auth || {}, null, 2);
|
|
|
|
|
|
setCodexAuth(authString);
|
|
|
|
|
|
setCodexConfig(preset.config || "");
|
|
|
|
|
|
|
|
|
|
|
|
setFormData({
|
|
|
|
|
|
name: preset.name,
|
|
|
|
|
|
websiteUrl: preset.websiteUrl,
|
|
|
|
|
|
settingsConfig: formData.settingsConfig,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
setSelectedCodexPreset(index);
|
|
|
|
|
|
|
2025-08-30 22:09:19 +08:00
|
|
|
|
// 清空 API Key,让用户重新输入
|
|
|
|
|
|
setCodexApiKey("");
|
2025-08-30 22:08:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-03 15:58:02 +08:00
|
|
|
|
// Codex: 处理点击自定义按钮
|
|
|
|
|
|
const handleCodexCustomClick = () => {
|
|
|
|
|
|
setSelectedCodexPreset(-1);
|
|
|
|
|
|
setFormData({
|
|
|
|
|
|
name: "",
|
|
|
|
|
|
websiteUrl: "",
|
|
|
|
|
|
settingsConfig: "",
|
|
|
|
|
|
});
|
|
|
|
|
|
setCodexAuth("");
|
|
|
|
|
|
setCodexConfig("");
|
|
|
|
|
|
setCodexApiKey("");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 22:20:57 +08:00
|
|
|
|
// 处理 API Key 输入并自动更新配置
|
|
|
|
|
|
const handleApiKeyChange = (key: string) => {
|
|
|
|
|
|
setApiKey(key);
|
|
|
|
|
|
|
2025-08-26 10:37:44 +08:00
|
|
|
|
const configString = setApiKeyInConfig(
|
|
|
|
|
|
formData.settingsConfig,
|
|
|
|
|
|
key.trim(),
|
2025-09-06 16:21:21 +08:00
|
|
|
|
{ createIfMissing: selectedPreset !== null && selectedPreset !== -1 }
|
2025-08-26 10:37:44 +08:00
|
|
|
|
);
|
2025-08-21 22:20:57 +08:00
|
|
|
|
|
2025-08-26 10:37:44 +08:00
|
|
|
|
// 更新表单配置
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
settingsConfig: configString,
|
|
|
|
|
|
}));
|
2025-08-21 22:20:57 +08:00
|
|
|
|
|
2025-08-26 10:37:44 +08:00
|
|
|
|
// 同步选择框状态
|
|
|
|
|
|
const hasCoAuthoredDisabled = checkCoAuthoredSetting(configString);
|
|
|
|
|
|
setDisableCoAuthored(hasCoAuthoredDisabled);
|
|
|
|
|
|
};
|
2025-08-21 22:20:57 +08:00
|
|
|
|
|
2025-08-30 22:08:41 +08:00
|
|
|
|
// Codex: 处理 API Key 输入并写回 auth.json
|
|
|
|
|
|
const handleCodexApiKeyChange = (key: string) => {
|
|
|
|
|
|
setCodexApiKey(key);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const auth = JSON.parse(codexAuth || "{}");
|
2025-08-31 16:39:38 +08:00
|
|
|
|
auth.OPENAI_API_KEY = key.trim();
|
2025-08-30 22:08:41 +08:00
|
|
|
|
setCodexAuth(JSON.stringify(auth, null, 2));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// ignore
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-26 10:37:44 +08:00
|
|
|
|
// 根据当前配置决定是否展示 API Key 输入框
|
2025-09-03 15:58:02 +08:00
|
|
|
|
// 自定义模式(-1)不显示独立的 API Key 输入框
|
2025-08-26 10:37:44 +08:00
|
|
|
|
const showApiKey =
|
2025-09-05 21:26:01 +08:00
|
|
|
|
(selectedPreset !== null && selectedPreset !== -1) ||
|
2025-09-03 15:58:02 +08:00
|
|
|
|
(!showPresets && hasApiKeyField(formData.settingsConfig));
|
2025-08-21 22:20:57 +08:00
|
|
|
|
|
2025-08-29 09:03:11 +08:00
|
|
|
|
// 判断当前选中的预设是否是官方
|
2025-08-29 11:35:17 +08:00
|
|
|
|
const isOfficialPreset =
|
|
|
|
|
|
selectedPreset !== null &&
|
2025-09-03 15:58:02 +08:00
|
|
|
|
selectedPreset >= 0 &&
|
2025-08-29 09:03:11 +08:00
|
|
|
|
providerPresets[selectedPreset]?.isOfficial === true;
|
|
|
|
|
|
|
2025-08-30 22:08:41 +08:00
|
|
|
|
// Codex: 控制显示 API Key 与官方标记
|
|
|
|
|
|
const getCodexAuthApiKey = (authString: string): string => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const auth = JSON.parse(authString || "{}");
|
2025-08-31 16:39:38 +08:00
|
|
|
|
return typeof auth.OPENAI_API_KEY === "string" ? auth.OPENAI_API_KEY : "";
|
2025-08-30 22:08:41 +08:00
|
|
|
|
} catch {
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-09-03 15:58:02 +08:00
|
|
|
|
// 自定义模式(-1)不显示独立的 API Key 输入框
|
2025-08-30 22:08:41 +08:00
|
|
|
|
const showCodexApiKey =
|
2025-09-05 21:26:01 +08:00
|
|
|
|
(selectedCodexPreset !== null && selectedCodexPreset !== -1) ||
|
2025-09-03 15:58:02 +08:00
|
|
|
|
(!showPresets && getCodexAuthApiKey(codexAuth) !== "");
|
2025-08-30 22:08:41 +08:00
|
|
|
|
const isCodexOfficialPreset =
|
|
|
|
|
|
selectedCodexPreset !== null &&
|
2025-09-03 15:58:02 +08:00
|
|
|
|
selectedCodexPreset >= 0 &&
|
2025-08-30 22:08:41 +08:00
|
|
|
|
codexProviderPresets[selectedCodexPreset]?.isOfficial === true;
|
|
|
|
|
|
|
2025-08-26 10:37:44 +08:00
|
|
|
|
// 初始时从配置中同步 API Key(编辑模式)
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (initialData) {
|
|
|
|
|
|
const parsedKey = getApiKeyFromConfig(
|
2025-09-06 16:21:21 +08:00
|
|
|
|
JSON.stringify(initialData.settingsConfig)
|
2025-08-26 10:37:44 +08:00
|
|
|
|
);
|
|
|
|
|
|
if (parsedKey) setApiKey(parsedKey);
|
2025-08-21 22:20:57 +08:00
|
|
|
|
}
|
2025-08-26 10:37:44 +08:00
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
|
}, []);
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
2025-08-26 12:34:47 +08:00
|
|
|
|
// 支持按下 ESC 关闭弹窗
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const onKeyDown = (e: KeyboardEvent) => {
|
2025-08-26 15:12:27 +08:00
|
|
|
|
if (e.key === "Escape") {
|
2025-08-26 12:34:47 +08:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
onClose();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-08-26 15:12:27 +08:00
|
|
|
|
window.addEventListener("keydown", onKeyDown);
|
|
|
|
|
|
return () => window.removeEventListener("keydown", onKeyDown);
|
2025-08-26 12:34:47 +08:00
|
|
|
|
}, [onClose]);
|
|
|
|
|
|
|
2025-08-08 15:03:38 +08:00
|
|
|
|
return (
|
2025-08-26 15:12:27 +08:00
|
|
|
|
<div
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className="fixed inset-0 z-50 flex items-center justify-center"
|
2025-08-26 15:12:27 +08:00
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
|
if (e.target === e.currentTarget) onClose();
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
{/* Backdrop */}
|
|
|
|
|
|
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" />
|
|
|
|
|
|
|
|
|
|
|
|
{/* Modal */}
|
|
|
|
|
|
<div className="relative bg-white rounded-xl shadow-lg max-w-3xl w-full mx-4 max-h-[90vh] overflow-hidden flex flex-col">
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div className="flex items-center justify-between p-6 border-b border-[var(--color-border)]">
|
|
|
|
|
|
<h2 className="text-xl font-semibold text-[var(--color-text-primary)]">
|
2025-08-26 15:12:27 +08:00
|
|
|
|
{title}
|
2025-09-06 16:21:21 +08:00
|
|
|
|
</h2>
|
2025-08-26 12:34:47 +08:00
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onClose}
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className="p-1 text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-tertiary)] rounded-md transition-colors"
|
|
|
|
|
|
aria-label="关闭"
|
2025-08-26 12:34:47 +08:00
|
|
|
|
>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<X size={18} />
|
2025-08-26 12:34:47 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<form onSubmit={handleSubmit} className="flex flex-col flex-1 min-h-0">
|
|
|
|
|
|
<div className="flex-1 overflow-auto p-6 space-y-6">
|
|
|
|
|
|
{error && (
|
|
|
|
|
|
<div className="flex items-center gap-3 p-4 bg-[var(--color-error-light)] border border-[var(--color-error)]/20 rounded-lg">
|
|
|
|
|
|
<AlertCircle
|
|
|
|
|
|
size={20}
|
|
|
|
|
|
className="text-[var(--color-error)] flex-shrink-0"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<p className="text-[var(--color-error)] text-sm font-medium">
|
|
|
|
|
|
{error}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-08-26 15:12:27 +08:00
|
|
|
|
|
2025-08-30 21:54:11 +08:00
|
|
|
|
{showPresets && !isCodex && (
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-[var(--color-text-primary)] mb-3">
|
|
|
|
|
|
选择配置类型
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
|
|
|
|
|
selectedPreset === -1
|
|
|
|
|
|
? "bg-[var(--color-primary)] text-white"
|
|
|
|
|
|
: "bg-[var(--color-bg-tertiary)] text-[var(--color-text-secondary)] hover:bg-[var(--color-border)]"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
onClick={handleCustomClick}
|
|
|
|
|
|
>
|
|
|
|
|
|
自定义
|
|
|
|
|
|
</button>
|
|
|
|
|
|
{providerPresets.map((preset, index) => (
|
2025-08-29 09:03:11 +08:00
|
|
|
|
<button
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
type="button"
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
|
|
|
|
|
selectedPreset === index
|
|
|
|
|
|
? preset.isOfficial
|
|
|
|
|
|
? "bg-[var(--color-warning)] text-white"
|
|
|
|
|
|
: "bg-[var(--color-primary)] text-white"
|
|
|
|
|
|
: "bg-[var(--color-bg-tertiary)] text-[var(--color-text-secondary)] hover:bg-[var(--color-border)]"
|
|
|
|
|
|
}`}
|
2025-08-29 09:03:11 +08:00
|
|
|
|
onClick={() => applyPreset(preset, index)}
|
|
|
|
|
|
>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
{preset.isOfficial && <Zap size={14} />}
|
2025-08-29 09:03:11 +08:00
|
|
|
|
{preset.name}
|
|
|
|
|
|
</button>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
2025-08-26 15:12:27 +08:00
|
|
|
|
</div>
|
2025-09-03 15:58:02 +08:00
|
|
|
|
{selectedPreset === -1 && (
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<p className="text-sm text-[var(--color-text-secondary)]">
|
2025-09-03 15:58:02 +08:00
|
|
|
|
手动配置供应商,需要填写完整的配置信息
|
2025-09-06 16:21:21 +08:00
|
|
|
|
</p>
|
2025-09-03 15:58:02 +08:00
|
|
|
|
)}
|
|
|
|
|
|
{selectedPreset !== -1 && selectedPreset !== null && (
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<p className="text-sm text-[var(--color-text-secondary)]">
|
2025-09-05 21:26:01 +08:00
|
|
|
|
{isOfficialPreset
|
|
|
|
|
|
? "Claude 官方登录,不需要填写 API Key"
|
2025-09-03 15:58:02 +08:00
|
|
|
|
: "使用预设配置,只需填写 API Key"}
|
2025-09-06 16:21:21 +08:00
|
|
|
|
</p>
|
2025-09-03 15:58:02 +08:00
|
|
|
|
)}
|
2025-08-26 12:34:47 +08:00
|
|
|
|
</div>
|
2025-08-26 15:12:27 +08:00
|
|
|
|
)}
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
2025-08-30 22:08:41 +08:00
|
|
|
|
{showPresets && isCodex && (
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-[var(--color-text-primary)] mb-3">
|
|
|
|
|
|
选择配置类型
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
2025-08-30 22:08:41 +08:00
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
|
|
|
|
|
selectedCodexPreset === -1
|
|
|
|
|
|
? "bg-[var(--color-primary)] text-white"
|
|
|
|
|
|
: "bg-[var(--color-bg-tertiary)] text-[var(--color-text-secondary)] hover:bg-[var(--color-border)]"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
onClick={handleCodexCustomClick}
|
2025-08-30 22:08:41 +08:00
|
|
|
|
>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
自定义
|
2025-08-30 22:08:41 +08:00
|
|
|
|
</button>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
{codexProviderPresets.map((preset, index) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={index}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
|
|
|
|
|
selectedCodexPreset === index
|
|
|
|
|
|
? preset.isOfficial
|
|
|
|
|
|
? "bg-[var(--color-warning)] text-white"
|
|
|
|
|
|
: "bg-[var(--color-primary)] text-white"
|
|
|
|
|
|
: "bg-[var(--color-bg-tertiary)] text-[var(--color-text-secondary)] hover:bg-[var(--color-border)]"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
onClick={() => applyCodexPreset(preset, index)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{preset.isOfficial && <Zap size={14} />}
|
|
|
|
|
|
{preset.name}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
2025-08-30 22:08:41 +08:00
|
|
|
|
</div>
|
2025-09-03 15:58:02 +08:00
|
|
|
|
{selectedCodexPreset === -1 && (
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<p className="text-sm text-[var(--color-text-secondary)]">
|
2025-09-03 15:58:02 +08:00
|
|
|
|
手动配置供应商,需要填写完整的配置信息
|
2025-09-06 16:21:21 +08:00
|
|
|
|
</p>
|
2025-09-03 15:58:02 +08:00
|
|
|
|
)}
|
|
|
|
|
|
{selectedCodexPreset !== -1 && selectedCodexPreset !== null && (
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<p className="text-sm text-[var(--color-text-secondary)]">
|
2025-09-05 21:26:01 +08:00
|
|
|
|
{isCodexOfficialPreset
|
|
|
|
|
|
? "Codex 官方登录,不需要填写 API Key"
|
2025-09-03 15:58:02 +08:00
|
|
|
|
: "使用预设配置,只需填写 API Key"}
|
2025-09-06 16:21:21 +08:00
|
|
|
|
</p>
|
2025-09-03 15:58:02 +08:00
|
|
|
|
)}
|
2025-08-30 22:08:41 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="name"
|
|
|
|
|
|
className="block text-sm font-medium text-[var(--color-text-primary)]"
|
|
|
|
|
|
>
|
|
|
|
|
|
供应商名称 *
|
|
|
|
|
|
</label>
|
2025-08-21 22:20:57 +08:00
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
2025-08-26 15:12:27 +08:00
|
|
|
|
id="name"
|
|
|
|
|
|
name="name"
|
|
|
|
|
|
value={formData.name}
|
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
|
placeholder="例如:Anthropic 官方"
|
|
|
|
|
|
required
|
2025-08-21 22:20:57 +08:00
|
|
|
|
autoComplete="off"
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className="w-full px-3 py-2 border border-[var(--color-border)] rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)] transition-colors"
|
2025-08-21 22:20:57 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
2025-09-06 16:21:21 +08:00
|
|
|
|
{!isCodex && showApiKey && (
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="apiKey"
|
|
|
|
|
|
className="block text-sm font-medium text-[var(--color-text-primary)]"
|
|
|
|
|
|
>
|
|
|
|
|
|
API Key *
|
|
|
|
|
|
</label>
|
2025-08-30 21:54:11 +08:00
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
id="apiKey"
|
|
|
|
|
|
value={apiKey}
|
|
|
|
|
|
onChange={(e) => handleApiKeyChange(e.target.value)}
|
|
|
|
|
|
placeholder={
|
|
|
|
|
|
isOfficialPreset
|
|
|
|
|
|
? "官方登录无需填写 API Key,直接保存即可"
|
|
|
|
|
|
: "只需要填这里,下方配置会自动填充"
|
|
|
|
|
|
}
|
|
|
|
|
|
disabled={isOfficialPreset}
|
|
|
|
|
|
autoComplete="off"
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className={`w-full px-3 py-2 border rounded-lg text-sm transition-colors ${
|
2025-08-30 21:54:11 +08:00
|
|
|
|
isOfficialPreset
|
2025-09-06 16:21:21 +08:00
|
|
|
|
? "bg-[var(--color-bg-tertiary)] border-[var(--color-border)] text-[var(--color-text-tertiary)] cursor-not-allowed"
|
|
|
|
|
|
: "border-[var(--color-border)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)]"
|
|
|
|
|
|
}`}
|
2025-08-30 21:54:11 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-08-26 15:12:27 +08:00
|
|
|
|
|
2025-09-06 16:21:21 +08:00
|
|
|
|
{isCodex && showCodexApiKey && (
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="codexApiKey"
|
|
|
|
|
|
className="block text-sm font-medium text-[var(--color-text-primary)]"
|
|
|
|
|
|
>
|
|
|
|
|
|
API Key *
|
|
|
|
|
|
</label>
|
2025-08-30 22:08:41 +08:00
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
id="codexApiKey"
|
|
|
|
|
|
value={codexApiKey}
|
|
|
|
|
|
onChange={(e) => handleCodexApiKeyChange(e.target.value)}
|
|
|
|
|
|
placeholder={
|
|
|
|
|
|
isCodexOfficialPreset
|
|
|
|
|
|
? "官方无需填写 API Key,直接保存即可"
|
2025-08-30 23:02:49 +08:00
|
|
|
|
: "只需要填这里,下方 auth.json 会自动填充"
|
2025-08-30 22:08:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
disabled={isCodexOfficialPreset}
|
2025-08-31 18:14:31 +08:00
|
|
|
|
required={
|
2025-09-05 21:26:01 +08:00
|
|
|
|
selectedCodexPreset !== null &&
|
|
|
|
|
|
selectedCodexPreset >= 0 &&
|
|
|
|
|
|
!isCodexOfficialPreset
|
2025-08-31 18:14:31 +08:00
|
|
|
|
}
|
2025-08-30 22:08:41 +08:00
|
|
|
|
autoComplete="off"
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className={`w-full px-3 py-2 border rounded-lg text-sm transition-colors ${
|
2025-08-30 22:08:41 +08:00
|
|
|
|
isCodexOfficialPreset
|
2025-09-06 16:21:21 +08:00
|
|
|
|
? "bg-[var(--color-bg-tertiary)] border-[var(--color-border)] text-[var(--color-text-tertiary)] cursor-not-allowed"
|
|
|
|
|
|
: "border-[var(--color-border)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)]"
|
|
|
|
|
|
}`}
|
2025-08-30 22:08:41 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="websiteUrl"
|
|
|
|
|
|
className="block text-sm font-medium text-[var(--color-text-primary)]"
|
|
|
|
|
|
>
|
|
|
|
|
|
官网地址
|
|
|
|
|
|
</label>
|
2025-08-26 15:12:27 +08:00
|
|
|
|
<input
|
|
|
|
|
|
type="url"
|
|
|
|
|
|
id="websiteUrl"
|
|
|
|
|
|
name="websiteUrl"
|
|
|
|
|
|
value={formData.websiteUrl}
|
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
|
placeholder="https://example.com(可选)"
|
|
|
|
|
|
autoComplete="off"
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className="w-full px-3 py-2 border border-[var(--color-border)] rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)] transition-colors"
|
2025-08-26 15:12:27 +08:00
|
|
|
|
/>
|
2025-08-08 15:03:38 +08:00
|
|
|
|
</div>
|
2025-08-26 15:12:27 +08:00
|
|
|
|
|
2025-08-30 21:54:11 +08:00
|
|
|
|
{/* Claude 或 Codex 的配置部分 */}
|
|
|
|
|
|
{isCodex ? (
|
|
|
|
|
|
// Codex: 双编辑器
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="codexAuth"
|
|
|
|
|
|
className="block text-sm font-medium text-[var(--color-text-primary)]"
|
|
|
|
|
|
>
|
|
|
|
|
|
auth.json (JSON) *
|
|
|
|
|
|
</label>
|
2025-08-30 21:54:11 +08:00
|
|
|
|
<textarea
|
|
|
|
|
|
id="codexAuth"
|
|
|
|
|
|
value={codexAuth}
|
2025-08-30 22:08:41 +08:00
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const value = e.target.value;
|
|
|
|
|
|
setCodexAuth(value);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const auth = JSON.parse(value || "{}");
|
2025-08-30 23:02:49 +08:00
|
|
|
|
const key =
|
2025-08-31 16:39:38 +08:00
|
|
|
|
typeof auth.OPENAI_API_KEY === "string"
|
|
|
|
|
|
? auth.OPENAI_API_KEY
|
|
|
|
|
|
: "";
|
2025-08-30 22:08:41 +08:00
|
|
|
|
setCodexApiKey(key);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// ignore
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
2025-08-30 21:54:11 +08:00
|
|
|
|
placeholder={`{
|
2025-08-30 23:02:49 +08:00
|
|
|
|
"OPENAI_API_KEY": "sk-your-api-key-here"
|
2025-08-30 21:54:11 +08:00
|
|
|
|
}`}
|
|
|
|
|
|
rows={6}
|
|
|
|
|
|
required
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className="w-full px-3 py-2 border border-[var(--color-border)] rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)] transition-colors resize-y min-h-[8rem]"
|
2025-08-26 15:12:27 +08:00
|
|
|
|
/>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<p className="text-xs text-[var(--color-text-secondary)]">
|
|
|
|
|
|
Codex auth.json 配置内容
|
|
|
|
|
|
</p>
|
2025-08-30 21:54:11 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="codexConfig"
|
|
|
|
|
|
className="block text-sm font-medium text-[var(--color-text-primary)]"
|
|
|
|
|
|
>
|
|
|
|
|
|
config.toml (TOML)
|
|
|
|
|
|
</label>
|
2025-08-30 21:54:11 +08:00
|
|
|
|
<textarea
|
|
|
|
|
|
id="codexConfig"
|
|
|
|
|
|
value={codexConfig}
|
|
|
|
|
|
onChange={(e) => setCodexConfig(e.target.value)}
|
2025-09-06 16:21:21 +08:00
|
|
|
|
placeholder=""
|
2025-08-30 21:54:11 +08:00
|
|
|
|
rows={8}
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className="w-full px-3 py-2 border border-[var(--color-border)] rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)] transition-colors resize-y min-h-[10rem]"
|
2025-08-30 21:54:11 +08:00
|
|
|
|
/>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<p className="text-xs text-[var(--color-text-secondary)]">
|
2025-09-03 15:58:02 +08:00
|
|
|
|
Codex config.toml 配置内容
|
2025-09-06 16:21:21 +08:00
|
|
|
|
</p>
|
2025-08-30 21:54:11 +08:00
|
|
|
|
</div>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
</div>
|
2025-08-30 21:54:11 +08:00
|
|
|
|
) : (
|
|
|
|
|
|
// Claude: 原有的单编辑器
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<label
|
|
|
|
|
|
htmlFor="settingsConfig"
|
|
|
|
|
|
className="block text-sm font-medium text-[var(--color-text-primary)]"
|
|
|
|
|
|
>
|
2025-08-30 21:54:11 +08:00
|
|
|
|
Claude Code 配置 (JSON) *
|
|
|
|
|
|
</label>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<label className="inline-flex items-center gap-2 text-sm text-[var(--color-text-secondary)] cursor-pointer">
|
2025-08-30 21:54:11 +08:00
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={disableCoAuthored}
|
|
|
|
|
|
onChange={(e) => handleCoAuthoredToggle(e.target.checked)}
|
2025-09-06 16:21:21 +08:00
|
|
|
|
className="w-4 h-4 text-[var(--color-primary)] bg-white border-[var(--color-border)] rounded focus:ring-[var(--color-primary)] focus:ring-2"
|
2025-08-30 21:54:11 +08:00
|
|
|
|
/>
|
|
|
|
|
|
禁止 Claude Code 签名
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
2025-09-06 09:30:09 +08:00
|
|
|
|
<JsonEditor
|
2025-08-30 21:54:11 +08:00
|
|
|
|
value={formData.settingsConfig}
|
2025-09-06 16:21:21 +08:00
|
|
|
|
onChange={(value) =>
|
|
|
|
|
|
handleChange({
|
|
|
|
|
|
target: { name: "settingsConfig", value },
|
|
|
|
|
|
} as React.ChangeEvent<HTMLTextAreaElement>)
|
|
|
|
|
|
}
|
2025-08-30 21:54:11 +08:00
|
|
|
|
placeholder={`{
|
2025-08-08 15:03:38 +08:00
|
|
|
|
"env": {
|
|
|
|
|
|
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
|
|
|
|
|
|
"ANTHROPIC_AUTH_TOKEN": "sk-your-api-key-here"
|
|
|
|
|
|
}
|
|
|
|
|
|
}`}
|
2025-08-30 21:54:11 +08:00
|
|
|
|
rows={12}
|
|
|
|
|
|
/>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<p className="text-xs text-[var(--color-text-secondary)]">
|
2025-08-30 21:54:11 +08:00
|
|
|
|
完整的 Claude Code settings.json 配置内容
|
2025-09-06 16:21:21 +08:00
|
|
|
|
</p>
|
2025-08-30 21:54:11 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-08-08 15:03:38 +08:00
|
|
|
|
</div>
|
2025-08-26 12:34:47 +08:00
|
|
|
|
|
2025-09-06 16:21:21 +08:00
|
|
|
|
{/* Footer */}
|
|
|
|
|
|
<div className="flex items-center justify-end gap-3 p-6 border-t border-[var(--color-border)] bg-[var(--color-bg-tertiary)]">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
|
className="px-4 py-2 text-sm font-medium text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-white rounded-lg transition-colors"
|
|
|
|
|
|
>
|
2025-08-26 15:12:27 +08:00
|
|
|
|
取消
|
|
|
|
|
|
</button>
|
2025-09-06 16:21:21 +08:00
|
|
|
|
<button
|
|
|
|
|
|
type="submit"
|
|
|
|
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-hover)] transition-colors text-sm font-medium"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Save size={16} />
|
2025-08-26 15:12:27 +08:00
|
|
|
|
{submitText}
|
|
|
|
|
|
</button>
|
2025-08-26 12:34:47 +08:00
|
|
|
|
</div>
|
2025-08-08 15:03:38 +08:00
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-08-21 22:20:57 +08:00
|
|
|
|
);
|
|
|
|
|
|
};
|
2025-08-08 15:03:38 +08:00
|
|
|
|
|
2025-08-21 22:20:57 +08:00
|
|
|
|
export default ProviderForm;
|