feat(settings): add 'Apply to Claude Code extension' toggle

- Apply immediately on save (write or remove primaryApiKey)
- Honor setting on provider switch (enabled: write for non-official, remove for official; disabled: no auto writes)
- Remove per-provider Claude plugin buttons from ProviderList
- Upsert primaryApiKey=any preserving other fields; respect override dir
- Add zh/en i18n for the new setting
This commit is contained in:
Jason
2025-10-10 16:35:21 +08:00
parent 70d8d2cc43
commit c350e64687
8 changed files with 87 additions and 87 deletions

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React from "react";
import { useTranslation } from "react-i18next";
import { Provider } from "../types";
import { Play, Edit3, Trash2, CheckCircle2, Users, Check } from "lucide-react";
@@ -58,55 +58,7 @@ const ProviderList: React.FC<ProviderListProps> = ({
}
};
const [claudeApplied, setClaudeApplied] = useState<boolean>(false);
// 检查 Claude 插件配置是否已应用
useEffect(() => {
const checkClaude = async () => {
if (appType !== "claude" || !currentProviderId) {
setClaudeApplied(false);
return;
}
try {
const applied = await window.api.isClaudePluginApplied();
setClaudeApplied(applied);
} catch (error) {
console.error(t("console.setupListenerFailed"), error);
setClaudeApplied(false);
}
};
checkClaude();
}, [appType, currentProviderId, providers, t]);
const handleApplyToClaudePlugin = async () => {
try {
await window.api.applyClaudePluginConfig({ official: false });
onNotify?.(t("notifications.appliedToClaudePlugin"), "success", 3000);
setClaudeApplied(true);
} catch (error: any) {
console.error(error);
const msg =
error && error.message
? error.message
: t("notifications.syncClaudePluginFailed");
onNotify?.(msg, "error", 5000);
}
};
const handleRemoveFromClaudePlugin = async () => {
try {
await window.api.applyClaudePluginConfig({ official: true });
onNotify?.(t("notifications.removedFromClaudePlugin"), "success", 3000);
setClaudeApplied(false);
} catch (error: any) {
console.error(error);
const msg =
error && error.message
? error.message
: t("notifications.syncClaudePluginFailed");
onNotify?.(msg, "error", 5000);
}
};
// 列表页不再提供 Claude 插件按钮,统一在“设置”中控制
// 对供应商列表进行排序
const sortedProviders = Object.values(providers).sort((a, b) => {
@@ -201,34 +153,6 @@ const ProviderList: React.FC<ProviderListProps> = ({
</div>
<div className="flex items-center gap-2 ml-4">
{appType === "claude" ? (
<div className="flex-shrink-0">
{provider.category !== "official" && isCurrent && (
<button
onClick={() =>
claudeApplied
? handleRemoveFromClaudePlugin()
: handleApplyToClaudePlugin()
}
className={cn(
"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md transition-colors w-full whitespace-nowrap justify-center",
claudeApplied
? "border border-gray-300 text-gray-600 hover:border-red-300 hover:text-red-600 hover:bg-red-50 dark:border-gray-600 dark:text-gray-400 dark:hover:border-red-800 dark:hover:text-red-400 dark:hover:bg-red-900/20"
: "border border-gray-300 text-gray-700 hover:border-green-300 hover:text-green-600 hover:bg-green-50 dark:border-gray-600 dark:text-gray-300 dark:hover:border-green-700 dark:hover:text-green-400 dark:hover:bg-green-900/20",
)}
title={
claudeApplied
? t("provider.removeFromClaudePlugin")
: t("provider.applyToClaudePlugin")
}
>
{claudeApplied
? t("provider.removeFromClaudePlugin")
: t("provider.applyToClaudePlugin")}
</button>
)}
</div>
) : null}
<button
onClick={() => onSwitch(provider.id)}
disabled={isCurrent}

View File

@@ -50,6 +50,7 @@ export default function SettingsModal({
const [settings, setSettings] = useState<Settings>({
showInTray: true,
minimizeToTrayOnClose: true,
enableClaudePluginIntegration: false,
claudeConfigDir: undefined,
codexConfigDir: undefined,
language: persistedLanguage,
@@ -116,6 +117,11 @@ export default function SettingsModal({
setSettings({
showInTray,
minimizeToTrayOnClose,
enableClaudePluginIntegration:
typeof (loadedSettings as any)?.enableClaudePluginIntegration ===
"boolean"
? (loadedSettings as any).enableClaudePluginIntegration
: false,
claudeConfigDir:
typeof (loadedSettings as any)?.claudeConfigDir === "string"
? (loadedSettings as any).claudeConfigDir
@@ -184,6 +190,16 @@ export default function SettingsModal({
language: selectedLanguage,
};
await window.api.saveSettings(payload);
// 立即生效:根据开关无条件写入/移除 ~/.claude/config.json
try {
if (payload.enableClaudePluginIntegration) {
await window.api.applyClaudePluginConfig({ official: false });
} else {
await window.api.applyClaudePluginConfig({ official: true });
}
} catch (e) {
console.warn("[Settings] Apply Claude plugin config on save failed", e);
}
setSettings(payload);
try {
window.localStorage.setItem("language", selectedLanguage);
@@ -506,6 +522,28 @@ export default function SettingsModal({
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500/20"
/>
</label>
{/* Claude 插件联动开关 */}
<label className="flex items-center justify-between">
<div>
<span className="text-sm text-gray-900 dark:text-gray-100">
{t("settings.enableClaudePluginIntegration")}
</span>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1 max-w-[34rem]">
{t("settings.enableClaudePluginIntegrationDescription")}
</p>
</div>
<input
type="checkbox"
checked={!!settings.enableClaudePluginIntegration}
onChange={(e) =>
setSettings((prev) => ({
...prev,
enableClaudePluginIntegration: e.target.checked,
}))
}
className="w-4 h-4 text-blue-500 rounded focus:ring-blue-500/20"
/>
</label>
</div>
</div>