fix: unify dialog layout and fix content padding issues
- Fix negative margin overflow in all dialog content areas - Standardize dialog structure with flex-col layout - Add consistent py-4 spacing to all content areas - Ensure proper spacing between header, content, and footer Affected components: - AddProviderDialog, EditProviderDialog - McpFormModal, McpPanel - UsageScriptModal - SettingsDialog All dialogs now follow unified layout pattern: - DialogContent: flex flex-col max-h-[90vh] - Content area: flex-1 overflow-y-auto px-6 py-4 - No negative margins that cause content overflow
This commit is contained in:
@@ -4,7 +4,12 @@ import { toast } from "sonner";
|
|||||||
import { Plus, Settings } from "lucide-react";
|
import { Plus, Settings } from "lucide-react";
|
||||||
import type { Provider } from "@/types";
|
import type { Provider } from "@/types";
|
||||||
import { useProvidersQuery } from "@/lib/query";
|
import { useProvidersQuery } from "@/lib/query";
|
||||||
import { providersApi, settingsApi, type AppType, type ProviderSwitchEvent } from "@/lib/api";
|
import {
|
||||||
|
providersApi,
|
||||||
|
settingsApi,
|
||||||
|
type AppType,
|
||||||
|
type ProviderSwitchEvent,
|
||||||
|
} from "@/lib/api";
|
||||||
import { useProviderActions } from "@/hooks/useProviderActions";
|
import { useProviderActions } from "@/hooks/useProviderActions";
|
||||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||||
import { AppSwitcher } from "@/components/AppSwitcher";
|
import { AppSwitcher } from "@/components/AppSwitcher";
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
|
|||||||
appType,
|
appType,
|
||||||
usageEnabled,
|
usageEnabled,
|
||||||
}) => {
|
}) => {
|
||||||
const { data: usage, isLoading: loading, refetch } = useUsageQuery(
|
const {
|
||||||
providerId,
|
data: usage,
|
||||||
appType,
|
isLoading: loading,
|
||||||
usageEnabled,
|
refetch,
|
||||||
);
|
} = useUsageQuery(providerId, appType, usageEnabled);
|
||||||
|
|
||||||
// 只在启用用量查询且有数据时显示
|
// 只在启用用量查询且有数据时显示
|
||||||
if (!usageEnabled || !usage) return null;
|
if (!usageEnabled || !usage) return null;
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{/* Content - Scrollable */}
|
{/* Content - Scrollable */}
|
||||||
<div className="flex-1 overflow-y-auto -mx-6 px-6 space-y-4">
|
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
|
||||||
{/* 启用开关 */}
|
{/* 启用开关 */}
|
||||||
<label className="flex items-center gap-2 cursor-pointer">
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -22,8 +22,15 @@ import { mcpApi, type AppType } from "@/lib/api";
|
|||||||
import { McpServer, McpServerSpec } from "@/types";
|
import { McpServer, McpServerSpec } from "@/types";
|
||||||
import { mcpPresets, getMcpPresetWithDescription } from "@/config/mcpPresets";
|
import { mcpPresets, getMcpPresetWithDescription } from "@/config/mcpPresets";
|
||||||
import McpWizardModal from "./McpWizardModal";
|
import McpWizardModal from "./McpWizardModal";
|
||||||
import { extractErrorMessage, translateMcpBackendError } from "@/utils/errorUtils";
|
import {
|
||||||
import { tomlToMcpServer, extractIdFromToml, mcpServerToToml } from "@/utils/tomlUtils";
|
extractErrorMessage,
|
||||||
|
translateMcpBackendError,
|
||||||
|
} from "@/utils/errorUtils";
|
||||||
|
import {
|
||||||
|
tomlToMcpServer,
|
||||||
|
extractIdFromToml,
|
||||||
|
mcpServerToToml,
|
||||||
|
} from "@/utils/tomlUtils";
|
||||||
import { useMcpValidation } from "./useMcpValidation";
|
import { useMcpValidation } from "./useMcpValidation";
|
||||||
|
|
||||||
interface McpFormModalProps {
|
interface McpFormModalProps {
|
||||||
@@ -426,7 +433,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{/* Content - Scrollable */}
|
{/* Content - Scrollable */}
|
||||||
<div className="flex-1 overflow-y-auto -mx-6 px-6 space-y-4">
|
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
|
||||||
{/* 预设选择(仅新增时展示) */}
|
{/* 预设选择(仅新增时展示) */}
|
||||||
{!isEditing && (
|
{!isEditing && (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -25,11 +25,7 @@ interface McpPanelProps {
|
|||||||
* MCP 管理面板
|
* MCP 管理面板
|
||||||
* 采用与主界面一致的设计风格,右上角添加按钮,每个 MCP 占一行
|
* 采用与主界面一致的设计风格,右上角添加按钮,每个 MCP 占一行
|
||||||
*/
|
*/
|
||||||
const McpPanel: React.FC<McpPanelProps> = ({
|
const McpPanel: React.FC<McpPanelProps> = ({ open, onOpenChange, appType }) => {
|
||||||
open,
|
|
||||||
onOpenChange,
|
|
||||||
appType,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isFormOpen, setIsFormOpen] = useState(false);
|
const [isFormOpen, setIsFormOpen] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<string | null>(null);
|
const [editingId, setEditingId] = useState<string | null>(null);
|
||||||
@@ -142,7 +138,7 @@ const McpPanel: React.FC<McpPanelProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content - Scrollable */}
|
{/* Content - Scrollable */}
|
||||||
<div className="flex-1 overflow-y-auto -mx-6 px-6">
|
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
|
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
|
||||||
{t("mcp.loading")}
|
{t("mcp.loading")}
|
||||||
|
|||||||
@@ -63,7 +63,11 @@ export function AddProviderDialog({
|
|||||||
if (appType === "claude") {
|
if (appType === "claude") {
|
||||||
const presets = providerPresets;
|
const presets = providerPresets;
|
||||||
const presetIndex = parseInt(values.presetId.replace("claude-", ""));
|
const presetIndex = parseInt(values.presetId.replace("claude-", ""));
|
||||||
if (!isNaN(presetIndex) && presetIndex >= 0 && presetIndex < presets.length) {
|
if (
|
||||||
|
!isNaN(presetIndex) &&
|
||||||
|
presetIndex >= 0 &&
|
||||||
|
presetIndex < presets.length
|
||||||
|
) {
|
||||||
const preset = presets[presetIndex];
|
const preset = presets[presetIndex];
|
||||||
if (preset?.endpointCandidates) {
|
if (preset?.endpointCandidates) {
|
||||||
preset.endpointCandidates.forEach(addUrl);
|
preset.endpointCandidates.forEach(addUrl);
|
||||||
@@ -72,7 +76,11 @@ export function AddProviderDialog({
|
|||||||
} else if (appType === "codex") {
|
} else if (appType === "codex") {
|
||||||
const presets = codexProviderPresets;
|
const presets = codexProviderPresets;
|
||||||
const presetIndex = parseInt(values.presetId.replace("codex-", ""));
|
const presetIndex = parseInt(values.presetId.replace("codex-", ""));
|
||||||
if (!isNaN(presetIndex) && presetIndex >= 0 && presetIndex < presets.length) {
|
if (
|
||||||
|
!isNaN(presetIndex) &&
|
||||||
|
presetIndex >= 0 &&
|
||||||
|
presetIndex < presets.length
|
||||||
|
) {
|
||||||
const preset = presets[presetIndex];
|
const preset = presets[presetIndex];
|
||||||
if ((preset as any).endpointCandidates) {
|
if ((preset as any).endpointCandidates) {
|
||||||
(preset as any).endpointCandidates.forEach(addUrl);
|
(preset as any).endpointCandidates.forEach(addUrl);
|
||||||
@@ -139,7 +147,7 @@ export function AddProviderDialog({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto -mx-6 px-6">
|
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||||
<ProviderForm
|
<ProviderForm
|
||||||
appType={appType}
|
appType={appType}
|
||||||
submitLabel={t("common.add", { defaultValue: "添加" })}
|
submitLabel={t("common.add", { defaultValue: "添加" })}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function EditProviderDialog({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto -mx-6 px-6">
|
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||||
<ProviderForm
|
<ProviderForm
|
||||||
appType={appType}
|
appType={appType}
|
||||||
submitLabel={t("common.save", { defaultValue: "保存" })}
|
submitLabel={t("common.save", { defaultValue: "保存" })}
|
||||||
|
|||||||
@@ -122,8 +122,14 @@ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
|
|||||||
{t("claudeConfig.fullSettingsHint")}
|
{t("claudeConfig.fullSettingsHint")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Dialog open={isCommonConfigModalOpen} onOpenChange={(open) => !open && closeModal()}>
|
<Dialog
|
||||||
<DialogContent zIndex="nested" className="max-w-2xl max-h-[90vh] flex flex-col p-0">
|
open={isCommonConfigModalOpen}
|
||||||
|
onOpenChange={(open) => !open && closeModal()}
|
||||||
|
>
|
||||||
|
<DialogContent
|
||||||
|
zIndex="nested"
|
||||||
|
className="max-w-2xl max-h-[90vh] flex flex-col p-0"
|
||||||
|
>
|
||||||
<DialogHeader className="px-6 pt-6 pb-0">
|
<DialogHeader className="px-6 pt-6 pb-0">
|
||||||
<DialogTitle>{t("claudeConfig.editCommonConfigTitle")}</DialogTitle>
|
<DialogTitle>{t("claudeConfig.editCommonConfigTitle")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ interface ClaudeFormFieldsProps {
|
|||||||
claudeSmallFastModel: string;
|
claudeSmallFastModel: string;
|
||||||
onModelChange: (
|
onModelChange: (
|
||||||
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||||
value: string
|
value: string,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
// Kimi Model Selector
|
// Kimi Model Selector
|
||||||
@@ -49,7 +49,7 @@ interface ClaudeFormFieldsProps {
|
|||||||
kimiAnthropicSmallFastModel: string;
|
kimiAnthropicSmallFastModel: string;
|
||||||
onKimiModelChange: (
|
onKimiModelChange: (
|
||||||
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||||
value: string
|
value: string,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
// Speed Test Endpoints
|
// Speed Test Endpoints
|
||||||
|
|||||||
@@ -33,7 +33,10 @@ export const CodexCommonConfigModal: React.FC<CodexCommonConfigModalProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||||
<DialogContent zIndex="nested" className="max-w-2xl max-h-[90vh] flex flex-col p-0">
|
<DialogContent
|
||||||
|
zIndex="nested"
|
||||||
|
className="max-w-2xl max-h-[90vh] flex flex-col p-0"
|
||||||
|
>
|
||||||
<DialogHeader className="px-6 pt-6 pb-0">
|
<DialogHeader className="px-6 pt-6 pb-0">
|
||||||
<DialogTitle>{t("codexConfig.editCommonConfigTitle")}</DialogTitle>
|
<DialogTitle>{t("codexConfig.editCommonConfigTitle")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -60,9 +60,12 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
|||||||
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
||||||
|
|
||||||
// Use internal state or external state
|
// Use internal state or external state
|
||||||
const [internalTemplateModalOpen, setInternalTemplateModalOpen] = useState(false);
|
const [internalTemplateModalOpen, setInternalTemplateModalOpen] =
|
||||||
const isTemplateModalOpen = externalTemplateModalOpen ?? internalTemplateModalOpen;
|
useState(false);
|
||||||
const setIsTemplateModalOpen = externalSetTemplateModalOpen ?? setInternalTemplateModalOpen;
|
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(() => {
|
||||||
@@ -74,7 +77,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
|||||||
const handleQuickWizardApply = (
|
const handleQuickWizardApply = (
|
||||||
auth: string,
|
auth: string,
|
||||||
config: string,
|
config: string,
|
||||||
extras: { websiteUrl?: string; displayName?: string }
|
extras: { websiteUrl?: string; displayName?: string },
|
||||||
) => {
|
) => {
|
||||||
onAuthChange(auth);
|
onAuthChange(auth);
|
||||||
onConfigChange(config);
|
onConfigChange(config);
|
||||||
|
|||||||
@@ -18,10 +18,14 @@ import { Input } from "@/components/ui/input";
|
|||||||
interface CodexQuickWizardModalProps {
|
interface CodexQuickWizardModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onApply: (auth: string, config: string, extras: {
|
onApply: (
|
||||||
websiteUrl?: string;
|
auth: string,
|
||||||
displayName?: string;
|
config: string,
|
||||||
}) => void;
|
extras: {
|
||||||
|
websiteUrl?: string;
|
||||||
|
displayName?: string;
|
||||||
|
},
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,14 +92,10 @@ export const CodexQuickWizardModal: React.FC<CodexQuickWizardModalProps> = ({
|
|||||||
trimmedModel,
|
trimmedModel,
|
||||||
);
|
);
|
||||||
|
|
||||||
onApply(
|
onApply(JSON.stringify(auth, null, 2), config, {
|
||||||
JSON.stringify(auth, null, 2),
|
websiteUrl: templateWebsiteUrl.trim(),
|
||||||
config,
|
displayName: templateDisplayName.trim(),
|
||||||
{
|
});
|
||||||
websiteUrl: templateWebsiteUrl.trim(),
|
|
||||||
displayName: templateDisplayName.trim(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
resetForm();
|
resetForm();
|
||||||
onClose();
|
onClose();
|
||||||
@@ -111,7 +111,10 @@ export const CodexQuickWizardModal: React.FC<CodexQuickWizardModalProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={(open) => !open && handleClose()}>
|
<Dialog open={isOpen} onOpenChange={(open) => !open && handleClose()}>
|
||||||
<DialogContent zIndex="nested" className="max-w-2xl max-h-[90vh] flex flex-col p-0">
|
<DialogContent
|
||||||
|
zIndex="nested"
|
||||||
|
className="max-w-2xl max-h-[90vh] flex flex-col p-0"
|
||||||
|
>
|
||||||
<DialogHeader className="px-6 pt-6 pb-0">
|
<DialogHeader className="px-6 pt-6 pb-0">
|
||||||
<DialogTitle>{t("codexConfig.quickWizard")}</DialogTitle>
|
<DialogTitle>{t("codexConfig.quickWizard")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -108,7 +108,10 @@ export function CommonConfigEditor({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog open={isModalOpen} onOpenChange={(open) => !open && onModalClose()}>
|
<Dialog
|
||||||
|
open={isModalOpen}
|
||||||
|
onOpenChange={(open) => !open && onModalClose()}
|
||||||
|
>
|
||||||
<DialogContent className="sm:max-w-[600px]">
|
<DialogContent className="sm:max-w-[600px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
@@ -120,8 +123,7 @@ export function CommonConfigEditor({
|
|||||||
<div className="space-y-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t("claudeConfig.commonConfigHint", {
|
{t("claudeConfig.commonConfigHint", {
|
||||||
defaultValue:
|
defaultValue: "通用配置片段将合并到所有启用它的供应商配置中",
|
||||||
"通用配置片段将合并到所有启用它的供应商配置中",
|
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
|
||||||
|
|
||||||
// 临时类型定义,待后端 API 实现后替换
|
// 临时类型定义,待后端 API 实现后替换
|
||||||
interface CustomEndpoint {
|
interface CustomEndpoint {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -118,12 +117,17 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
try {
|
try {
|
||||||
if (!providerId) return;
|
if (!providerId) return;
|
||||||
|
|
||||||
const customEndpoints = await vscodeApi.getCustomEndpoints(appType, providerId);
|
const customEndpoints = await vscodeApi.getCustomEndpoints(
|
||||||
|
appType,
|
||||||
|
providerId,
|
||||||
|
);
|
||||||
|
|
||||||
const candidates: EndpointCandidate[] = customEndpoints.map((ep: CustomEndpoint) => ({
|
const candidates: EndpointCandidate[] = customEndpoints.map(
|
||||||
url: ep.url,
|
(ep: CustomEndpoint) => ({
|
||||||
isCustom: true,
|
url: ep.url,
|
||||||
}));
|
isCustom: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
setEntries((prev) => {
|
setEntries((prev) => {
|
||||||
const map = new Map<string, EndpointEntry>();
|
const map = new Map<string, EndpointEntry>();
|
||||||
@@ -391,7 +395,9 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
|
|
||||||
if (autoSelect) {
|
if (autoSelect) {
|
||||||
const successful = results
|
const successful = results
|
||||||
.filter((item) => typeof item.latency === "number" && item.latency !== null)
|
.filter(
|
||||||
|
(item) => typeof item.latency === "number" && item.latency !== null,
|
||||||
|
)
|
||||||
.sort((a, b) => (a.latency! || 0) - (b.latency! || 0));
|
.sort((a, b) => (a.latency! || 0) - (b.latency! || 0));
|
||||||
const best = successful[0];
|
const best = successful[0];
|
||||||
if (best && best.url && best.url !== normalizedSelected) {
|
if (best && best.url && best.url !== normalizedSelected) {
|
||||||
@@ -430,7 +436,10 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={visible} onOpenChange={(open) => !open && onClose()}>
|
<Dialog open={visible} onOpenChange={(open) => !open && onClose()}>
|
||||||
<DialogContent zIndex="nested" className="max-w-2xl max-h-[80vh] flex flex-col p-0">
|
<DialogContent
|
||||||
|
zIndex="nested"
|
||||||
|
className="max-w-2xl max-h-[80vh] flex flex-col p-0"
|
||||||
|
>
|
||||||
<DialogHeader className="px-6 pt-6 pb-0">
|
<DialogHeader className="px-6 pt-6 pb-0">
|
||||||
<DialogTitle>{t("endpointTest.title")}</DialogTitle>
|
<DialogTitle>{t("endpointTest.title")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export function ProviderForm({
|
|||||||
const isEditMode = Boolean(initialData);
|
const isEditMode = Boolean(initialData);
|
||||||
|
|
||||||
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(
|
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(
|
||||||
initialData ? null : "custom"
|
initialData ? null : "custom",
|
||||||
);
|
);
|
||||||
const [activePreset, setActivePreset] = useState<{
|
const [activePreset, setActivePreset] = useState<{
|
||||||
id: string;
|
id: string;
|
||||||
@@ -76,7 +76,7 @@ export function ProviderForm({
|
|||||||
|
|
||||||
// 新建供应商:收集端点测速弹窗中的"自定义端点",提交时一次性落盘到 meta.custom_endpoints
|
// 新建供应商:收集端点测速弹窗中的"自定义端点",提交时一次性落盘到 meta.custom_endpoints
|
||||||
const [draftCustomEndpoints, setDraftCustomEndpoints] = useState<string[]>(
|
const [draftCustomEndpoints, setDraftCustomEndpoints] = useState<string[]>(
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 使用 category hook
|
// 使用 category hook
|
||||||
@@ -101,7 +101,7 @@ export function ProviderForm({
|
|||||||
? CODEX_DEFAULT_CONFIG
|
? CODEX_DEFAULT_CONFIG
|
||||||
: CLAUDE_DEFAULT_CONFIG,
|
: CLAUDE_DEFAULT_CONFIG,
|
||||||
}),
|
}),
|
||||||
[initialData, appType]
|
[initialData, appType],
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = useForm<ProviderFormData>({
|
const form = useForm<ProviderFormData>({
|
||||||
@@ -155,13 +155,17 @@ export function ProviderForm({
|
|||||||
} = useCodexConfigState({ initialData });
|
} = useCodexConfigState({ initialData });
|
||||||
|
|
||||||
// 使用 Codex TOML 校验 hook (仅 Codex 模式)
|
// 使用 Codex TOML 校验 hook (仅 Codex 模式)
|
||||||
const { configError: codexConfigError, debouncedValidate } = useCodexTomlValidation();
|
const { configError: codexConfigError, debouncedValidate } =
|
||||||
|
useCodexTomlValidation();
|
||||||
|
|
||||||
// 包装 handleCodexConfigChange,添加实时校验
|
// 包装 handleCodexConfigChange,添加实时校验
|
||||||
const handleCodexConfigChange = useCallback((value: string) => {
|
const handleCodexConfigChange = useCallback(
|
||||||
originalHandleCodexConfigChange(value);
|
(value: string) => {
|
||||||
debouncedValidate(value);
|
originalHandleCodexConfigChange(value);
|
||||||
}, [originalHandleCodexConfigChange, debouncedValidate]);
|
debouncedValidate(value);
|
||||||
|
},
|
||||||
|
[originalHandleCodexConfigChange, debouncedValidate],
|
||||||
|
);
|
||||||
|
|
||||||
const [isCodexEndpointModalOpen, setIsCodexEndpointModalOpen] =
|
const [isCodexEndpointModalOpen, setIsCodexEndpointModalOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
@@ -187,7 +191,7 @@ export function ProviderForm({
|
|||||||
defaultValue: "第三方",
|
defaultValue: "第三方",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[t]
|
[t],
|
||||||
);
|
);
|
||||||
|
|
||||||
const presetEntries = useMemo(() => {
|
const presetEntries = useMemo(() => {
|
||||||
@@ -334,7 +338,7 @@ export function ProviderForm({
|
|||||||
|
|
||||||
const categoryKeys = useMemo(() => {
|
const categoryKeys = useMemo(() => {
|
||||||
return Object.keys(groupedPresets).filter(
|
return Object.keys(groupedPresets).filter(
|
||||||
(key) => key !== "custom" && groupedPresets[key]?.length
|
(key) => key !== "custom" && groupedPresets[key]?.length,
|
||||||
);
|
);
|
||||||
}, [groupedPresets]);
|
}, [groupedPresets]);
|
||||||
|
|
||||||
@@ -429,7 +433,7 @@ export function ProviderForm({
|
|||||||
const preset = entry.preset as ProviderPreset;
|
const preset = entry.preset as ProviderPreset;
|
||||||
const config = applyTemplateValues(
|
const config = applyTemplateValues(
|
||||||
preset.settingsConfig,
|
preset.settingsConfig,
|
||||||
preset.templateValues
|
preset.templateValues,
|
||||||
);
|
);
|
||||||
|
|
||||||
form.reset({
|
form.reset({
|
||||||
@@ -461,7 +465,7 @@ export function ProviderForm({
|
|||||||
<ClaudeFormFields
|
<ClaudeFormFields
|
||||||
shouldShowApiKey={shouldShowApiKey(
|
shouldShowApiKey={shouldShowApiKey(
|
||||||
form.watch("settingsConfig"),
|
form.watch("settingsConfig"),
|
||||||
isEditMode
|
isEditMode,
|
||||||
)}
|
)}
|
||||||
apiKey={apiKey}
|
apiKey={apiKey}
|
||||||
onApiKeyChange={handleApiKeyChange}
|
onApiKeyChange={handleApiKeyChange}
|
||||||
@@ -479,7 +483,9 @@ export function ProviderForm({
|
|||||||
onEndpointModalToggle={setIsEndpointModalOpen}
|
onEndpointModalToggle={setIsEndpointModalOpen}
|
||||||
onCustomEndpointsChange={setDraftCustomEndpoints}
|
onCustomEndpointsChange={setDraftCustomEndpoints}
|
||||||
shouldShowKimiSelector={shouldShowKimiSelector}
|
shouldShowKimiSelector={shouldShowKimiSelector}
|
||||||
shouldShowModelSelector={category !== "official" && !shouldShowKimiSelector}
|
shouldShowModelSelector={
|
||||||
|
category !== "official" && !shouldShowKimiSelector
|
||||||
|
}
|
||||||
claudeModel={claudeModel}
|
claudeModel={claudeModel}
|
||||||
claudeSmallFastModel={claudeSmallFastModel}
|
claudeSmallFastModel={claudeSmallFastModel}
|
||||||
onModelChange={handleModelChange}
|
onModelChange={handleModelChange}
|
||||||
|
|||||||
@@ -53,11 +53,12 @@ export function useApiKeyLink({
|
|||||||
}, [selectedPresetId, presetEntries, formWebsiteUrl]);
|
}, [selectedPresetId, presetEntries, formWebsiteUrl]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shouldShowApiKeyLink: appType === "claude"
|
shouldShowApiKeyLink:
|
||||||
? shouldShowApiKeyLink
|
appType === "claude"
|
||||||
: appType === "codex"
|
|
||||||
? shouldShowApiKeyLink
|
? shouldShowApiKeyLink
|
||||||
: false,
|
: appType === "codex"
|
||||||
|
? shouldShowApiKeyLink
|
||||||
|
: false,
|
||||||
websiteUrl: getWebsiteUrl,
|
websiteUrl: getWebsiteUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { useState, useCallback, useRef, useEffect } from "react";
|
import { useState, useCallback, useRef, useEffect } from "react";
|
||||||
import { extractCodexBaseUrl, setCodexBaseUrl as setCodexBaseUrlInConfig } from "@/utils/providerConfigUtils";
|
import {
|
||||||
|
extractCodexBaseUrl,
|
||||||
|
setCodexBaseUrl as setCodexBaseUrlInConfig,
|
||||||
|
} from "@/utils/providerConfigUtils";
|
||||||
import type { ProviderCategory } from "@/types";
|
import type { ProviderCategory } from "@/types";
|
||||||
|
|
||||||
interface UseBaseUrlStateProps {
|
interface UseBaseUrlStateProps {
|
||||||
@@ -93,7 +96,10 @@ export function useBaseUrlState({
|
|||||||
}
|
}
|
||||||
|
|
||||||
isUpdatingRef.current = true;
|
isUpdatingRef.current = true;
|
||||||
const updatedConfig = setCodexBaseUrlInConfig(codexConfig || "", sanitized);
|
const updatedConfig = setCodexBaseUrlInConfig(
|
||||||
|
codexConfig || "",
|
||||||
|
sanitized,
|
||||||
|
);
|
||||||
onCodexConfigChange(updatedConfig);
|
onCodexConfigChange(updatedConfig);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ export function useCodexCommonConfig({
|
|||||||
return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET;
|
return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const stored = window.localStorage.getItem(CODEX_COMMON_CONFIG_STORAGE_KEY);
|
const stored = window.localStorage.getItem(
|
||||||
|
CODEX_COMMON_CONFIG_STORAGE_KEY,
|
||||||
|
);
|
||||||
if (stored && stored.trim()) {
|
if (stored && stored.trim()) {
|
||||||
return stored;
|
return stored;
|
||||||
}
|
}
|
||||||
@@ -78,11 +80,12 @@ export function useCodexCommonConfig({
|
|||||||
// 处理通用配置开关
|
// 处理通用配置开关
|
||||||
const handleCommonConfigToggle = useCallback(
|
const handleCommonConfigToggle = useCallback(
|
||||||
(checked: boolean) => {
|
(checked: boolean) => {
|
||||||
const { updatedConfig, error: snippetError } = updateTomlCommonConfigSnippet(
|
const { updatedConfig, error: snippetError } =
|
||||||
codexConfig,
|
updateTomlCommonConfigSnippet(
|
||||||
commonConfigSnippet,
|
codexConfig,
|
||||||
checked,
|
commonConfigSnippet,
|
||||||
);
|
checked,
|
||||||
|
);
|
||||||
|
|
||||||
if (snippetError) {
|
if (snippetError) {
|
||||||
setCommonConfigError(snippetError);
|
setCommonConfigError(snippetError);
|
||||||
@@ -157,12 +160,7 @@ export function useCodexCommonConfig({
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[commonConfigSnippet, codexConfig, useCommonConfig, onConfigChange],
|
||||||
commonConfigSnippet,
|
|
||||||
codexConfig,
|
|
||||||
useCommonConfig,
|
|
||||||
onConfigChange,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { useState, useCallback, useEffect, useRef } from "react";
|
import { useState, useCallback, useEffect, useRef } from "react";
|
||||||
import { extractCodexBaseUrl, setCodexBaseUrl as setCodexBaseUrlInConfig } from "@/utils/providerConfigUtils";
|
import {
|
||||||
|
extractCodexBaseUrl,
|
||||||
|
setCodexBaseUrl as setCodexBaseUrlInConfig,
|
||||||
|
} from "@/utils/providerConfigUtils";
|
||||||
|
|
||||||
interface UseCodexConfigStateProps {
|
interface UseCodexConfigStateProps {
|
||||||
initialData?: {
|
initialData?: {
|
||||||
@@ -31,7 +34,10 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
|
|||||||
setCodexAuthState(JSON.stringify(auth, null, 2));
|
setCodexAuthState(JSON.stringify(auth, null, 2));
|
||||||
|
|
||||||
// 设置 config.toml
|
// 设置 config.toml
|
||||||
const configStr = typeof (config as any).config === "string" ? (config as any).config : "";
|
const configStr =
|
||||||
|
typeof (config as any).config === "string"
|
||||||
|
? (config as any).config
|
||||||
|
: "";
|
||||||
setCodexConfigState(configStr);
|
setCodexConfigState(configStr);
|
||||||
|
|
||||||
// 提取 Base URL
|
// 提取 Base URL
|
||||||
@@ -77,82 +83,100 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 设置 auth 并验证
|
// 设置 auth 并验证
|
||||||
const setCodexAuth = useCallback((value: string) => {
|
const setCodexAuth = useCallback(
|
||||||
setCodexAuthState(value);
|
(value: string) => {
|
||||||
setCodexAuthError(validateCodexAuth(value));
|
setCodexAuthState(value);
|
||||||
}, [validateCodexAuth]);
|
setCodexAuthError(validateCodexAuth(value));
|
||||||
|
},
|
||||||
|
[validateCodexAuth],
|
||||||
|
);
|
||||||
|
|
||||||
// 设置 config (支持函数更新)
|
// 设置 config (支持函数更新)
|
||||||
const setCodexConfig = useCallback((value: string | ((prev: string) => string)) => {
|
const setCodexConfig = useCallback(
|
||||||
setCodexConfigState((prev) =>
|
(value: string | ((prev: string) => string)) => {
|
||||||
typeof value === "function"
|
setCodexConfigState((prev) =>
|
||||||
? (value as (input: string) => string)(prev)
|
typeof value === "function"
|
||||||
: value,
|
? (value as (input: string) => string)(prev)
|
||||||
);
|
: value,
|
||||||
}, []);
|
);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
// 处理 Codex API Key 输入并写回 auth.json
|
// 处理 Codex API Key 输入并写回 auth.json
|
||||||
const handleCodexApiKeyChange = useCallback((key: string) => {
|
const handleCodexApiKeyChange = useCallback(
|
||||||
setCodexApiKey(key);
|
(key: string) => {
|
||||||
try {
|
setCodexApiKey(key);
|
||||||
const auth = JSON.parse(codexAuth || "{}");
|
try {
|
||||||
auth.OPENAI_API_KEY = key.trim();
|
const auth = JSON.parse(codexAuth || "{}");
|
||||||
setCodexAuth(JSON.stringify(auth, null, 2));
|
auth.OPENAI_API_KEY = key.trim();
|
||||||
} catch {
|
setCodexAuth(JSON.stringify(auth, null, 2));
|
||||||
// ignore
|
} catch {
|
||||||
}
|
// ignore
|
||||||
}, [codexAuth, setCodexAuth]);
|
}
|
||||||
|
},
|
||||||
|
[codexAuth, setCodexAuth],
|
||||||
|
);
|
||||||
|
|
||||||
// 处理 Codex Base URL 变化
|
// 处理 Codex Base URL 变化
|
||||||
const handleCodexBaseUrlChange = useCallback((url: string) => {
|
const handleCodexBaseUrlChange = useCallback(
|
||||||
const sanitized = url.trim().replace(/\/+$/, "");
|
(url: string) => {
|
||||||
setCodexBaseUrl(sanitized);
|
const sanitized = url.trim().replace(/\/+$/, "");
|
||||||
|
setCodexBaseUrl(sanitized);
|
||||||
|
|
||||||
if (!sanitized) {
|
if (!sanitized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isUpdatingCodexBaseUrlRef.current = true;
|
isUpdatingCodexBaseUrlRef.current = true;
|
||||||
setCodexConfig((prev) => setCodexBaseUrlInConfig(prev, sanitized));
|
setCodexConfig((prev) => setCodexBaseUrlInConfig(prev, sanitized));
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isUpdatingCodexBaseUrlRef.current = false;
|
isUpdatingCodexBaseUrlRef.current = false;
|
||||||
}, 0);
|
}, 0);
|
||||||
}, [setCodexConfig]);
|
},
|
||||||
|
[setCodexConfig],
|
||||||
|
);
|
||||||
|
|
||||||
// 处理 config 变化(同步 Base URL)
|
// 处理 config 变化(同步 Base URL)
|
||||||
const handleCodexConfigChange = useCallback((value: string) => {
|
const handleCodexConfigChange = useCallback(
|
||||||
setCodexConfig(value);
|
(value: string) => {
|
||||||
|
setCodexConfig(value);
|
||||||
|
|
||||||
if (!isUpdatingCodexBaseUrlRef.current) {
|
if (!isUpdatingCodexBaseUrlRef.current) {
|
||||||
const extracted = extractCodexBaseUrl(value) || "";
|
const extracted = extractCodexBaseUrl(value) || "";
|
||||||
if (extracted !== codexBaseUrl) {
|
if (extracted !== codexBaseUrl) {
|
||||||
setCodexBaseUrl(extracted);
|
setCodexBaseUrl(extracted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, [setCodexConfig, codexBaseUrl]);
|
[setCodexConfig, codexBaseUrl],
|
||||||
|
);
|
||||||
|
|
||||||
// 重置配置(用于预设切换)
|
// 重置配置(用于预设切换)
|
||||||
const resetCodexConfig = useCallback((auth: Record<string, unknown>, config: string) => {
|
const resetCodexConfig = useCallback(
|
||||||
const authString = JSON.stringify(auth, null, 2);
|
(auth: Record<string, unknown>, config: string) => {
|
||||||
setCodexAuth(authString);
|
const authString = JSON.stringify(auth, null, 2);
|
||||||
setCodexConfig(config);
|
setCodexAuth(authString);
|
||||||
|
setCodexConfig(config);
|
||||||
|
|
||||||
const baseUrl = extractCodexBaseUrl(config);
|
const baseUrl = extractCodexBaseUrl(config);
|
||||||
if (baseUrl) {
|
if (baseUrl) {
|
||||||
setCodexBaseUrl(baseUrl);
|
setCodexBaseUrl(baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取 API Key
|
// 提取 API Key
|
||||||
try {
|
try {
|
||||||
if (auth && typeof auth.OPENAI_API_KEY === "string") {
|
if (auth && typeof auth.OPENAI_API_KEY === "string") {
|
||||||
setCodexApiKey(auth.OPENAI_API_KEY);
|
setCodexApiKey(auth.OPENAI_API_KEY);
|
||||||
} else {
|
} else {
|
||||||
|
setCodexApiKey("");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
setCodexApiKey("");
|
setCodexApiKey("");
|
||||||
}
|
}
|
||||||
} catch {
|
},
|
||||||
setCodexApiKey("");
|
[setCodexAuth, setCodexConfig],
|
||||||
}
|
);
|
||||||
}, [setCodexAuth, setCodexConfig]);
|
|
||||||
|
|
||||||
// 获取 API Key(从 auth JSON)
|
// 获取 API Key(从 auth JSON)
|
||||||
const getCodexAuthApiKey = useCallback((authString: string): string => {
|
const getCodexAuthApiKey = useCallback((authString: string): string => {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
import { useState, useCallback, useEffect, useRef } from "react";
|
||||||
import TOML from 'smol-toml';
|
import TOML from "smol-toml";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Codex config.toml 格式校验 Hook
|
* Codex config.toml 格式校验 Hook
|
||||||
* 使用 smol-toml 进行实时 TOML 语法校验(带 debounce)
|
* 使用 smol-toml 进行实时 TOML 语法校验(带 debounce)
|
||||||
*/
|
*/
|
||||||
export function useCodexTomlValidation() {
|
export function useCodexTomlValidation() {
|
||||||
const [configError, setConfigError] = useState('');
|
const [configError, setConfigError] = useState("");
|
||||||
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,18 +17,17 @@ export function useCodexTomlValidation() {
|
|||||||
const validateToml = useCallback((tomlText: string): boolean => {
|
const validateToml = useCallback((tomlText: string): boolean => {
|
||||||
// 空字符串视为合法(允许为空)
|
// 空字符串视为合法(允许为空)
|
||||||
if (!tomlText.trim()) {
|
if (!tomlText.trim()) {
|
||||||
setConfigError('');
|
setConfigError("");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TOML.parse(tomlText);
|
TOML.parse(tomlText);
|
||||||
setConfigError('');
|
setConfigError("");
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error
|
const errorMessage =
|
||||||
? error.message
|
error instanceof Error ? error.message : "TOML 格式错误";
|
||||||
: 'TOML 格式错误';
|
|
||||||
setConfigError(errorMessage);
|
setConfigError(errorMessage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -38,23 +37,26 @@ export function useCodexTomlValidation() {
|
|||||||
* 带 debounce 的校验函数(500ms 延迟)
|
* 带 debounce 的校验函数(500ms 延迟)
|
||||||
* @param tomlText - 待校验的 TOML 文本
|
* @param tomlText - 待校验的 TOML 文本
|
||||||
*/
|
*/
|
||||||
const debouncedValidate = useCallback((tomlText: string) => {
|
const debouncedValidate = useCallback(
|
||||||
// 清除之前的定时器
|
(tomlText: string) => {
|
||||||
if (debounceTimerRef.current) {
|
// 清除之前的定时器
|
||||||
clearTimeout(debounceTimerRef.current);
|
if (debounceTimerRef.current) {
|
||||||
}
|
clearTimeout(debounceTimerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
// 设置新的定时器
|
// 设置新的定时器
|
||||||
debounceTimerRef.current = setTimeout(() => {
|
debounceTimerRef.current = setTimeout(() => {
|
||||||
validateToml(tomlText);
|
validateToml(tomlText);
|
||||||
}, 500);
|
}, 500);
|
||||||
}, [validateToml]);
|
},
|
||||||
|
[validateToml],
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空错误信息
|
* 清空错误信息
|
||||||
*/
|
*/
|
||||||
const clearError = useCallback(() => {
|
const clearError = useCallback(() => {
|
||||||
setConfigError('');
|
setConfigError("");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 清理定时器
|
// 清理定时器
|
||||||
|
|||||||
@@ -51,11 +51,7 @@ export function useCommonConfigSnippet({
|
|||||||
// 初始化时检查通用配置片段(编辑模式)
|
// 初始化时检查通用配置片段(编辑模式)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialData) {
|
if (initialData) {
|
||||||
const configString = JSON.stringify(
|
const configString = JSON.stringify(initialData.settingsConfig, null, 2);
|
||||||
initialData.settingsConfig,
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
const hasCommon = hasCommonConfigSnippet(
|
const hasCommon = hasCommonConfigSnippet(
|
||||||
configString,
|
configString,
|
||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
@@ -168,12 +164,7 @@ export function useCommonConfigSnippet({
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[commonConfigSnippet, settingsConfig, useCommonConfig, onConfigChange],
|
||||||
commonConfigSnippet,
|
|
||||||
settingsConfig,
|
|
||||||
useCommonConfig,
|
|
||||||
onConfigChange,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ export function useKimiModelSelector({
|
|||||||
presetName = "",
|
presetName = "",
|
||||||
}: UseKimiModelSelectorProps) {
|
}: UseKimiModelSelectorProps) {
|
||||||
const [kimiAnthropicModel, setKimiAnthropicModel] = useState("");
|
const [kimiAnthropicModel, setKimiAnthropicModel] = useState("");
|
||||||
const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] = useState("");
|
const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] =
|
||||||
|
useState("");
|
||||||
|
|
||||||
// 判断是否显示 Kimi 模型选择器
|
// 判断是否显示 Kimi 模型选择器
|
||||||
const shouldShowKimiSelector =
|
const shouldShowKimiSelector =
|
||||||
@@ -32,23 +33,30 @@ export function useKimiModelSelector({
|
|||||||
// 判断是否正在编辑 Kimi 供应商
|
// 判断是否正在编辑 Kimi 供应商
|
||||||
const isEditingKimi = Boolean(
|
const isEditingKimi = Boolean(
|
||||||
initialData &&
|
initialData &&
|
||||||
(settingsConfig.includes("api.moonshot.cn") &&
|
settingsConfig.includes("api.moonshot.cn") &&
|
||||||
settingsConfig.includes("ANTHROPIC_MODEL"))
|
settingsConfig.includes("ANTHROPIC_MODEL"),
|
||||||
);
|
);
|
||||||
|
|
||||||
const shouldShow = shouldShowKimiSelector || isEditingKimi;
|
const shouldShow = shouldShowKimiSelector || isEditingKimi;
|
||||||
|
|
||||||
// 初始化 Kimi 模型选择(编辑模式)
|
// 初始化 Kimi 模型选择(编辑模式)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialData?.settingsConfig && typeof initialData.settingsConfig === "object") {
|
if (
|
||||||
const config = initialData.settingsConfig as { env?: Record<string, unknown> };
|
initialData?.settingsConfig &&
|
||||||
|
typeof initialData.settingsConfig === "object"
|
||||||
|
) {
|
||||||
|
const config = initialData.settingsConfig as {
|
||||||
|
env?: Record<string, unknown>;
|
||||||
|
};
|
||||||
if (config.env) {
|
if (config.env) {
|
||||||
const model = typeof config.env.ANTHROPIC_MODEL === "string"
|
const model =
|
||||||
? config.env.ANTHROPIC_MODEL
|
typeof config.env.ANTHROPIC_MODEL === "string"
|
||||||
: "";
|
? config.env.ANTHROPIC_MODEL
|
||||||
const smallFastModel = typeof config.env.ANTHROPIC_SMALL_FAST_MODEL === "string"
|
: "";
|
||||||
? config.env.ANTHROPIC_SMALL_FAST_MODEL
|
const smallFastModel =
|
||||||
: "";
|
typeof config.env.ANTHROPIC_SMALL_FAST_MODEL === "string"
|
||||||
|
? config.env.ANTHROPIC_SMALL_FAST_MODEL
|
||||||
|
: "";
|
||||||
setKimiAnthropicModel(model);
|
setKimiAnthropicModel(model);
|
||||||
setKimiAnthropicSmallFastModel(smallFastModel);
|
setKimiAnthropicSmallFastModel(smallFastModel);
|
||||||
}
|
}
|
||||||
@@ -57,7 +65,10 @@ export function useKimiModelSelector({
|
|||||||
|
|
||||||
// 处理 Kimi 模型变化
|
// 处理 Kimi 模型变化
|
||||||
const handleKimiModelChange = useCallback(
|
const handleKimiModelChange = useCallback(
|
||||||
(field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL", value: string) => {
|
(
|
||||||
|
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||||
|
value: string,
|
||||||
|
) => {
|
||||||
if (field === "ANTHROPIC_MODEL") {
|
if (field === "ANTHROPIC_MODEL") {
|
||||||
setKimiAnthropicModel(value);
|
setKimiAnthropicModel(value);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ export function useModelState({
|
|||||||
const [claudeSmallFastModel, setClaudeSmallFastModel] = useState("");
|
const [claudeSmallFastModel, setClaudeSmallFastModel] = useState("");
|
||||||
|
|
||||||
const handleModelChange = useCallback(
|
const handleModelChange = useCallback(
|
||||||
(field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL", value: string) => {
|
(
|
||||||
|
field: "ANTHROPIC_MODEL" | "ANTHROPIC_SMALL_FAST_MODEL",
|
||||||
|
value: string,
|
||||||
|
) => {
|
||||||
if (field === "ANTHROPIC_MODEL") {
|
if (field === "ANTHROPIC_MODEL") {
|
||||||
setClaudeModel(value);
|
setClaudeModel(value);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export function useSpeedTestEndpoints({
|
|||||||
// 添加预设自己的 baseUrl
|
// 添加预设自己的 baseUrl
|
||||||
const presetConfig = preset.config || "";
|
const presetConfig = preset.config || "";
|
||||||
const presetMatch = /base_url\s*=\s*["']([^"']+)["']/i.exec(
|
const presetMatch = /base_url\s*=\s*["']([^"']+)["']/i.exec(
|
||||||
presetConfig
|
presetConfig,
|
||||||
);
|
);
|
||||||
if (presetMatch?.[1]) {
|
if (presetMatch?.[1]) {
|
||||||
add(presetMatch[1]);
|
add(presetMatch[1]);
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import type { ProviderPreset, TemplateValueConfig } from "@/config/providerPresets";
|
import type {
|
||||||
|
ProviderPreset,
|
||||||
|
TemplateValueConfig,
|
||||||
|
} from "@/config/providerPresets";
|
||||||
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
||||||
import { applyTemplateValues } from "@/utils/providerConfigUtils";
|
import { applyTemplateValues } from "@/utils/providerConfigUtils";
|
||||||
|
|
||||||
|
|||||||
@@ -169,13 +169,13 @@ export function SettingsDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleDialogChange}>
|
<Dialog open={open} onOpenChange={handleDialogChange}>
|
||||||
<DialogContent className="max-w-3xl">
|
<DialogContent className="max-w-3xl max-h-[90vh] flex flex-col">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("settings.title")}</DialogTitle>
|
<DialogTitle>{t("settings.title")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{isBusy ? (
|
{isBusy ? (
|
||||||
<div className="flex min-h-[320px] items-center justify-center px-6">
|
<div className="flex min-h-[320px] items-center justify-center">
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -195,7 +195,10 @@ export function SettingsDialog({
|
|||||||
<TabsTrigger value="about">{t("common.about")}</TabsTrigger>
|
<TabsTrigger value="about">{t("common.about")}</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="general" className="space-y-6 mt-6 min-h-[400px]">
|
<TabsContent
|
||||||
|
value="general"
|
||||||
|
className="space-y-6 mt-6 min-h-[400px]"
|
||||||
|
>
|
||||||
{settings ? (
|
{settings ? (
|
||||||
<>
|
<>
|
||||||
<LanguageSettings
|
<LanguageSettings
|
||||||
@@ -211,7 +214,10 @@ export function SettingsDialog({
|
|||||||
) : null}
|
) : null}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="advanced" className="space-y-6 mt-6 min-h-[400px]">
|
<TabsContent
|
||||||
|
value="advanced"
|
||||||
|
className="space-y-6 mt-6 min-h-[400px]"
|
||||||
|
>
|
||||||
{settings ? (
|
{settings ? (
|
||||||
<>
|
<>
|
||||||
<DirectorySettings
|
<DirectorySettings
|
||||||
@@ -268,7 +274,10 @@ export function SettingsDialog({
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<Dialog open={showRestartPrompt} onOpenChange={(open) => !open && handleRestartLater()}>
|
<Dialog
|
||||||
|
open={showRestartPrompt}
|
||||||
|
onOpenChange={(open) => !open && handleRestartLater()}
|
||||||
|
>
|
||||||
<DialogContent zIndex="alert" className="max-w-md">
|
<DialogContent zIndex="alert" className="max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("settings.restartRequired")}</DialogTitle>
|
<DialogTitle>{t("settings.restartRequired")}</DialogTitle>
|
||||||
|
|||||||
@@ -52,7 +52,12 @@ interface ThemeButtonProps {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ThemeButton({ active, onClick, icon: Icon, children }: ThemeButtonProps) {
|
function ThemeButton({
|
||||||
|
active,
|
||||||
|
onClick,
|
||||||
|
icon: Icon,
|
||||||
|
children,
|
||||||
|
}: ThemeButtonProps) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -66,10 +66,9 @@ export function useMcpActions(appType: AppType): UseMcpActionsResult {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await mcpApi.setEnabled(appType, id, enabled);
|
await mcpApi.setEnabled(appType, id, enabled);
|
||||||
toast.success(
|
toast.success(enabled ? t("mcp.msg.enabled") : t("mcp.msg.disabled"), {
|
||||||
enabled ? t("mcp.msg.enabled") : t("mcp.msg.disabled"),
|
duration: 1500,
|
||||||
{ duration: 1500 },
|
});
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Rollback on failure
|
// Rollback on failure
|
||||||
setServers(previousServers);
|
setServers(previousServers);
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import { toast } from "sonner";
|
|||||||
import { settingsApi, type AppType } from "@/lib/api";
|
import { settingsApi, type AppType } from "@/lib/api";
|
||||||
import { useSettingsQuery, useSaveSettingsMutation } from "@/lib/query";
|
import { useSettingsQuery, useSaveSettingsMutation } from "@/lib/query";
|
||||||
import type { Settings } from "@/types";
|
import type { Settings } from "@/types";
|
||||||
import {
|
import { useSettingsForm, type SettingsFormState } from "./useSettingsForm";
|
||||||
useSettingsForm,
|
|
||||||
type SettingsFormState,
|
|
||||||
} from "./useSettingsForm";
|
|
||||||
import {
|
import {
|
||||||
useDirectorySettings,
|
useDirectorySettings,
|
||||||
type ResolvedDirectories,
|
type ResolvedDirectories,
|
||||||
|
|||||||
@@ -33,10 +33,7 @@ export function useSettingsMetadata(): UseSettingsMetadataResult {
|
|||||||
|
|
||||||
setIsPortable(portable);
|
setIsPortable(portable);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error("[useSettingsMetadata] Failed to load metadata", error);
|
||||||
"[useSettingsMetadata] Failed to load metadata",
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (active) {
|
if (active) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { useQuery, type UseQueryResult, keepPreviousData } from "@tanstack/react-query";
|
import {
|
||||||
|
useQuery,
|
||||||
|
type UseQueryResult,
|
||||||
|
keepPreviousData,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
import { providersApi, settingsApi, usageApi, type AppType } from "@/lib/api";
|
import { providersApi, settingsApi, usageApi, type AppType } from "@/lib/api";
|
||||||
import type { Provider, Settings, UsageResult } from "@/types";
|
import type { Provider, Settings, UsageResult } from "@/types";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user