Files
CC-Switch/src/components/settings/RectifierConfigPanel.tsx
T
Jason 6692343d1e Add media fallback rectifier for text-only models
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.
2026-06-04 11:40:16 +08:00

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>
);
}