mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-03 11:01:22 +08:00
feat(ui): add format transform configuration in provider form
- Add FormatTransformConfig type definition - Add format transform section in ProviderAdvancedConfig component - Support source/target format selection (Anthropic/OpenAI) - Add transform streaming toggle option
This commit is contained in:
@@ -8,19 +8,33 @@ import {
|
|||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
X,
|
X,
|
||||||
|
ArrowLeftRight,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { ProviderTestConfig, ProviderProxyConfig } from "@/types";
|
import type {
|
||||||
|
ProviderTestConfig,
|
||||||
|
ProviderProxyConfig,
|
||||||
|
FormatTransformConfig,
|
||||||
|
} from "@/types";
|
||||||
|
|
||||||
interface ProviderAdvancedConfigProps {
|
interface ProviderAdvancedConfigProps {
|
||||||
testConfig: ProviderTestConfig;
|
testConfig: ProviderTestConfig;
|
||||||
proxyConfig: ProviderProxyConfig;
|
proxyConfig: ProviderProxyConfig;
|
||||||
|
formatTransform?: FormatTransformConfig;
|
||||||
onTestConfigChange: (config: ProviderTestConfig) => void;
|
onTestConfigChange: (config: ProviderTestConfig) => void;
|
||||||
onProxyConfigChange: (config: ProviderProxyConfig) => void;
|
onProxyConfigChange: (config: ProviderProxyConfig) => void;
|
||||||
|
onFormatTransformChange?: (config: FormatTransformConfig) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 从 ProviderProxyConfig 构建完整 URL */
|
/** 从 ProviderProxyConfig 构建完整 URL */
|
||||||
@@ -71,14 +85,19 @@ function parseProxyUrl(url: string): Partial<ProviderProxyConfig> {
|
|||||||
export function ProviderAdvancedConfig({
|
export function ProviderAdvancedConfig({
|
||||||
testConfig,
|
testConfig,
|
||||||
proxyConfig,
|
proxyConfig,
|
||||||
|
formatTransform,
|
||||||
onTestConfigChange,
|
onTestConfigChange,
|
||||||
onProxyConfigChange,
|
onProxyConfigChange,
|
||||||
|
onFormatTransformChange,
|
||||||
}: ProviderAdvancedConfigProps) {
|
}: ProviderAdvancedConfigProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isTestConfigOpen, setIsTestConfigOpen] = useState(testConfig.enabled);
|
const [isTestConfigOpen, setIsTestConfigOpen] = useState(testConfig.enabled);
|
||||||
const [isProxyConfigOpen, setIsProxyConfigOpen] = useState(
|
const [isProxyConfigOpen, setIsProxyConfigOpen] = useState(
|
||||||
proxyConfig.enabled,
|
proxyConfig.enabled,
|
||||||
);
|
);
|
||||||
|
const [isFormatTransformOpen, setIsFormatTransformOpen] = useState(
|
||||||
|
formatTransform?.enabled ?? false,
|
||||||
|
);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
// 代理 URL 输入状态(仅在初始化时从 proxyConfig 构建)
|
// 代理 URL 输入状态(仅在初始化时从 proxyConfig 构建)
|
||||||
@@ -97,6 +116,11 @@ export function ProviderAdvancedConfig({
|
|||||||
setIsProxyConfigOpen(proxyConfig.enabled);
|
setIsProxyConfigOpen(proxyConfig.enabled);
|
||||||
}, [proxyConfig.enabled]);
|
}, [proxyConfig.enabled]);
|
||||||
|
|
||||||
|
// 同步外部 formatTransform.enabled 变化到展开状态
|
||||||
|
useEffect(() => {
|
||||||
|
setIsFormatTransformOpen(formatTransform?.enabled ?? false);
|
||||||
|
}, [formatTransform?.enabled]);
|
||||||
|
|
||||||
// 仅在外部 proxyConfig 变化且非用户输入时同步(如:重置表单、加载数据)
|
// 仅在外部 proxyConfig 变化且非用户输入时同步(如:重置表单、加载数据)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isUserTyping) {
|
if (!isUserTyping) {
|
||||||
@@ -450,6 +474,135 @@ export function ProviderAdvancedConfig({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 格式转换配置 */}
|
||||||
|
{onFormatTransformChange && (
|
||||||
|
<div className="rounded-lg border border-border/50 bg-muted/20">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex w-full items-center justify-between p-4 hover:bg-muted/30 transition-colors"
|
||||||
|
onClick={() => setIsFormatTransformOpen(!isFormatTransformOpen)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<ArrowLeftRight className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span className="font-medium">
|
||||||
|
{t("providerAdvanced.formatTransform")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Label
|
||||||
|
htmlFor="format-transform-enabled"
|
||||||
|
className="text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
|
{t("providerAdvanced.enableFormatTransform")}
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id="format-transform-enabled"
|
||||||
|
checked={formatTransform?.enabled ?? false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
onFormatTransformChange({
|
||||||
|
...(formatTransform ?? { enabled: false }),
|
||||||
|
enabled: checked,
|
||||||
|
});
|
||||||
|
if (checked) setIsFormatTransformOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{isFormatTransformOpen ? (
|
||||||
|
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden transition-all duration-200",
|
||||||
|
isFormatTransformOpen
|
||||||
|
? "max-h-[500px] opacity-100"
|
||||||
|
: "max-h-0 opacity-0",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="border-t border-border/50 p-4 space-y-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("providerAdvanced.formatTransformDesc")}
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="source-format">
|
||||||
|
{t("providerAdvanced.sourceFormat")}
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={formatTransform?.sourceFormat ?? "anthropic"}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
onFormatTransformChange({
|
||||||
|
...(formatTransform ?? { enabled: false }),
|
||||||
|
sourceFormat: value as "anthropic" | "openai",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!formatTransform?.enabled}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="source-format">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="anthropic">
|
||||||
|
Anthropic (Claude)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="openai">OpenAI</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="target-format">
|
||||||
|
{t("providerAdvanced.targetFormat")}
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={formatTransform?.targetFormat ?? "openai"}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
onFormatTransformChange({
|
||||||
|
...(formatTransform ?? { enabled: false }),
|
||||||
|
targetFormat: value as "anthropic" | "openai",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!formatTransform?.enabled}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="target-format">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="anthropic">
|
||||||
|
Anthropic (Claude)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="openai">OpenAI</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Switch
|
||||||
|
id="transform-streaming"
|
||||||
|
checked={formatTransform?.transformStreaming ?? true}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
onFormatTransformChange({
|
||||||
|
...(formatTransform ?? { enabled: false }),
|
||||||
|
transformStreaming: checked,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!formatTransform?.enabled}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="transform-streaming" className="text-sm">
|
||||||
|
{t("providerAdvanced.transformStreaming")}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
ProviderMeta,
|
ProviderMeta,
|
||||||
ProviderTestConfig,
|
ProviderTestConfig,
|
||||||
ProviderProxyConfig,
|
ProviderProxyConfig,
|
||||||
|
FormatTransformConfig,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import {
|
import {
|
||||||
providerPresets,
|
providerPresets,
|
||||||
@@ -168,6 +169,9 @@ export function ProviderForm({
|
|||||||
const [proxyConfig, setProxyConfig] = useState<ProviderProxyConfig>(
|
const [proxyConfig, setProxyConfig] = useState<ProviderProxyConfig>(
|
||||||
() => initialData?.meta?.proxyConfig ?? { enabled: false },
|
() => initialData?.meta?.proxyConfig ?? { enabled: false },
|
||||||
);
|
);
|
||||||
|
const [formatTransform, setFormatTransform] = useState<FormatTransformConfig>(
|
||||||
|
() => initialData?.meta?.formatTransform ?? { enabled: false },
|
||||||
|
);
|
||||||
|
|
||||||
// 使用 category hook
|
// 使用 category hook
|
||||||
const { category } = useProviderCategory({
|
const { category } = useProviderCategory({
|
||||||
@@ -940,6 +944,7 @@ export function ProviderForm({
|
|||||||
// 添加高级配置
|
// 添加高级配置
|
||||||
testConfig: testConfig.enabled ? testConfig : undefined,
|
testConfig: testConfig.enabled ? testConfig : undefined,
|
||||||
proxyConfig: proxyConfig.enabled ? proxyConfig : undefined,
|
proxyConfig: proxyConfig.enabled ? proxyConfig : undefined,
|
||||||
|
formatTransform: formatTransform.enabled ? formatTransform : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmit(payload);
|
onSubmit(payload);
|
||||||
@@ -1464,8 +1469,10 @@ export function ProviderForm({
|
|||||||
<ProviderAdvancedConfig
|
<ProviderAdvancedConfig
|
||||||
testConfig={testConfig}
|
testConfig={testConfig}
|
||||||
proxyConfig={proxyConfig}
|
proxyConfig={proxyConfig}
|
||||||
|
formatTransform={formatTransform}
|
||||||
onTestConfigChange={setTestConfig}
|
onTestConfigChange={setTestConfig}
|
||||||
onProxyConfigChange={setProxyConfig}
|
onProxyConfigChange={setProxyConfig}
|
||||||
|
onFormatTransformChange={setFormatTransform}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showButtons && (
|
{showButtons && (
|
||||||
|
|||||||
@@ -119,6 +119,18 @@ export interface ProviderProxyConfig {
|
|||||||
proxyPassword?: string;
|
proxyPassword?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式转换配置(用于 OpenRouter 等需要 API 格式转换的供应商)
|
||||||
|
export interface FormatTransformConfig {
|
||||||
|
// 是否启用格式转换
|
||||||
|
enabled: boolean;
|
||||||
|
// 源格式:anthropic, openai, gemini
|
||||||
|
sourceFormat?: "anthropic" | "openai" | "gemini";
|
||||||
|
// 目标格式:anthropic, openai, gemini
|
||||||
|
targetFormat?: "anthropic" | "openai" | "gemini";
|
||||||
|
// 是否转换流式响应(默认 true)
|
||||||
|
transformStreaming?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// 供应商元数据(字段名与后端一致,保持 snake_case)
|
// 供应商元数据(字段名与后端一致,保持 snake_case)
|
||||||
export interface ProviderMeta {
|
export interface ProviderMeta {
|
||||||
// 自定义端点:以 URL 为键,值为端点信息
|
// 自定义端点:以 URL 为键,值为端点信息
|
||||||
@@ -135,6 +147,8 @@ export interface ProviderMeta {
|
|||||||
testConfig?: ProviderTestConfig;
|
testConfig?: ProviderTestConfig;
|
||||||
// 供应商单独的代理配置
|
// 供应商单独的代理配置
|
||||||
proxyConfig?: ProviderProxyConfig;
|
proxyConfig?: ProviderProxyConfig;
|
||||||
|
// 格式转换配置(用于 OpenRouter 等需要 API 格式转换的供应商)
|
||||||
|
formatTransform?: FormatTransformConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skill 同步方式
|
// Skill 同步方式
|
||||||
|
|||||||
Reference in New Issue
Block a user