mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 00:35:32 +08:00
6692343d1e
Replace unsupported Anthropic image blocks with an [Unsupported Image] marker when routed models are text-only or upstream rejects image input. Add rectifier settings for media fallback and heuristic model detection, wire the controls into the settings UI, and cover the sanitizer and forwarder gates with regression tests.
238 lines
8.3 KiB
TypeScript
238 lines
8.3 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { toast } from "sonner";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Label } from "@/components/ui/label";
|
|
import {
|
|
settingsApi,
|
|
type RectifierConfig,
|
|
type OptimizerConfig,
|
|
} from "@/lib/api/settings";
|
|
|
|
export function RectifierConfigPanel() {
|
|
const { t } = useTranslation();
|
|
const [config, setConfig] = useState<RectifierConfig>({
|
|
enabled: true,
|
|
requestThinkingSignature: true,
|
|
requestThinkingBudget: true,
|
|
requestMediaFallback: true,
|
|
requestMediaHeuristic: true,
|
|
});
|
|
const [optimizerConfig, setOptimizerConfig] = useState<OptimizerConfig>({
|
|
enabled: false,
|
|
thinkingOptimizer: true,
|
|
cacheInjection: true,
|
|
cacheTtl: "1h",
|
|
});
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
settingsApi
|
|
.getRectifierConfig()
|
|
.then(setConfig)
|
|
.catch((e) => console.error("Failed to load rectifier config:", e))
|
|
.finally(() => setIsLoading(false));
|
|
settingsApi
|
|
.getOptimizerConfig()
|
|
.then(setOptimizerConfig)
|
|
.catch((e) => console.error("Failed to load optimizer config:", e));
|
|
}, []);
|
|
|
|
const handleChange = async (updates: Partial<RectifierConfig>) => {
|
|
const newConfig = { ...config, ...updates };
|
|
setConfig(newConfig);
|
|
try {
|
|
await settingsApi.setRectifierConfig(newConfig);
|
|
} catch (e) {
|
|
console.error("Failed to save rectifier config:", e);
|
|
toast.error(String(e));
|
|
setConfig(config);
|
|
}
|
|
};
|
|
|
|
const handleOptimizerChange = async (updates: Partial<OptimizerConfig>) => {
|
|
const newConfig = { ...optimizerConfig, ...updates };
|
|
setOptimizerConfig(newConfig);
|
|
try {
|
|
await settingsApi.setOptimizerConfig(newConfig);
|
|
} catch (e) {
|
|
console.error("Failed to save optimizer config:", e);
|
|
toast.error(String(e));
|
|
setOptimizerConfig(optimizerConfig);
|
|
}
|
|
};
|
|
|
|
if (isLoading) return null;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label>{t("settings.advanced.rectifier.enabled")}</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t("settings.advanced.rectifier.enabledDescription")}
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={config.enabled}
|
|
onCheckedChange={(checked) => handleChange({ enabled: checked })}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<h4 className="text-sm font-medium text-muted-foreground">
|
|
{t("settings.advanced.rectifier.requestGroup")}
|
|
</h4>
|
|
<div className="flex items-center justify-between pl-4">
|
|
<div className="space-y-0.5">
|
|
<Label>{t("settings.advanced.rectifier.thinkingSignature")}</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t("settings.advanced.rectifier.thinkingSignatureDescription")}
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={config.requestThinkingSignature}
|
|
disabled={!config.enabled}
|
|
onCheckedChange={(checked) =>
|
|
handleChange({ requestThinkingSignature: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center justify-between pl-4">
|
|
<div className="space-y-0.5">
|
|
<Label>{t("settings.advanced.rectifier.thinkingBudget")}</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t("settings.advanced.rectifier.thinkingBudgetDescription")}
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={config.requestThinkingBudget}
|
|
disabled={!config.enabled}
|
|
onCheckedChange={(checked) =>
|
|
handleChange({ requestThinkingBudget: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center justify-between pl-4">
|
|
<div className="space-y-0.5">
|
|
<Label>{t("settings.advanced.rectifier.mediaFallback")}</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t("settings.advanced.rectifier.mediaFallbackDescription")}
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={config.requestMediaFallback}
|
|
disabled={!config.enabled}
|
|
onCheckedChange={(checked) =>
|
|
handleChange({ requestMediaFallback: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center justify-between pl-8">
|
|
<div className="space-y-0.5">
|
|
<Label>{t("settings.advanced.rectifier.mediaHeuristic")}</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t("settings.advanced.rectifier.mediaHeuristicDescription")}
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={config.requestMediaHeuristic}
|
|
disabled={!config.enabled || !config.requestMediaFallback}
|
|
onCheckedChange={(checked) =>
|
|
handleChange({ requestMediaHeuristic: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-t pt-6 mt-6">
|
|
<div className="space-y-1 mb-4">
|
|
<h3 className="text-sm font-medium">
|
|
{t("settings.advanced.optimizer.title")}
|
|
</h3>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t("settings.advanced.optimizer.description")}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label>{t("settings.advanced.optimizer.enabled")}</Label>
|
|
</div>
|
|
<Switch
|
|
checked={optimizerConfig.enabled}
|
|
onCheckedChange={(checked) =>
|
|
handleOptimizerChange({ enabled: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-4 pl-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label>
|
|
{t("settings.advanced.optimizer.thinkingOptimizer")}
|
|
</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t(
|
|
"settings.advanced.optimizer.thinkingOptimizerDescription",
|
|
)}
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={optimizerConfig.thinkingOptimizer}
|
|
disabled={!optimizerConfig.enabled}
|
|
onCheckedChange={(checked) =>
|
|
handleOptimizerChange({ thinkingOptimizer: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label>{t("settings.advanced.optimizer.cacheInjection")}</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t("settings.advanced.optimizer.cacheInjectionDescription")}
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={optimizerConfig.cacheInjection}
|
|
disabled={!optimizerConfig.enabled}
|
|
onCheckedChange={(checked) =>
|
|
handleOptimizerChange({ cacheInjection: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{optimizerConfig.cacheInjection && (
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label>{t("settings.advanced.optimizer.cacheTtl")}</Label>
|
|
</div>
|
|
<select
|
|
className="h-9 rounded-md border border-input bg-background px-3 text-sm"
|
|
value={optimizerConfig.cacheTtl}
|
|
disabled={
|
|
!optimizerConfig.enabled || !optimizerConfig.cacheInjection
|
|
}
|
|
onChange={(e) =>
|
|
handleOptimizerChange({ cacheTtl: e.target.value })
|
|
}
|
|
>
|
|
<option value="5m">
|
|
{t("settings.advanced.optimizer.cacheTtl5m")}
|
|
</option>
|
|
<option value="1h">
|
|
{t("settings.advanced.optimizer.cacheTtl1h")}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|