refactor(codex): remove configuration wizard and unify provider setup experience
- Remove CodexQuickWizardModal component (~300 lines) - Add "Custom (Blank Template)" preset with annotated TOML template - Unify configuration experience across Claude/Codex/Gemini - Remove wizard-related i18n keys, keep apiUrlLabel for CodexFormFields - Simplify component integration by removing wizard state management This change reduces code complexity by ~250 lines while providing better user education through commented configuration templates in Chinese. Users can now: 1. Select "Custom (Blank Template)" preset 2. See annotated TOML template with inline documentation 3. Follow step-by-step comments to configure custom providers BREAKING CHANGE: Configuration wizard UI removed, replaced with template-based approach
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { CodexAuthSection, CodexConfigSection } from "./CodexConfigSections";
|
import { CodexAuthSection, CodexConfigSection } from "./CodexConfigSections";
|
||||||
import { CodexQuickWizardModal } from "./CodexQuickWizardModal";
|
|
||||||
import { CodexCommonConfigModal } from "./CodexCommonConfigModal";
|
import { CodexCommonConfigModal } from "./CodexCommonConfigModal";
|
||||||
|
|
||||||
interface CodexConfigEditorProps {
|
interface CodexConfigEditorProps {
|
||||||
@@ -27,14 +26,6 @@ interface CodexConfigEditorProps {
|
|||||||
authError: string;
|
authError: string;
|
||||||
|
|
||||||
configError: string; // config.toml 错误提示
|
configError: string; // config.toml 错误提示
|
||||||
|
|
||||||
onWebsiteUrlChange?: (url: string) => void; // 更新网址回调
|
|
||||||
|
|
||||||
isTemplateModalOpen?: boolean; // 模态框状态
|
|
||||||
|
|
||||||
setIsTemplateModalOpen?: (open: boolean) => void; // 设置模态框状态
|
|
||||||
|
|
||||||
onNameChange?: (name: string) => void; // 更新供应商名称回调
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
||||||
@@ -50,21 +41,9 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
|||||||
commonConfigError,
|
commonConfigError,
|
||||||
authError,
|
authError,
|
||||||
configError,
|
configError,
|
||||||
onWebsiteUrlChange,
|
|
||||||
onNameChange,
|
|
||||||
isTemplateModalOpen: externalTemplateModalOpen,
|
|
||||||
setIsTemplateModalOpen: externalSetTemplateModalOpen,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
||||||
|
|
||||||
// Use internal state or external state
|
|
||||||
const [internalTemplateModalOpen, setInternalTemplateModalOpen] =
|
|
||||||
useState(false);
|
|
||||||
const isTemplateModalOpen =
|
|
||||||
externalTemplateModalOpen ?? internalTemplateModalOpen;
|
|
||||||
const setIsTemplateModalOpen =
|
|
||||||
externalSetTemplateModalOpen ?? setInternalTemplateModalOpen;
|
|
||||||
|
|
||||||
// Auto-open common config modal if there's an error
|
// Auto-open common config modal if there's an error
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (commonConfigError && !isCommonConfigModalOpen) {
|
if (commonConfigError && !isCommonConfigModalOpen) {
|
||||||
@@ -72,23 +51,6 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
|||||||
}
|
}
|
||||||
}, [commonConfigError, isCommonConfigModalOpen]);
|
}, [commonConfigError, isCommonConfigModalOpen]);
|
||||||
|
|
||||||
const handleQuickWizardApply = (
|
|
||||||
auth: string,
|
|
||||||
config: string,
|
|
||||||
extras: { websiteUrl?: string; displayName?: string },
|
|
||||||
) => {
|
|
||||||
onAuthChange(auth);
|
|
||||||
onConfigChange(config);
|
|
||||||
|
|
||||||
if (onWebsiteUrlChange && extras.websiteUrl) {
|
|
||||||
onWebsiteUrlChange(extras.websiteUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onNameChange && extras.displayName) {
|
|
||||||
onNameChange(extras.displayName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Auth JSON Section */}
|
{/* Auth JSON Section */}
|
||||||
@@ -110,13 +72,6 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
|||||||
configError={configError}
|
configError={configError}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Quick Wizard Modal */}
|
|
||||||
<CodexQuickWizardModal
|
|
||||||
isOpen={isTemplateModalOpen}
|
|
||||||
onClose={() => setIsTemplateModalOpen(false)}
|
|
||||||
onApply={handleQuickWizardApply}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Common Config Modal */}
|
{/* Common Config Modal */}
|
||||||
<CodexCommonConfigModal
|
<CodexCommonConfigModal
|
||||||
isOpen={isCommonConfigModalOpen}
|
isOpen={isCommonConfigModalOpen}
|
||||||
|
|||||||
@@ -1,298 +0,0 @@
|
|||||||
import React, { useState, useRef } from "react";
|
|
||||||
import { Save } from "lucide-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import {
|
|
||||||
generateThirdPartyAuth,
|
|
||||||
generateThirdPartyConfig,
|
|
||||||
} from "@/config/codexProviderPresets";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogFooter,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
|
|
||||||
interface CodexQuickWizardModalProps {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
onApply: (
|
|
||||||
auth: string,
|
|
||||||
config: string,
|
|
||||||
extras: {
|
|
||||||
websiteUrl?: string;
|
|
||||||
displayName?: string;
|
|
||||||
},
|
|
||||||
) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CodexQuickWizardModal - Codex quick configuration wizard
|
|
||||||
* Helps users quickly generate auth.json and config.toml
|
|
||||||
*/
|
|
||||||
export const CodexQuickWizardModal: React.FC<CodexQuickWizardModalProps> = ({
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
onApply,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [templateApiKey, setTemplateApiKey] = useState("");
|
|
||||||
const [templateProviderName, setTemplateProviderName] = useState("");
|
|
||||||
const [templateBaseUrl, setTemplateBaseUrl] = useState("");
|
|
||||||
const [templateWebsiteUrl, setTemplateWebsiteUrl] = useState("");
|
|
||||||
const [templateModelName, setTemplateModelName] = useState("gpt-5-codex");
|
|
||||||
const [templateDisplayName, setTemplateDisplayName] = useState("");
|
|
||||||
|
|
||||||
const apiKeyInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const baseUrlInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const modelNameInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const displayNameInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
setTemplateApiKey("");
|
|
||||||
setTemplateProviderName("");
|
|
||||||
setTemplateBaseUrl("");
|
|
||||||
setTemplateWebsiteUrl("");
|
|
||||||
setTemplateModelName("gpt-5-codex");
|
|
||||||
setTemplateDisplayName("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
resetForm();
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyTemplate = () => {
|
|
||||||
const requiredInputs = [
|
|
||||||
displayNameInputRef.current,
|
|
||||||
apiKeyInputRef.current,
|
|
||||||
baseUrlInputRef.current,
|
|
||||||
modelNameInputRef.current,
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const input of requiredInputs) {
|
|
||||||
if (input && !input.checkValidity()) {
|
|
||||||
input.reportValidity();
|
|
||||||
input.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const trimmedKey = templateApiKey.trim();
|
|
||||||
const trimmedBaseUrl = templateBaseUrl.trim();
|
|
||||||
const trimmedModel = templateModelName.trim();
|
|
||||||
|
|
||||||
const auth = generateThirdPartyAuth(trimmedKey);
|
|
||||||
const config = generateThirdPartyConfig(
|
|
||||||
templateProviderName || "custom",
|
|
||||||
trimmedBaseUrl,
|
|
||||||
trimmedModel,
|
|
||||||
);
|
|
||||||
|
|
||||||
onApply(JSON.stringify(auth, null, 2), config, {
|
|
||||||
websiteUrl: templateWebsiteUrl.trim(),
|
|
||||||
displayName: templateDisplayName.trim(),
|
|
||||||
});
|
|
||||||
|
|
||||||
resetForm();
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
applyTemplate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={isOpen} onOpenChange={(open) => !open && handleClose()}>
|
|
||||||
<DialogContent
|
|
||||||
zIndex="nested"
|
|
||||||
className="max-w-2xl max-h-[90vh] flex flex-col p-0"
|
|
||||||
>
|
|
||||||
<DialogHeader className="px-6 pt-6 pb-0">
|
|
||||||
<DialogTitle>{t("codexConfig.quickWizard")}</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 space-y-4 overflow-auto px-6 py-4">
|
|
||||||
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20">
|
|
||||||
<p className="text-sm text-blue-800 dark:text-blue-200">
|
|
||||||
{t("codexConfig.wizardHint")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{/* API Key */}
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{t("codexConfig.apiKeyLabel")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={templateApiKey}
|
|
||||||
ref={apiKeyInputRef}
|
|
||||||
onChange={(e) => setTemplateApiKey(e.target.value)}
|
|
||||||
onKeyDown={handleInputKeyDown}
|
|
||||||
pattern=".*\S.*"
|
|
||||||
title={t("common.enterValidValue")}
|
|
||||||
placeholder={t("codexConfig.apiKeyPlaceholder")}
|
|
||||||
required
|
|
||||||
className="font-mono"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Display Name */}
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{t("codexConfig.supplierNameLabel")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={templateDisplayName}
|
|
||||||
ref={displayNameInputRef}
|
|
||||||
onChange={(e) => setTemplateDisplayName(e.target.value)}
|
|
||||||
onKeyDown={handleInputKeyDown}
|
|
||||||
placeholder={t("codexConfig.supplierNamePlaceholder")}
|
|
||||||
required
|
|
||||||
pattern=".*\S.*"
|
|
||||||
title={t("common.enterValidValue")}
|
|
||||||
/>
|
|
||||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{t("codexConfig.supplierNameHint")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Provider Name */}
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{t("codexConfig.supplierCodeLabel")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={templateProviderName}
|
|
||||||
onChange={(e) => setTemplateProviderName(e.target.value)}
|
|
||||||
onKeyDown={handleInputKeyDown}
|
|
||||||
placeholder={t("codexConfig.supplierCodePlaceholder")}
|
|
||||||
/>
|
|
||||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{t("codexConfig.supplierCodeHint")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Base URL */}
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{t("codexConfig.apiUrlLabel")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
type="url"
|
|
||||||
value={templateBaseUrl}
|
|
||||||
ref={baseUrlInputRef}
|
|
||||||
onChange={(e) => setTemplateBaseUrl(e.target.value)}
|
|
||||||
onKeyDown={handleInputKeyDown}
|
|
||||||
placeholder={t("codexConfig.apiUrlPlaceholder")}
|
|
||||||
required
|
|
||||||
className="font-mono"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Website URL */}
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{t("codexConfig.websiteLabel")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
type="url"
|
|
||||||
value={templateWebsiteUrl}
|
|
||||||
onChange={(e) => setTemplateWebsiteUrl(e.target.value)}
|
|
||||||
onKeyDown={handleInputKeyDown}
|
|
||||||
placeholder={t("codexConfig.websitePlaceholder")}
|
|
||||||
/>
|
|
||||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{t("codexConfig.websiteHint")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Model Name */}
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{t("codexConfig.modelNameLabel")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={templateModelName}
|
|
||||||
ref={modelNameInputRef}
|
|
||||||
onChange={(e) => setTemplateModelName(e.target.value)}
|
|
||||||
onKeyDown={handleInputKeyDown}
|
|
||||||
pattern=".*\S.*"
|
|
||||||
title={t("common.enterValidValue")}
|
|
||||||
placeholder={t("codexConfig.modelNamePlaceholder")}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Preview */}
|
|
||||||
{(templateApiKey || templateProviderName || templateBaseUrl) && (
|
|
||||||
<div className="space-y-2 border-t border-border-default pt-4 ">
|
|
||||||
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{t("codexConfig.configPreview")}
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
||||||
auth.json
|
|
||||||
</label>
|
|
||||||
<pre className="overflow-x-auto rounded-lg bg-gray-50 p-3 text-xs font-mono text-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
|
||||||
{JSON.stringify(
|
|
||||||
generateThirdPartyAuth(templateApiKey),
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
||||||
config.toml
|
|
||||||
</label>
|
|
||||||
<pre className="whitespace-pre-wrap rounded-lg bg-gray-50 p-3 text-xs font-mono text-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
|
||||||
{templateProviderName && templateBaseUrl
|
|
||||||
? generateThirdPartyConfig(
|
|
||||||
templateProviderName,
|
|
||||||
templateBaseUrl,
|
|
||||||
templateModelName,
|
|
||||||
)
|
|
||||||
: ""}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button type="button" variant="outline" onClick={handleClose}>
|
|
||||||
{t("common.cancel")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
applyTemplate();
|
|
||||||
}}
|
|
||||||
className="gap-2"
|
|
||||||
>
|
|
||||||
<Save className="h-4 w-4" />
|
|
||||||
{t("codexConfig.applyConfig")}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -220,9 +220,6 @@ export function ProviderForm({
|
|||||||
[originalHandleCodexConfigChange, debouncedValidate],
|
[originalHandleCodexConfigChange, debouncedValidate],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [isCodexTemplateModalOpen, setIsCodexTemplateModalOpen] =
|
|
||||||
useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset(defaultValues);
|
form.reset(defaultValues);
|
||||||
}, [defaultValues, form]);
|
}, [defaultValues, form]);
|
||||||
@@ -615,11 +612,6 @@ export function ProviderForm({
|
|||||||
onPresetChange={handlePresetChange}
|
onPresetChange={handlePresetChange}
|
||||||
category={category}
|
category={category}
|
||||||
appId={appId}
|
appId={appId}
|
||||||
onOpenWizard={
|
|
||||||
appId === "codex"
|
|
||||||
? () => setIsCodexTemplateModalOpen(true)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -739,10 +731,6 @@ export function ProviderForm({
|
|||||||
commonConfigError={codexCommonConfigError}
|
commonConfigError={codexCommonConfigError}
|
||||||
authError={codexAuthError}
|
authError={codexAuthError}
|
||||||
configError={codexConfigError}
|
configError={codexConfigError}
|
||||||
onWebsiteUrlChange={(url) => form.setValue("websiteUrl", url)}
|
|
||||||
onNameChange={(name) => form.setValue("name", name)}
|
|
||||||
isTemplateModalOpen={isCodexTemplateModalOpen}
|
|
||||||
setIsTemplateModalOpen={setIsCodexTemplateModalOpen}
|
|
||||||
/>
|
/>
|
||||||
{/* 配置验证错误显示 */}
|
{/* 配置验证错误显示 */}
|
||||||
<FormField
|
<FormField
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ interface ProviderPresetSelectorProps {
|
|||||||
categoryKeys: string[];
|
categoryKeys: string[];
|
||||||
presetCategoryLabels: Record<string, string>;
|
presetCategoryLabels: Record<string, string>;
|
||||||
onPresetChange: (value: string) => void;
|
onPresetChange: (value: string) => void;
|
||||||
category?: ProviderCategory; // 新增:当前选中的分类
|
category?: ProviderCategory; // 当前选中的分类
|
||||||
appId?: AppId;
|
appId?: AppId;
|
||||||
onOpenWizard?: () => void; // Codex 专用:打开配置向导
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProviderPresetSelector({
|
export function ProviderPresetSelector({
|
||||||
@@ -32,7 +31,6 @@ export function ProviderPresetSelector({
|
|||||||
onPresetChange,
|
onPresetChange,
|
||||||
category,
|
category,
|
||||||
appId,
|
appId,
|
||||||
onOpenWizard,
|
|
||||||
}: ProviderPresetSelectorProps) {
|
}: ProviderPresetSelectorProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -56,23 +54,6 @@ export function ProviderPresetSelector({
|
|||||||
defaultValue: "💡 第三方供应商需要填写 API Key 和请求地址",
|
defaultValue: "💡 第三方供应商需要填写 API Key 和请求地址",
|
||||||
});
|
});
|
||||||
case "custom":
|
case "custom":
|
||||||
// Codex 自定义:在此位置显示"手动配置…或者 使用配置向导"
|
|
||||||
if (appId === "codex" && onOpenWizard) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{t("providerForm.manualConfig")}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onOpenWizard}
|
|
||||||
className="ml-1 text-blue-500 dark:text-blue-400 hover:text-blue-600 dark:hover:text-blue-300 underline-offset-2 hover:underline"
|
|
||||||
aria-label={t("providerForm.openConfigWizard")}
|
|
||||||
>
|
|
||||||
{t("providerForm.useConfigWizard")}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// 其他情况沿用原提示
|
|
||||||
return t("providerForm.customApiKeyHint", {
|
return t("providerForm.customApiKeyHint", {
|
||||||
defaultValue: "💡 自定义配置需手动填写所有必要字段",
|
defaultValue: "💡 自定义配置需手动填写所有必要字段",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -98,6 +98,52 @@ requires_openai_auth = true`,
|
|||||||
textColor: "#FFFFFF",
|
textColor: "#FFFFFF",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Custom (Blank Template)",
|
||||||
|
websiteUrl: "https://docs.anthropic.com",
|
||||||
|
category: "third_party",
|
||||||
|
isCustomTemplate: true,
|
||||||
|
auth: generateThirdPartyAuth(""),
|
||||||
|
config: `# ========================================
|
||||||
|
# Codex 自定义供应商配置模板
|
||||||
|
# ========================================
|
||||||
|
# 快速上手:
|
||||||
|
# 1. 在上方 auth.json 中设置 API Key
|
||||||
|
# 2. 将下方 'custom' 替换为供应商名称(小写、无空格)
|
||||||
|
# 3. 替换 base_url 为实际的 API 端点
|
||||||
|
# 4. 根据需要调整模型名称
|
||||||
|
#
|
||||||
|
# 文档: https://docs.anthropic.com
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# ========== 模型配置 ==========
|
||||||
|
model_provider = "custom" # 供应商唯一标识
|
||||||
|
model = "gpt-5-codex" # 模型名称
|
||||||
|
model_reasoning_effort = "high" # 推理强度:low, medium, high
|
||||||
|
disable_response_storage = true # 隐私:不本地存储响应
|
||||||
|
|
||||||
|
# ========== 供应商设置 ==========
|
||||||
|
[model_providers.custom]
|
||||||
|
name = "custom" # 与上方 model_provider 保持一致
|
||||||
|
base_url = "https://api.example.com/v1" # 👈 替换为实际端点
|
||||||
|
wire_api = "responses" # API 响应格式
|
||||||
|
requires_openai_auth = true # 使用 auth.json 中的 OPENAI_API_KEY
|
||||||
|
|
||||||
|
# ========== 可选:自定义请求头 ==========
|
||||||
|
# 如果供应商需要自定义请求头,取消注释:
|
||||||
|
# [model_providers.custom.headers]
|
||||||
|
# X-Custom-Header = "value"
|
||||||
|
|
||||||
|
# ========== 可选:模型覆盖 ==========
|
||||||
|
# 如果需要覆盖特定模型,取消注释:
|
||||||
|
# [model_overrides]
|
||||||
|
# "gpt-5-codex" = { model_provider = "custom", model = "your-model-name" }`,
|
||||||
|
theme: {
|
||||||
|
icon: "generic",
|
||||||
|
backgroundColor: "#6B7280", // gray-500
|
||||||
|
textColor: "#FFFFFF",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "AiHubMix",
|
name: "AiHubMix",
|
||||||
websiteUrl: "https://aihubmix.com",
|
websiteUrl: "https://aihubmix.com",
|
||||||
|
|||||||
@@ -239,9 +239,6 @@
|
|||||||
"codexApiEndpointPlaceholder": "https://your-api-endpoint.com/v1",
|
"codexApiEndpointPlaceholder": "https://your-api-endpoint.com/v1",
|
||||||
"manageAndTest": "Manage & Test",
|
"manageAndTest": "Manage & Test",
|
||||||
"configContent": "Config Content",
|
"configContent": "Config Content",
|
||||||
"useConfigWizard": "Use Configuration Wizard",
|
|
||||||
"openConfigWizard": "Open configuration wizard",
|
|
||||||
"manualConfig": "Manually configure provider, requires complete configuration, or",
|
|
||||||
"officialNoApiKey": "Official login does not require API Key, save directly",
|
"officialNoApiKey": "Official login does not require API Key, save directly",
|
||||||
"codexOfficialNoApiKey": "Official does not require API Key, save directly",
|
"codexOfficialNoApiKey": "Official does not require API Key, save directly",
|
||||||
"codexApiKeyAutoFill": "Just fill in here, auth.json below will be auto-filled",
|
"codexApiKeyAutoFill": "Just fill in here, auth.json below will be auto-filled",
|
||||||
@@ -314,7 +311,6 @@
|
|||||||
"testFailed": "Speed test failed: {{error}}"
|
"testFailed": "Speed test failed: {{error}}"
|
||||||
},
|
},
|
||||||
"codexConfig": {
|
"codexConfig": {
|
||||||
"quickWizard": "Quick Configuration Wizard",
|
|
||||||
"authJson": "auth.json (JSON) *",
|
"authJson": "auth.json (JSON) *",
|
||||||
"authJsonPlaceholder": "{\n \"OPENAI_API_KEY\": \"sk-your-api-key-here\"\n}",
|
"authJsonPlaceholder": "{\n \"OPENAI_API_KEY\": \"sk-your-api-key-here\"\n}",
|
||||||
"authJsonHint": "Codex auth.json configuration content",
|
"authJsonHint": "Codex auth.json configuration content",
|
||||||
@@ -324,24 +320,7 @@
|
|||||||
"editCommonConfig": "Edit Common Config",
|
"editCommonConfig": "Edit Common Config",
|
||||||
"editCommonConfigTitle": "Edit Codex Common Config Snippet",
|
"editCommonConfigTitle": "Edit Codex Common Config Snippet",
|
||||||
"commonConfigHint": "This snippet will be appended to the end of config.toml when 'Write Common Config' is checked",
|
"commonConfigHint": "This snippet will be appended to the end of config.toml when 'Write Common Config' is checked",
|
||||||
"wizardHint": "Enter key parameters, the system will automatically generate standard auth.json and config.toml configuration.",
|
"apiUrlLabel": "API Request URL"
|
||||||
"apiKeyLabel": "API Key *",
|
|
||||||
"apiKeyPlaceholder": "sk-your-api-key-here",
|
|
||||||
"supplierNameLabel": "Provider Name *",
|
|
||||||
"supplierNamePlaceholder": "e.g., Codex Official",
|
|
||||||
"supplierNameHint": "Will be displayed in the provider list, can use Chinese",
|
|
||||||
"supplierCodeLabel": "Provider Code (English)",
|
|
||||||
"supplierCodePlaceholder": "custom (optional)",
|
|
||||||
"supplierCodeHint": "Will be used as identifier in config file, defaults to custom",
|
|
||||||
"apiUrlLabel": "API Request URL *",
|
|
||||||
"apiUrlPlaceholder": "https://your-api-endpoint.com/v1",
|
|
||||||
"websiteLabel": "Website URL",
|
|
||||||
"websitePlaceholder": "https://example.com",
|
|
||||||
"websiteHint": "Official website address (optional)",
|
|
||||||
"modelNameLabel": "Model Name *",
|
|
||||||
"modelNamePlaceholder": "gpt-5-codex",
|
|
||||||
"configPreview": "Configuration Preview",
|
|
||||||
"applyConfig": "Apply Configuration"
|
|
||||||
},
|
},
|
||||||
"geminiConfig": {
|
"geminiConfig": {
|
||||||
"envFile": "Environment Variables (.env)",
|
"envFile": "Environment Variables (.env)",
|
||||||
|
|||||||
@@ -239,9 +239,6 @@
|
|||||||
"codexApiEndpointPlaceholder": "https://your-api-endpoint.com/v1",
|
"codexApiEndpointPlaceholder": "https://your-api-endpoint.com/v1",
|
||||||
"manageAndTest": "管理与测速",
|
"manageAndTest": "管理与测速",
|
||||||
"configContent": "配置内容",
|
"configContent": "配置内容",
|
||||||
"useConfigWizard": "使用配置向导",
|
|
||||||
"openConfigWizard": "打开配置向导",
|
|
||||||
"manualConfig": "手动配置供应商,需要填写完整的配置信息,或者",
|
|
||||||
"officialNoApiKey": "官方登录无需填写 API Key,直接保存即可",
|
"officialNoApiKey": "官方登录无需填写 API Key,直接保存即可",
|
||||||
"codexOfficialNoApiKey": "官方无需填写 API Key,直接保存即可",
|
"codexOfficialNoApiKey": "官方无需填写 API Key,直接保存即可",
|
||||||
"codexApiKeyAutoFill": "只需要填这里,下方 auth.json 会自动填充",
|
"codexApiKeyAutoFill": "只需要填这里,下方 auth.json 会自动填充",
|
||||||
@@ -314,7 +311,6 @@
|
|||||||
"testFailed": "测速失败: {{error}}"
|
"testFailed": "测速失败: {{error}}"
|
||||||
},
|
},
|
||||||
"codexConfig": {
|
"codexConfig": {
|
||||||
"quickWizard": "快速配置向导",
|
|
||||||
"authJson": "auth.json (JSON) *",
|
"authJson": "auth.json (JSON) *",
|
||||||
"authJsonPlaceholder": "{\n \"OPENAI_API_KEY\": \"sk-your-api-key-here\"\n}",
|
"authJsonPlaceholder": "{\n \"OPENAI_API_KEY\": \"sk-your-api-key-here\"\n}",
|
||||||
"authJsonHint": "Codex auth.json 配置内容",
|
"authJsonHint": "Codex auth.json 配置内容",
|
||||||
@@ -324,24 +320,7 @@
|
|||||||
"editCommonConfig": "编辑通用配置",
|
"editCommonConfig": "编辑通用配置",
|
||||||
"editCommonConfigTitle": "编辑 Codex 通用配置片段",
|
"editCommonConfigTitle": "编辑 Codex 通用配置片段",
|
||||||
"commonConfigHint": "该片段会在勾选'写入通用配置'时追加到 config.toml 末尾",
|
"commonConfigHint": "该片段会在勾选'写入通用配置'时追加到 config.toml 末尾",
|
||||||
"wizardHint": "输入关键参数,系统将自动生成标准的 auth.json 和 config.toml 配置。",
|
"apiUrlLabel": "API 请求地址"
|
||||||
"apiKeyLabel": "API 密钥 *",
|
|
||||||
"apiKeyPlaceholder": "sk-your-api-key-here",
|
|
||||||
"supplierNameLabel": "供应商名称 *",
|
|
||||||
"supplierNamePlaceholder": "例如:Codex 官方",
|
|
||||||
"supplierNameHint": "将显示在供应商列表中,可使用中文",
|
|
||||||
"supplierCodeLabel": "供应商代号(英文)",
|
|
||||||
"supplierCodePlaceholder": "custom(可选)",
|
|
||||||
"supplierCodeHint": "将用作配置文件中的标识符,默认为 custom",
|
|
||||||
"apiUrlLabel": "API 请求地址 *",
|
|
||||||
"apiUrlPlaceholder": "https://your-api-endpoint.com/v1",
|
|
||||||
"websiteLabel": "官网地址",
|
|
||||||
"websitePlaceholder": "https://example.com",
|
|
||||||
"websiteHint": "官方网站地址(可选)",
|
|
||||||
"modelNameLabel": "模型名称 *",
|
|
||||||
"modelNamePlaceholder": "gpt-5-codex",
|
|
||||||
"configPreview": "配置预览",
|
|
||||||
"applyConfig": "应用配置"
|
|
||||||
},
|
},
|
||||||
"geminiConfig": {
|
"geminiConfig": {
|
||||||
"envFile": "环境变量 (.env)",
|
"envFile": "环境变量 (.env)",
|
||||||
|
|||||||
Reference in New Issue
Block a user