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:
Jason
2025-10-18 16:52:02 +08:00
parent 404297cd30
commit 57552b3159
31 changed files with 306 additions and 208 deletions

View File

@@ -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,
};
}

View File

@@ -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(() => {

View File

@@ -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],
);
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)

View File

@@ -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 => {

View File

@@ -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("");
}, []);
// 清理定时器

View File

@@ -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],
);
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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]);

View File

@@ -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";