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:
@@ -53,11 +53,12 @@ export function useApiKeyLink({
|
||||
}, [selectedPresetId, presetEntries, formWebsiteUrl]);
|
||||
|
||||
return {
|
||||
shouldShowApiKeyLink: appType === "claude"
|
||||
? shouldShowApiKeyLink
|
||||
: appType === "codex"
|
||||
shouldShowApiKeyLink:
|
||||
appType === "claude"
|
||||
? shouldShowApiKeyLink
|
||||
: false,
|
||||
: appType === "codex"
|
||||
? shouldShowApiKeyLink
|
||||
: false,
|
||||
websiteUrl: getWebsiteUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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";
|
||||
|
||||
interface UseBaseUrlStateProps {
|
||||
@@ -93,7 +96,10 @@ export function useBaseUrlState({
|
||||
}
|
||||
|
||||
isUpdatingRef.current = true;
|
||||
const updatedConfig = setCodexBaseUrlInConfig(codexConfig || "", sanitized);
|
||||
const updatedConfig = setCodexBaseUrlInConfig(
|
||||
codexConfig || "",
|
||||
sanitized,
|
||||
);
|
||||
onCodexConfigChange(updatedConfig);
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -31,7 +31,9 @@ export function useCodexCommonConfig({
|
||||
return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET;
|
||||
}
|
||||
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()) {
|
||||
return stored;
|
||||
}
|
||||
@@ -78,11 +80,12 @@ export function useCodexCommonConfig({
|
||||
// 处理通用配置开关
|
||||
const handleCommonConfigToggle = useCallback(
|
||||
(checked: boolean) => {
|
||||
const { updatedConfig, error: snippetError } = updateTomlCommonConfigSnippet(
|
||||
codexConfig,
|
||||
commonConfigSnippet,
|
||||
checked,
|
||||
);
|
||||
const { updatedConfig, error: snippetError } =
|
||||
updateTomlCommonConfigSnippet(
|
||||
codexConfig,
|
||||
commonConfigSnippet,
|
||||
checked,
|
||||
);
|
||||
|
||||
if (snippetError) {
|
||||
setCommonConfigError(snippetError);
|
||||
@@ -157,12 +160,7 @@ export function useCodexCommonConfig({
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
[
|
||||
commonConfigSnippet,
|
||||
codexConfig,
|
||||
useCommonConfig,
|
||||
onConfigChange,
|
||||
],
|
||||
[commonConfigSnippet, codexConfig, useCommonConfig, onConfigChange],
|
||||
);
|
||||
|
||||
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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 {
|
||||
initialData?: {
|
||||
@@ -31,7 +34,10 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
|
||||
setCodexAuthState(JSON.stringify(auth, null, 2));
|
||||
|
||||
// 设置 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);
|
||||
|
||||
// 提取 Base URL
|
||||
@@ -77,82 +83,100 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
|
||||
}, []);
|
||||
|
||||
// 设置 auth 并验证
|
||||
const setCodexAuth = useCallback((value: string) => {
|
||||
setCodexAuthState(value);
|
||||
setCodexAuthError(validateCodexAuth(value));
|
||||
}, [validateCodexAuth]);
|
||||
const setCodexAuth = useCallback(
|
||||
(value: string) => {
|
||||
setCodexAuthState(value);
|
||||
setCodexAuthError(validateCodexAuth(value));
|
||||
},
|
||||
[validateCodexAuth],
|
||||
);
|
||||
|
||||
// 设置 config (支持函数更新)
|
||||
const setCodexConfig = useCallback((value: string | ((prev: string) => string)) => {
|
||||
setCodexConfigState((prev) =>
|
||||
typeof value === "function"
|
||||
? (value as (input: string) => string)(prev)
|
||||
: value,
|
||||
);
|
||||
}, []);
|
||||
const setCodexConfig = useCallback(
|
||||
(value: string | ((prev: string) => string)) => {
|
||||
setCodexConfigState((prev) =>
|
||||
typeof value === "function"
|
||||
? (value as (input: string) => string)(prev)
|
||||
: value,
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// 处理 Codex API Key 输入并写回 auth.json
|
||||
const handleCodexApiKeyChange = useCallback((key: string) => {
|
||||
setCodexApiKey(key);
|
||||
try {
|
||||
const auth = JSON.parse(codexAuth || "{}");
|
||||
auth.OPENAI_API_KEY = key.trim();
|
||||
setCodexAuth(JSON.stringify(auth, null, 2));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, [codexAuth, setCodexAuth]);
|
||||
const handleCodexApiKeyChange = useCallback(
|
||||
(key: string) => {
|
||||
setCodexApiKey(key);
|
||||
try {
|
||||
const auth = JSON.parse(codexAuth || "{}");
|
||||
auth.OPENAI_API_KEY = key.trim();
|
||||
setCodexAuth(JSON.stringify(auth, null, 2));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
[codexAuth, setCodexAuth],
|
||||
);
|
||||
|
||||
// 处理 Codex Base URL 变化
|
||||
const handleCodexBaseUrlChange = useCallback((url: string) => {
|
||||
const sanitized = url.trim().replace(/\/+$/, "");
|
||||
setCodexBaseUrl(sanitized);
|
||||
const handleCodexBaseUrlChange = useCallback(
|
||||
(url: string) => {
|
||||
const sanitized = url.trim().replace(/\/+$/, "");
|
||||
setCodexBaseUrl(sanitized);
|
||||
|
||||
if (!sanitized) {
|
||||
return;
|
||||
}
|
||||
if (!sanitized) {
|
||||
return;
|
||||
}
|
||||
|
||||
isUpdatingCodexBaseUrlRef.current = true;
|
||||
setCodexConfig((prev) => setCodexBaseUrlInConfig(prev, sanitized));
|
||||
setTimeout(() => {
|
||||
isUpdatingCodexBaseUrlRef.current = false;
|
||||
}, 0);
|
||||
}, [setCodexConfig]);
|
||||
isUpdatingCodexBaseUrlRef.current = true;
|
||||
setCodexConfig((prev) => setCodexBaseUrlInConfig(prev, sanitized));
|
||||
setTimeout(() => {
|
||||
isUpdatingCodexBaseUrlRef.current = false;
|
||||
}, 0);
|
||||
},
|
||||
[setCodexConfig],
|
||||
);
|
||||
|
||||
// 处理 config 变化(同步 Base URL)
|
||||
const handleCodexConfigChange = useCallback((value: string) => {
|
||||
setCodexConfig(value);
|
||||
const handleCodexConfigChange = useCallback(
|
||||
(value: string) => {
|
||||
setCodexConfig(value);
|
||||
|
||||
if (!isUpdatingCodexBaseUrlRef.current) {
|
||||
const extracted = extractCodexBaseUrl(value) || "";
|
||||
if (extracted !== codexBaseUrl) {
|
||||
setCodexBaseUrl(extracted);
|
||||
if (!isUpdatingCodexBaseUrlRef.current) {
|
||||
const extracted = extractCodexBaseUrl(value) || "";
|
||||
if (extracted !== codexBaseUrl) {
|
||||
setCodexBaseUrl(extracted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [setCodexConfig, codexBaseUrl]);
|
||||
},
|
||||
[setCodexConfig, codexBaseUrl],
|
||||
);
|
||||
|
||||
// 重置配置(用于预设切换)
|
||||
const resetCodexConfig = useCallback((auth: Record<string, unknown>, config: string) => {
|
||||
const authString = JSON.stringify(auth, null, 2);
|
||||
setCodexAuth(authString);
|
||||
setCodexConfig(config);
|
||||
const resetCodexConfig = useCallback(
|
||||
(auth: Record<string, unknown>, config: string) => {
|
||||
const authString = JSON.stringify(auth, null, 2);
|
||||
setCodexAuth(authString);
|
||||
setCodexConfig(config);
|
||||
|
||||
const baseUrl = extractCodexBaseUrl(config);
|
||||
if (baseUrl) {
|
||||
setCodexBaseUrl(baseUrl);
|
||||
}
|
||||
const baseUrl = extractCodexBaseUrl(config);
|
||||
if (baseUrl) {
|
||||
setCodexBaseUrl(baseUrl);
|
||||
}
|
||||
|
||||
// 提取 API Key
|
||||
try {
|
||||
if (auth && typeof auth.OPENAI_API_KEY === "string") {
|
||||
setCodexApiKey(auth.OPENAI_API_KEY);
|
||||
} else {
|
||||
// 提取 API Key
|
||||
try {
|
||||
if (auth && typeof auth.OPENAI_API_KEY === "string") {
|
||||
setCodexApiKey(auth.OPENAI_API_KEY);
|
||||
} else {
|
||||
setCodexApiKey("");
|
||||
}
|
||||
} catch {
|
||||
setCodexApiKey("");
|
||||
}
|
||||
} catch {
|
||||
setCodexApiKey("");
|
||||
}
|
||||
}, [setCodexAuth, setCodexConfig]);
|
||||
},
|
||||
[setCodexAuth, setCodexConfig],
|
||||
);
|
||||
|
||||
// 获取 API Key(从 auth JSON)
|
||||
const getCodexAuthApiKey = useCallback((authString: string): string => {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import TOML from 'smol-toml';
|
||||
import { useState, useCallback, useEffect, useRef } from "react";
|
||||
import TOML from "smol-toml";
|
||||
|
||||
/**
|
||||
* Codex config.toml 格式校验 Hook
|
||||
* 使用 smol-toml 进行实时 TOML 语法校验(带 debounce)
|
||||
*/
|
||||
export function useCodexTomlValidation() {
|
||||
const [configError, setConfigError] = useState('');
|
||||
const [configError, setConfigError] = useState("");
|
||||
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
/**
|
||||
@@ -17,18 +17,17 @@ export function useCodexTomlValidation() {
|
||||
const validateToml = useCallback((tomlText: string): boolean => {
|
||||
// 空字符串视为合法(允许为空)
|
||||
if (!tomlText.trim()) {
|
||||
setConfigError('');
|
||||
setConfigError("");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
TOML.parse(tomlText);
|
||||
setConfigError('');
|
||||
setConfigError("");
|
||||
return true;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error
|
||||
? error.message
|
||||
: 'TOML 格式错误';
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "TOML 格式错误";
|
||||
setConfigError(errorMessage);
|
||||
return false;
|
||||
}
|
||||
@@ -38,23 +37,26 @@ export function useCodexTomlValidation() {
|
||||
* 带 debounce 的校验函数(500ms 延迟)
|
||||
* @param tomlText - 待校验的 TOML 文本
|
||||
*/
|
||||
const debouncedValidate = useCallback((tomlText: string) => {
|
||||
// 清除之前的定时器
|
||||
if (debounceTimerRef.current) {
|
||||
clearTimeout(debounceTimerRef.current);
|
||||
}
|
||||
const debouncedValidate = useCallback(
|
||||
(tomlText: string) => {
|
||||
// 清除之前的定时器
|
||||
if (debounceTimerRef.current) {
|
||||
clearTimeout(debounceTimerRef.current);
|
||||
}
|
||||
|
||||
// 设置新的定时器
|
||||
debounceTimerRef.current = setTimeout(() => {
|
||||
validateToml(tomlText);
|
||||
}, 500);
|
||||
}, [validateToml]);
|
||||
// 设置新的定时器
|
||||
debounceTimerRef.current = setTimeout(() => {
|
||||
validateToml(tomlText);
|
||||
}, 500);
|
||||
},
|
||||
[validateToml],
|
||||
);
|
||||
|
||||
/**
|
||||
* 清空错误信息
|
||||
*/
|
||||
const clearError = useCallback(() => {
|
||||
setConfigError('');
|
||||
setConfigError("");
|
||||
}, []);
|
||||
|
||||
// 清理定时器
|
||||
|
||||
@@ -51,11 +51,7 @@ export function useCommonConfigSnippet({
|
||||
// 初始化时检查通用配置片段(编辑模式)
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
const configString = JSON.stringify(
|
||||
initialData.settingsConfig,
|
||||
null,
|
||||
2,
|
||||
);
|
||||
const configString = JSON.stringify(initialData.settingsConfig, null, 2);
|
||||
const hasCommon = hasCommonConfigSnippet(
|
||||
configString,
|
||||
commonConfigSnippet,
|
||||
@@ -168,12 +164,7 @@ export function useCommonConfigSnippet({
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
[
|
||||
commonConfigSnippet,
|
||||
settingsConfig,
|
||||
useCommonConfig,
|
||||
onConfigChange,
|
||||
],
|
||||
[commonConfigSnippet, settingsConfig, useCommonConfig, onConfigChange],
|
||||
);
|
||||
|
||||
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
|
||||
|
||||
@@ -21,7 +21,8 @@ export function useKimiModelSelector({
|
||||
presetName = "",
|
||||
}: UseKimiModelSelectorProps) {
|
||||
const [kimiAnthropicModel, setKimiAnthropicModel] = useState("");
|
||||
const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] = useState("");
|
||||
const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] =
|
||||
useState("");
|
||||
|
||||
// 判断是否显示 Kimi 模型选择器
|
||||
const shouldShowKimiSelector =
|
||||
@@ -32,23 +33,30 @@ export function useKimiModelSelector({
|
||||
// 判断是否正在编辑 Kimi 供应商
|
||||
const isEditingKimi = Boolean(
|
||||
initialData &&
|
||||
(settingsConfig.includes("api.moonshot.cn") &&
|
||||
settingsConfig.includes("ANTHROPIC_MODEL"))
|
||||
settingsConfig.includes("api.moonshot.cn") &&
|
||||
settingsConfig.includes("ANTHROPIC_MODEL"),
|
||||
);
|
||||
|
||||
const shouldShow = shouldShowKimiSelector || isEditingKimi;
|
||||
|
||||
// 初始化 Kimi 模型选择(编辑模式)
|
||||
useEffect(() => {
|
||||
if (initialData?.settingsConfig && typeof initialData.settingsConfig === "object") {
|
||||
const config = initialData.settingsConfig as { env?: Record<string, unknown> };
|
||||
if (
|
||||
initialData?.settingsConfig &&
|
||||
typeof initialData.settingsConfig === "object"
|
||||
) {
|
||||
const config = initialData.settingsConfig as {
|
||||
env?: Record<string, unknown>;
|
||||
};
|
||||
if (config.env) {
|
||||
const 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 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
|
||||
: "";
|
||||
setKimiAnthropicModel(model);
|
||||
setKimiAnthropicSmallFastModel(smallFastModel);
|
||||
}
|
||||
@@ -57,7 +65,10 @@ export function useKimiModelSelector({
|
||||
|
||||
// 处理 Kimi 模型变化
|
||||
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") {
|
||||
setKimiAnthropicModel(value);
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,10 @@ export function useModelState({
|
||||
const [claudeSmallFastModel, setClaudeSmallFastModel] = useState("");
|
||||
|
||||
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") {
|
||||
setClaudeModel(value);
|
||||
} else {
|
||||
|
||||
@@ -122,7 +122,7 @@ export function useSpeedTestEndpoints({
|
||||
// 添加预设自己的 baseUrl
|
||||
const presetConfig = preset.config || "";
|
||||
const presetMatch = /base_url\s*=\s*["']([^"']+)["']/i.exec(
|
||||
presetConfig
|
||||
presetConfig,
|
||||
);
|
||||
if (presetMatch?.[1]) {
|
||||
add(presetMatch[1]);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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 { applyTemplateValues } from "@/utils/providerConfigUtils";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user