style: format codebase with Prettier\n\n- Apply Prettier across src to ensure consistent styling\n- No functional changes; whitespace and ordering only\n- Unblocks format:check for release pipeline
This commit is contained in:
@@ -116,7 +116,6 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 生成唯一ID
|
||||
const generateId = () => {
|
||||
return crypto.randomUUID();
|
||||
@@ -204,7 +203,6 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-gray-50 dark:bg-gray-950">
|
||||
{/* Linear 风格的顶部导航 */}
|
||||
@@ -270,7 +268,6 @@ function App() {
|
||||
onDelete={handleDeleteProvider}
|
||||
onEdit={setEditingProviderId}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -96,10 +96,15 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
const [kimiAnthropicModel, setKimiAnthropicModel] = useState("");
|
||||
const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] =
|
||||
useState("");
|
||||
|
||||
|
||||
// 初始化自定义模式的默认配置
|
||||
useEffect(() => {
|
||||
if (showPresets && selectedPreset === -1 && !initialData && formData.settingsConfig === "") {
|
||||
if (
|
||||
showPresets &&
|
||||
selectedPreset === -1 &&
|
||||
!initialData &&
|
||||
formData.settingsConfig === ""
|
||||
) {
|
||||
// 设置自定义模板
|
||||
const customTemplate = {
|
||||
env: {
|
||||
@@ -108,10 +113,10 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
// 可选配置
|
||||
// ANTHROPIC_MODEL: "your-model-name",
|
||||
// ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name"
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
setFormData(prev => ({
|
||||
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
settingsConfig: JSON.stringify(customTemplate, null, 2),
|
||||
}));
|
||||
@@ -138,7 +143,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
setClaudeModel(config.env.ANTHROPIC_MODEL || "");
|
||||
setClaudeSmallFastModel(config.env.ANTHROPIC_SMALL_FAST_MODEL || "");
|
||||
setBaseUrl(config.env.ANTHROPIC_BASE_URL || ""); // 初始化基础 URL
|
||||
|
||||
|
||||
// 初始化 Kimi 模型选择
|
||||
setKimiAnthropicModel(config.env.ANTHROPIC_MODEL || "");
|
||||
setKimiAnthropicSmallFastModel(
|
||||
@@ -155,14 +160,18 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
if (!isCodex) {
|
||||
if (selectedPreset !== null && selectedPreset >= 0) {
|
||||
const preset = providerPresets[selectedPreset];
|
||||
setCategory(preset?.category || (preset?.isOfficial ? "official" : undefined));
|
||||
setCategory(
|
||||
preset?.category || (preset?.isOfficial ? "official" : undefined),
|
||||
);
|
||||
} else if (selectedPreset === -1) {
|
||||
setCategory("custom");
|
||||
}
|
||||
} else {
|
||||
if (selectedCodexPreset !== null && selectedCodexPreset >= 0) {
|
||||
const preset = codexProviderPresets[selectedCodexPreset];
|
||||
setCategory(preset?.category || (preset?.isOfficial ? "official" : undefined));
|
||||
setCategory(
|
||||
preset?.category || (preset?.isOfficial ? "official" : undefined),
|
||||
);
|
||||
} else if (selectedCodexPreset === -1) {
|
||||
setCategory("custom");
|
||||
}
|
||||
@@ -288,7 +297,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
websiteUrl: preset.websiteUrl,
|
||||
settingsConfig: configString,
|
||||
});
|
||||
setCategory(preset.category || (preset.isOfficial ? "official" : undefined));
|
||||
setCategory(
|
||||
preset.category || (preset.isOfficial ? "official" : undefined),
|
||||
);
|
||||
|
||||
// 设置选中的预设
|
||||
setSelectedPreset(index);
|
||||
@@ -307,11 +318,13 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
if (config.env) {
|
||||
setClaudeModel(config.env.ANTHROPIC_MODEL || "");
|
||||
setClaudeSmallFastModel(config.env.ANTHROPIC_SMALL_FAST_MODEL || "");
|
||||
|
||||
|
||||
// 如果是 Kimi 预设,同步 Kimi 模型选择
|
||||
if (preset.name?.includes("Kimi")) {
|
||||
setKimiAnthropicModel(config.env.ANTHROPIC_MODEL || "");
|
||||
setKimiAnthropicSmallFastModel(config.env.ANTHROPIC_SMALL_FAST_MODEL || "");
|
||||
setKimiAnthropicSmallFastModel(
|
||||
config.env.ANTHROPIC_SMALL_FAST_MODEL || "",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setClaudeModel("");
|
||||
@@ -323,7 +336,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
// 处理点击自定义按钮
|
||||
const handleCustomClick = () => {
|
||||
setSelectedPreset(-1);
|
||||
|
||||
|
||||
// 设置自定义模板
|
||||
const customTemplate = {
|
||||
env: {
|
||||
@@ -332,9 +345,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
// 可选配置
|
||||
// ANTHROPIC_MODEL: "your-model-name",
|
||||
// ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name"
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
setFormData({
|
||||
name: "",
|
||||
websiteUrl: "",
|
||||
@@ -366,7 +379,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
}));
|
||||
|
||||
setSelectedCodexPreset(index);
|
||||
setCategory(preset.category || (preset.isOfficial ? "official" : undefined));
|
||||
setCategory(
|
||||
preset.category || (preset.isOfficial ? "official" : undefined),
|
||||
);
|
||||
|
||||
// 清空 API Key,让用户重新输入
|
||||
setCodexApiKey("");
|
||||
@@ -410,17 +425,17 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
// 处理基础 URL 变化
|
||||
const handleBaseUrlChange = (url: string) => {
|
||||
setBaseUrl(url);
|
||||
|
||||
|
||||
try {
|
||||
const config = JSON.parse(formData.settingsConfig || "{}");
|
||||
if (!config.env) {
|
||||
config.env = {};
|
||||
}
|
||||
config.env.ANTHROPIC_BASE_URL = url.trim();
|
||||
|
||||
setFormData(prev => ({
|
||||
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
settingsConfig: JSON.stringify(config, null, 2)
|
||||
settingsConfig: JSON.stringify(config, null, 2),
|
||||
}));
|
||||
} catch {
|
||||
// ignore
|
||||
@@ -442,7 +457,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
// 根据当前配置决定是否展示 API Key 输入框
|
||||
// 自定义模式(-1)也需要显示 API Key 输入框
|
||||
const showApiKey =
|
||||
(selectedPreset !== null) ||
|
||||
selectedPreset !== null ||
|
||||
(!showPresets && hasApiKeyField(formData.settingsConfig));
|
||||
|
||||
// 判断当前选中的预设是否是官方
|
||||
@@ -474,13 +489,18 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
const showBaseUrlInput = selectedPreset === -1 && !isCodex;
|
||||
|
||||
// 判断是否显示"获取 API Key"链接(国产官方、聚合站和第三方显示)
|
||||
const shouldShowApiKeyLink = !isCodex && !isOfficialPreset &&
|
||||
(category === "cn_official" || category === "aggregator" || category === "third_party" ||
|
||||
(selectedPreset !== null && selectedPreset >= 0 &&
|
||||
(providerPresets[selectedPreset]?.category === "cn_official" ||
|
||||
providerPresets[selectedPreset]?.category === "aggregator" ||
|
||||
providerPresets[selectedPreset]?.category === "third_party")));
|
||||
|
||||
const shouldShowApiKeyLink =
|
||||
!isCodex &&
|
||||
!isOfficialPreset &&
|
||||
(category === "cn_official" ||
|
||||
category === "aggregator" ||
|
||||
category === "third_party" ||
|
||||
(selectedPreset !== null &&
|
||||
selectedPreset >= 0 &&
|
||||
(providerPresets[selectedPreset]?.category === "cn_official" ||
|
||||
providerPresets[selectedPreset]?.category === "aggregator" ||
|
||||
providerPresets[selectedPreset]?.category === "third_party")));
|
||||
|
||||
// 获取当前供应商的网址
|
||||
const getCurrentWebsiteUrl = () => {
|
||||
if (selectedPreset !== null && selectedPreset >= 0) {
|
||||
@@ -522,16 +542,27 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
category === "official";
|
||||
|
||||
// 判断是否显示 Codex 的"获取 API Key"链接
|
||||
const shouldShowCodexApiKeyLink = isCodex && !isCodexOfficialPreset &&
|
||||
(category === "cn_official" || category === "aggregator" || category === "third_party" ||
|
||||
(selectedCodexPreset !== null && selectedCodexPreset >= 0 &&
|
||||
(codexProviderPresets[selectedCodexPreset]?.category === "cn_official" ||
|
||||
codexProviderPresets[selectedCodexPreset]?.category === "aggregator" ||
|
||||
codexProviderPresets[selectedCodexPreset]?.category === "third_party")));
|
||||
const shouldShowCodexApiKeyLink =
|
||||
isCodex &&
|
||||
!isCodexOfficialPreset &&
|
||||
(category === "cn_official" ||
|
||||
category === "aggregator" ||
|
||||
category === "third_party" ||
|
||||
(selectedCodexPreset !== null &&
|
||||
selectedCodexPreset >= 0 &&
|
||||
(codexProviderPresets[selectedCodexPreset]?.category ===
|
||||
"cn_official" ||
|
||||
codexProviderPresets[selectedCodexPreset]?.category ===
|
||||
"aggregator" ||
|
||||
codexProviderPresets[selectedCodexPreset]?.category ===
|
||||
"third_party")));
|
||||
|
||||
// 处理模型输入变化,自动更新 JSON 配置
|
||||
const handleModelChange = (field: 'ANTHROPIC_MODEL' | 'ANTHROPIC_SMALL_FAST_MODEL', value: string) => {
|
||||
if (field === 'ANTHROPIC_MODEL') {
|
||||
const handleModelChange = (
|
||||
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||
value: string,
|
||||
) => {
|
||||
if (field === "ANTHROPIC_MODEL") {
|
||||
setClaudeModel(value);
|
||||
} else {
|
||||
setClaudeSmallFastModel(value);
|
||||
@@ -539,16 +570,18 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
|
||||
// 更新 JSON 配置
|
||||
try {
|
||||
const currentConfig = formData.settingsConfig ? JSON.parse(formData.settingsConfig) : { env: {} };
|
||||
const currentConfig = formData.settingsConfig
|
||||
? JSON.parse(formData.settingsConfig)
|
||||
: { env: {} };
|
||||
if (!currentConfig.env) currentConfig.env = {};
|
||||
|
||||
|
||||
if (value.trim()) {
|
||||
currentConfig.env[field] = value.trim();
|
||||
} else {
|
||||
delete currentConfig.env[field];
|
||||
}
|
||||
|
||||
setFormData(prev => ({
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
settingsConfig: JSON.stringify(currentConfig, null, 2),
|
||||
}));
|
||||
@@ -619,9 +652,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
<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-gray-200">
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
{title}
|
||||
</h2>
|
||||
<h2 className="text-xl font-semibold text-gray-900">{title}</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
@@ -636,13 +667,8 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
<div className="flex-1 overflow-auto p-6 space-y-6">
|
||||
{error && (
|
||||
<div className="flex items-center gap-3 p-4 bg-red-100 border border-red-500/20 rounded-lg">
|
||||
<AlertCircle
|
||||
size={20}
|
||||
className="text-red-500 flex-shrink-0"
|
||||
/>
|
||||
<p className="text-red-500 text-sm font-medium">
|
||||
{error}
|
||||
</p>
|
||||
<AlertCircle size={20} className="text-red-500 flex-shrink-0" />
|
||||
<p className="text-red-500 text-sm font-medium">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -844,13 +870,15 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
type="text"
|
||||
id="anthropicModel"
|
||||
value={claudeModel}
|
||||
onChange={(e) => handleModelChange('ANTHROPIC_MODEL', e.target.value)}
|
||||
onChange={(e) =>
|
||||
handleModelChange("ANTHROPIC_MODEL", e.target.value)
|
||||
}
|
||||
placeholder="例如: GLM-4.5"
|
||||
autoComplete="off"
|
||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="anthropicSmallFastModel"
|
||||
@@ -862,14 +890,19 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
type="text"
|
||||
id="anthropicSmallFastModel"
|
||||
value={claudeSmallFastModel}
|
||||
onChange={(e) => handleModelChange('ANTHROPIC_SMALL_FAST_MODEL', e.target.value)}
|
||||
onChange={(e) =>
|
||||
handleModelChange(
|
||||
"ANTHROPIC_SMALL_FAST_MODEL",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
placeholder="例如: GLM-4.5-Air"
|
||||
autoComplete="off"
|
||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="p-3 bg-amber-50 border border-amber-200 rounded-lg">
|
||||
<p className="text-xs text-amber-600">
|
||||
💡 留空将使用供应商的默认模型
|
||||
@@ -877,7 +910,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<ClaudeConfigEditor
|
||||
value={formData.settingsConfig}
|
||||
onChange={(value) =>
|
||||
|
||||
@@ -34,10 +34,7 @@ const ApiKeyInput: React.FC<ApiKeyInputProps> = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="block text-sm font-medium text-gray-900"
|
||||
>
|
||||
<label htmlFor={id} className="block text-sm font-medium text-gray-900">
|
||||
{label} {required && "*"}
|
||||
</label>
|
||||
<div className="relative">
|
||||
|
||||
@@ -36,9 +36,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors resize-y min-h-[8rem]"
|
||||
/>
|
||||
<p className="text-xs text-gray-500">
|
||||
Codex auth.json 配置内容
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">Codex auth.json 配置内容</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -56,9 +54,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
||||
rows={8}
|
||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors resize-y min-h-[10rem]"
|
||||
/>
|
||||
<p className="text-xs text-gray-500">
|
||||
Codex config.toml 配置内容
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">Codex config.toml 配置内容</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -98,9 +98,7 @@ const KimiModelSelector: React.FC<KimiModelSelectorProps> = ({
|
||||
onChange: (value: string) => void;
|
||||
}> = ({ label, value, onChange }) => (
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-900">
|
||||
{label}
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-gray-900">{label}</label>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={value}
|
||||
@@ -132,9 +130,7 @@ const KimiModelSelector: React.FC<KimiModelSelectorProps> = ({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium text-gray-900">
|
||||
模型配置
|
||||
</h3>
|
||||
<h3 className="text-sm font-medium text-gray-900">模型配置</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => debouncedKey && fetchModelsWithKey(debouncedKey)}
|
||||
@@ -148,10 +144,7 @@ const KimiModelSelector: React.FC<KimiModelSelectorProps> = ({
|
||||
|
||||
{error && (
|
||||
<div className="flex items-center gap-2 p-3 bg-red-100 border border-red-500/20 rounded-lg">
|
||||
<AlertCircle
|
||||
size={16}
|
||||
className="text-red-500 flex-shrink-0"
|
||||
/>
|
||||
<AlertCircle size={16} className="text-red-500 flex-shrink-0" />
|
||||
<p className="text-red-500 text-xs">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -25,7 +25,11 @@ const PresetSelector: React.FC<PresetSelectorProps> = ({
|
||||
onCustomClick,
|
||||
customLabel = "自定义",
|
||||
}) => {
|
||||
const getButtonClass = (index: number, isOfficial?: boolean, category?: ProviderCategory) => {
|
||||
const getButtonClass = (
|
||||
index: number,
|
||||
isOfficial?: boolean,
|
||||
category?: ProviderCategory,
|
||||
) => {
|
||||
const isSelected = selectedIndex === index;
|
||||
const baseClass =
|
||||
"inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors";
|
||||
@@ -63,7 +67,7 @@ const PresetSelector: React.FC<PresetSelectorProps> = ({
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className={`${getButtonClass(-1)} ${selectedIndex === -1 ? '' : ''}`}
|
||||
className={`${getButtonClass(-1)} ${selectedIndex === -1 ? "" : ""}`}
|
||||
onClick={onCustomClick}
|
||||
>
|
||||
{customLabel}
|
||||
@@ -72,19 +76,23 @@ const PresetSelector: React.FC<PresetSelectorProps> = ({
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
className={getButtonClass(index, preset.isOfficial, preset.category)}
|
||||
className={getButtonClass(
|
||||
index,
|
||||
preset.isOfficial,
|
||||
preset.category,
|
||||
)}
|
||||
onClick={() => onSelectPreset(index)}
|
||||
>
|
||||
{(preset.isOfficial || preset.category === "official") && <Zap size={14} />}
|
||||
{(preset.isOfficial || preset.category === "official") && (
|
||||
<Zap size={14} />
|
||||
)}
|
||||
{preset.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{getDescription() && (
|
||||
<p className="text-sm text-gray-500">
|
||||
{getDescription()}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">{getDescription()}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -53,16 +53,16 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
// 有时间戳的按时间升序排列
|
||||
const timeA = a.createdAt || 0;
|
||||
const timeB = b.createdAt || 0;
|
||||
|
||||
|
||||
// 如果都没有时间戳,按名称排序
|
||||
if (timeA === 0 && timeB === 0) {
|
||||
return a.name.localeCompare(b.name, 'zh-CN');
|
||||
return a.name.localeCompare(b.name, "zh-CN");
|
||||
}
|
||||
|
||||
|
||||
// 如果只有一个没有时间戳,没有时间戳的排在前面
|
||||
if (timeA === 0) return -1;
|
||||
if (timeB === 0) return 1;
|
||||
|
||||
|
||||
// 都有时间戳,按时间升序
|
||||
return timeA - timeB;
|
||||
});
|
||||
@@ -91,7 +91,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
<div
|
||||
key={provider.id}
|
||||
className={cn(
|
||||
isCurrent ? cardStyles.selected : cardStyles.interactive
|
||||
isCurrent ? cardStyles.selected : cardStyles.interactive,
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
@@ -140,7 +140,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md transition-colors",
|
||||
isCurrent
|
||||
? "bg-gray-100 text-gray-400 dark:bg-gray-800 dark:text-gray-500 cursor-not-allowed"
|
||||
: "bg-blue-500 text-white hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700"
|
||||
: "bg-blue-500 text-white hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700",
|
||||
)}
|
||||
>
|
||||
<Play size={14} />
|
||||
@@ -162,7 +162,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
||||
buttonStyles.icon,
|
||||
isCurrent
|
||||
? "text-gray-400 cursor-not-allowed"
|
||||
: "text-gray-500 hover:text-red-500 hover:bg-red-100 dark:text-gray-400 dark:hover:text-red-400 dark:hover:bg-red-500/10"
|
||||
: "text-gray-500 hover:text-red-500 hover:bg-red-100 dark:text-gray-400 dark:hover:text-red-400 dark:hover:bg-red-500/10",
|
||||
)}
|
||||
title="删除供应商"
|
||||
>
|
||||
|
||||
@@ -141,7 +141,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
// 如果未知或为空,回退到 releases 首页
|
||||
if (!targetVersion || targetVersion === "未知") {
|
||||
await window.api.openExternal(
|
||||
"https://github.com/farion1231/cc-switch/releases"
|
||||
"https://github.com/farion1231/cc-switch/releases",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
||||
? targetVersion
|
||||
: `v${targetVersion}`;
|
||||
await window.api.openExternal(
|
||||
`https://github.com/farion1231/cc-switch/releases/tag/${tag}`
|
||||
`https://github.com/farion1231/cc-switch/releases/tag/${tag}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("打开更新日志失败:", error);
|
||||
|
||||
@@ -8,12 +8,12 @@ interface UpdateBadgeProps {
|
||||
|
||||
export function UpdateBadge({ className = "", onClick }: UpdateBadgeProps) {
|
||||
const { hasUpdate, updateInfo, isDismissed, dismissUpdate } = useUpdate();
|
||||
|
||||
|
||||
// 如果没有更新或已关闭,不显示
|
||||
if (!hasUpdate || isDismissed || !updateInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
|
||||
@@ -52,7 +52,8 @@ export const providerPresets: ProviderPreset[] = [
|
||||
websiteUrl: "https://bailian.console.aliyun.com",
|
||||
settingsConfig: {
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: "https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
|
||||
ANTHROPIC_BASE_URL:
|
||||
"https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy",
|
||||
ANTHROPIC_AUTH_TOKEN: "",
|
||||
ANTHROPIC_MODEL: "qwen3-coder-plus",
|
||||
ANTHROPIC_SMALL_FAST_MODEL: "qwen3-coder-plus",
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from "react";
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useRef,
|
||||
} from "react";
|
||||
import type { UpdateInfo, UpdateHandle } from "../lib/updater";
|
||||
import { checkForUpdate } from "../lib/updater";
|
||||
|
||||
@@ -9,11 +16,11 @@ interface UpdateContextValue {
|
||||
updateHandle: UpdateHandle | null;
|
||||
isChecking: boolean;
|
||||
error: string | null;
|
||||
|
||||
|
||||
// 提示状态
|
||||
isDismissed: boolean;
|
||||
dismissUpdate: () => void;
|
||||
|
||||
|
||||
// 操作方法
|
||||
checkUpdate: () => Promise<boolean>;
|
||||
resetDismiss: () => void;
|
||||
@@ -31,7 +38,7 @@ export function UpdateProvider({ children }: { children: React.ReactNode }) {
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isDismissed, setIsDismissed] = useState(false);
|
||||
|
||||
|
||||
// 从 localStorage 读取已关闭的版本
|
||||
useEffect(() => {
|
||||
const current = updateInfo?.availableVersion;
|
||||
@@ -50,7 +57,7 @@ export function UpdateProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
setIsDismissed(dismissedVersion === current);
|
||||
}, [updateInfo?.availableVersion]);
|
||||
|
||||
|
||||
const isCheckingRef = useRef(false);
|
||||
|
||||
const checkUpdate = useCallback(async () => {
|
||||
@@ -96,7 +103,7 @@ export function UpdateProvider({ children }: { children: React.ReactNode }) {
|
||||
isCheckingRef.current = false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
const dismissUpdate = useCallback(() => {
|
||||
setIsDismissed(true);
|
||||
if (updateInfo?.availableVersion) {
|
||||
@@ -105,13 +112,13 @@ export function UpdateProvider({ children }: { children: React.ReactNode }) {
|
||||
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
||||
}
|
||||
}, [updateInfo?.availableVersion]);
|
||||
|
||||
|
||||
const resetDismiss = useCallback(() => {
|
||||
setIsDismissed(false);
|
||||
localStorage.removeItem(DISMISSED_VERSION_KEY);
|
||||
localStorage.removeItem(LEGACY_DISMISSED_KEY);
|
||||
}, []);
|
||||
|
||||
|
||||
// 应用启动时自动检查更新
|
||||
useEffect(() => {
|
||||
// 延迟1秒后检查,避免影响启动体验
|
||||
@@ -121,7 +128,7 @@ export function UpdateProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [checkUpdate]);
|
||||
|
||||
|
||||
const value: UpdateContextValue = {
|
||||
hasUpdate,
|
||||
updateInfo,
|
||||
@@ -133,11 +140,9 @@ export function UpdateProvider({ children }: { children: React.ReactNode }) {
|
||||
checkUpdate,
|
||||
resetDismiss,
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<UpdateContext.Provider value={value}>
|
||||
{children}
|
||||
</UpdateContext.Provider>
|
||||
<UpdateContext.Provider value={value}>{children}</UpdateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export function useDarkMode() {
|
||||
// 初始设为 false,挂载后在 useEffect 中加载真实值
|
||||
@@ -7,49 +7,51 @@ export function useDarkMode() {
|
||||
|
||||
// 组件挂载后加载初始值(兼容 Tauri 环境)
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
try {
|
||||
// 尝试读取已保存的偏好
|
||||
const saved = localStorage.getItem('darkMode');
|
||||
const saved = localStorage.getItem("darkMode");
|
||||
if (saved !== null) {
|
||||
const savedBool = saved === 'true';
|
||||
const savedBool = saved === "true";
|
||||
setIsDarkMode(savedBool);
|
||||
console.log('[DarkMode] Loaded from localStorage:', savedBool);
|
||||
console.log("[DarkMode] Loaded from localStorage:", savedBool);
|
||||
} else {
|
||||
// 回退到系统偏好
|
||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const prefersDark =
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
setIsDarkMode(prefersDark);
|
||||
console.log('[DarkMode] Using system preference:', prefersDark);
|
||||
console.log("[DarkMode] Using system preference:", prefersDark);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DarkMode] Error loading preference:', error);
|
||||
console.error("[DarkMode] Error loading preference:", error);
|
||||
setIsDarkMode(false);
|
||||
}
|
||||
|
||||
|
||||
setIsInitialized(true);
|
||||
}, []); // 仅在首次挂载时运行
|
||||
|
||||
// 将 dark 类应用到文档根节点
|
||||
useEffect(() => {
|
||||
if (!isInitialized) return;
|
||||
|
||||
|
||||
// 添加短暂延迟以确保 Tauri 中 DOM 已就绪
|
||||
const timer = setTimeout(() => {
|
||||
try {
|
||||
if (isDarkMode) {
|
||||
document.documentElement.classList.add('dark');
|
||||
console.log('[DarkMode] Added dark class to document');
|
||||
document.documentElement.classList.add("dark");
|
||||
console.log("[DarkMode] Added dark class to document");
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
console.log('[DarkMode] Removed dark class from document');
|
||||
document.documentElement.classList.remove("dark");
|
||||
console.log("[DarkMode] Removed dark class from document");
|
||||
}
|
||||
|
||||
|
||||
// 检查类名是否已成功应用
|
||||
const hasClass = document.documentElement.classList.contains('dark');
|
||||
console.log('[DarkMode] Document has dark class:', hasClass);
|
||||
const hasClass = document.documentElement.classList.contains("dark");
|
||||
console.log("[DarkMode] Document has dark class:", hasClass);
|
||||
} catch (error) {
|
||||
console.error('[DarkMode] Error applying dark class:', error);
|
||||
console.error("[DarkMode] Error applying dark class:", error);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
@@ -59,19 +61,19 @@ export function useDarkMode() {
|
||||
// 将偏好保存到 localStorage
|
||||
useEffect(() => {
|
||||
if (!isInitialized) return;
|
||||
|
||||
|
||||
try {
|
||||
localStorage.setItem('darkMode', isDarkMode.toString());
|
||||
console.log('[DarkMode] Saved to localStorage:', isDarkMode);
|
||||
localStorage.setItem("darkMode", isDarkMode.toString());
|
||||
console.log("[DarkMode] Saved to localStorage:", isDarkMode);
|
||||
} catch (error) {
|
||||
console.error('[DarkMode] Error saving preference:', error);
|
||||
console.error("[DarkMode] Error saving preference:", error);
|
||||
}
|
||||
}, [isDarkMode, isInitialized]);
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
setIsDarkMode(prev => {
|
||||
setIsDarkMode((prev) => {
|
||||
const newValue = !prev;
|
||||
console.log('[DarkMode] Toggling from', prev, 'to', newValue);
|
||||
console.log("[DarkMode] Toggling from", prev, "to", newValue);
|
||||
return newValue;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -21,7 +21,9 @@ body {
|
||||
}
|
||||
|
||||
/* 暗色模式下启用暗色原生控件/滚动条配色 */
|
||||
html.dark { color-scheme: dark; }
|
||||
html.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
|
||||
@@ -5,20 +5,24 @@
|
||||
// 按钮样式
|
||||
export const buttonStyles = {
|
||||
// 主按钮:蓝底白字
|
||||
primary: "px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 transition-colors text-sm font-medium",
|
||||
|
||||
primary:
|
||||
"px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 transition-colors text-sm font-medium",
|
||||
|
||||
// 次按钮:灰背景,深色文本
|
||||
secondary: "px-4 py-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200 rounded-lg transition-colors text-sm font-medium",
|
||||
|
||||
secondary:
|
||||
"px-4 py-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200 rounded-lg transition-colors text-sm font-medium",
|
||||
|
||||
// 危险按钮:用于不可撤销/破坏性操作
|
||||
danger: "px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700 transition-colors text-sm font-medium",
|
||||
|
||||
danger:
|
||||
"px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700 transition-colors text-sm font-medium",
|
||||
|
||||
// 幽灵按钮:无背景,仅悬浮反馈
|
||||
ghost: "px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors text-sm font-medium",
|
||||
|
||||
ghost:
|
||||
"px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors text-sm font-medium",
|
||||
|
||||
// 图标按钮:小尺寸,仅图标
|
||||
icon: "p-1.5 text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors",
|
||||
|
||||
|
||||
// 禁用态:可与其他样式组合
|
||||
disabled: "opacity-50 cursor-not-allowed pointer-events-none",
|
||||
} as const;
|
||||
@@ -27,42 +31,49 @@ export const buttonStyles = {
|
||||
export const cardStyles = {
|
||||
// 基础卡片容器
|
||||
base: "bg-white rounded-lg border border-gray-200 p-4 dark:bg-gray-900 dark:border-gray-700",
|
||||
|
||||
|
||||
// 带悬浮效果的卡片
|
||||
interactive: "bg-white rounded-lg border border-gray-200 p-4 hover:border-gray-300 hover:shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:hover:border-gray-600 transition-all duration-200",
|
||||
|
||||
interactive:
|
||||
"bg-white rounded-lg border border-gray-200 p-4 hover:border-gray-300 hover:shadow-sm dark:bg-gray-900 dark:border-gray-700 dark:hover:border-gray-600 transition-all duration-200",
|
||||
|
||||
// 选中/激活态卡片
|
||||
selected: "bg-white rounded-lg border border-blue-500 ring-1 ring-blue-500/20 bg-blue-500/5 p-4 dark:bg-gray-900 dark:border-blue-400 dark:ring-blue-400/20 dark:bg-blue-400/10",
|
||||
selected:
|
||||
"bg-white rounded-lg border border-blue-500 ring-1 ring-blue-500/20 bg-blue-500/5 p-4 dark:bg-gray-900 dark:border-blue-400 dark:ring-blue-400/20 dark:bg-blue-400/10",
|
||||
} as const;
|
||||
|
||||
// 输入控件样式
|
||||
export const inputStyles = {
|
||||
// 文本输入框
|
||||
text: "w-full px-3 py-2 border border-gray-200 rounded-lg focus:border-blue-500 focus:ring-1 focus:ring-blue-500/20 outline-none dark:bg-gray-900 dark:border-gray-700 dark:text-gray-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20 transition-colors",
|
||||
|
||||
|
||||
// 下拉选择框
|
||||
select: "w-full px-3 py-2 border border-gray-200 rounded-lg focus:border-blue-500 focus:ring-1 focus:ring-blue-500/20 outline-none bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-gray-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20 transition-colors",
|
||||
|
||||
select:
|
||||
"w-full px-3 py-2 border border-gray-200 rounded-lg focus:border-blue-500 focus:ring-1 focus:ring-blue-500/20 outline-none bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-gray-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20 transition-colors",
|
||||
|
||||
// 复选框
|
||||
checkbox: "w-4 h-4 text-blue-500 rounded focus:ring-blue-500/20 border-gray-300 dark:border-gray-600 dark:bg-gray-800",
|
||||
checkbox:
|
||||
"w-4 h-4 text-blue-500 rounded focus:ring-blue-500/20 border-gray-300 dark:border-gray-600 dark:bg-gray-800",
|
||||
} as const;
|
||||
|
||||
// 徽标(Badge)样式
|
||||
export const badgeStyles = {
|
||||
// 成功徽标
|
||||
success: "inline-flex items-center gap-1 px-2 py-1 bg-green-500/10 text-green-500 rounded-md text-xs font-medium",
|
||||
|
||||
success:
|
||||
"inline-flex items-center gap-1 px-2 py-1 bg-green-500/10 text-green-500 rounded-md text-xs font-medium",
|
||||
|
||||
// 信息徽标
|
||||
info: "inline-flex items-center gap-1 px-2 py-1 bg-blue-500/10 text-blue-500 rounded-md text-xs font-medium",
|
||||
|
||||
|
||||
// 警告徽标
|
||||
warning: "inline-flex items-center gap-1 px-2 py-1 bg-amber-500/10 text-amber-500 rounded-md text-xs font-medium",
|
||||
|
||||
warning:
|
||||
"inline-flex items-center gap-1 px-2 py-1 bg-amber-500/10 text-amber-500 rounded-md text-xs font-medium",
|
||||
|
||||
// 错误徽标
|
||||
error: "inline-flex items-center gap-1 px-2 py-1 bg-red-500/10 text-red-500 rounded-md text-xs font-medium",
|
||||
error:
|
||||
"inline-flex items-center gap-1 px-2 py-1 bg-red-500/10 text-red-500 rounded-md text-xs font-medium",
|
||||
} as const;
|
||||
|
||||
// 组合类名的工具函数
|
||||
export function cn(...classes: (string | undefined | false)[]) {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user