fix: improve JSON validation with unified validation function
- Extract common validateJsonConfig function for reuse - Apply unified validation to both main config and common config snippets - Add real-time JSON validation to JsonEditor component using CodeMirror linter - Simplify error handling without over-engineering error position extraction
This commit is contained in:
@@ -10,6 +10,34 @@ use crate::config::{get_claude_settings_path, ConfigStatus};
|
|||||||
use crate::provider::Provider;
|
use crate::provider::Provider;
|
||||||
use crate::store::AppState;
|
use crate::store::AppState;
|
||||||
|
|
||||||
|
fn validate_provider_settings(app_type: &AppType, provider: &Provider) -> Result<(), String> {
|
||||||
|
match app_type {
|
||||||
|
AppType::Claude => {
|
||||||
|
if !provider.settings_config.is_object() {
|
||||||
|
return Err("Claude 配置必须是 JSON 对象".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppType::Codex => {
|
||||||
|
let settings = provider
|
||||||
|
.settings_config
|
||||||
|
.as_object()
|
||||||
|
.ok_or_else(|| "Codex 配置必须是 JSON 对象".to_string())?;
|
||||||
|
let auth = settings
|
||||||
|
.get("auth")
|
||||||
|
.ok_or_else(|| "Codex 配置缺少 auth 字段".to_string())?;
|
||||||
|
if !auth.is_object() {
|
||||||
|
return Err("Codex auth 配置必须是 JSON 对象".to_string());
|
||||||
|
}
|
||||||
|
if let Some(config_value) = settings.get("config") {
|
||||||
|
if !(config_value.is_string() || config_value.is_null()) {
|
||||||
|
return Err("Codex config 字段必须是字符串".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// 获取所有供应商
|
/// 获取所有供应商
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_providers(
|
pub async fn get_providers(
|
||||||
@@ -74,6 +102,8 @@ pub async fn add_provider(
|
|||||||
.or_else(|| appType.as_deref().map(|s| s.into()))
|
.or_else(|| appType.as_deref().map(|s| s.into()))
|
||||||
.unwrap_or(AppType::Claude);
|
.unwrap_or(AppType::Claude);
|
||||||
|
|
||||||
|
validate_provider_settings(&app_type, &provider)?;
|
||||||
|
|
||||||
// 读取当前是否是激活供应商(短锁)
|
// 读取当前是否是激活供应商(短锁)
|
||||||
let is_current = {
|
let is_current = {
|
||||||
let config = state
|
let config = state
|
||||||
@@ -139,6 +169,8 @@ pub async fn update_provider(
|
|||||||
.or_else(|| appType.as_deref().map(|s| s.into()))
|
.or_else(|| appType.as_deref().map(|s| s.into()))
|
||||||
.unwrap_or(AppType::Claude);
|
.unwrap_or(AppType::Claude);
|
||||||
|
|
||||||
|
validate_provider_settings(&app_type, &provider)?;
|
||||||
|
|
||||||
// 读取校验 & 是否当前(短锁)
|
// 读取校验 & 是否当前(短锁)
|
||||||
let (exists, is_current) = {
|
let (exists, is_current) = {
|
||||||
let config = state
|
let config = state
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { useRef, useEffect } from "react";
|
import React, { useRef, useEffect, useMemo } from "react";
|
||||||
import { EditorView, basicSetup } from "codemirror";
|
import { EditorView, basicSetup } from "codemirror";
|
||||||
import { json } from "@codemirror/lang-json";
|
import { json } from "@codemirror/lang-json";
|
||||||
import { oneDark } from "@codemirror/theme-one-dark";
|
import { oneDark } from "@codemirror/theme-one-dark";
|
||||||
import { EditorState } from "@codemirror/state";
|
import { EditorState } from "@codemirror/state";
|
||||||
import { placeholder } from "@codemirror/view";
|
import { placeholder } from "@codemirror/view";
|
||||||
|
import { linter, Diagnostic } from "@codemirror/lint";
|
||||||
|
|
||||||
interface JsonEditorProps {
|
interface JsonEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -11,6 +12,7 @@ interface JsonEditorProps {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
darkMode?: boolean;
|
darkMode?: boolean;
|
||||||
rows?: number;
|
rows?: number;
|
||||||
|
showValidation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const JsonEditor: React.FC<JsonEditorProps> = ({
|
const JsonEditor: React.FC<JsonEditorProps> = ({
|
||||||
@@ -19,10 +21,50 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
|||||||
placeholder: placeholderText = "",
|
placeholder: placeholderText = "",
|
||||||
darkMode = false,
|
darkMode = false,
|
||||||
rows = 12,
|
rows = 12,
|
||||||
|
showValidation = true,
|
||||||
}) => {
|
}) => {
|
||||||
const editorRef = useRef<HTMLDivElement>(null);
|
const editorRef = useRef<HTMLDivElement>(null);
|
||||||
const viewRef = useRef<EditorView | null>(null);
|
const viewRef = useRef<EditorView | null>(null);
|
||||||
|
|
||||||
|
// JSON linter 函数
|
||||||
|
const jsonLinter = useMemo(
|
||||||
|
() =>
|
||||||
|
linter((view) => {
|
||||||
|
const diagnostics: Diagnostic[] = [];
|
||||||
|
if (!showValidation) return diagnostics;
|
||||||
|
|
||||||
|
const doc = view.state.doc.toString();
|
||||||
|
if (!doc.trim()) return diagnostics;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(doc);
|
||||||
|
// 检查是否是JSON对象
|
||||||
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||||
|
// 格式正确
|
||||||
|
} else {
|
||||||
|
diagnostics.push({
|
||||||
|
from: 0,
|
||||||
|
to: doc.length,
|
||||||
|
severity: "error",
|
||||||
|
message: "配置必须是JSON对象,不能是数组或其他类型",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 简单处理JSON解析错误
|
||||||
|
const message = e instanceof SyntaxError ? e.message : "JSON格式错误";
|
||||||
|
diagnostics.push({
|
||||||
|
from: 0,
|
||||||
|
to: doc.length,
|
||||||
|
severity: "error",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return diagnostics;
|
||||||
|
}),
|
||||||
|
[showValidation]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editorRef.current) return;
|
if (!editorRef.current) return;
|
||||||
|
|
||||||
@@ -43,6 +85,7 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
|||||||
json(),
|
json(),
|
||||||
placeholder(placeholderText || ""),
|
placeholder(placeholderText || ""),
|
||||||
sizingTheme,
|
sizingTheme,
|
||||||
|
jsonLinter,
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
const newValue = update.state.doc.toString();
|
const newValue = update.state.doc.toString();
|
||||||
@@ -75,7 +118,7 @@ const JsonEditor: React.FC<JsonEditorProps> = ({
|
|||||||
view.destroy();
|
view.destroy();
|
||||||
viewRef.current = null;
|
viewRef.current = null;
|
||||||
};
|
};
|
||||||
}, [darkMode, rows]); // 依赖项中不包含 onChange 和 placeholder,避免不必要的重建
|
}, [darkMode, rows, jsonLinter]); // 依赖项中不包含 onChange 和 placeholder,避免不必要的重建
|
||||||
|
|
||||||
// 当 value 从外部改变时更新编辑器内容
|
// 当 value 从外部改变时更新编辑器内容
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
setApiKeyInConfig,
|
setApiKeyInConfig,
|
||||||
updateTomlCommonConfigSnippet,
|
updateTomlCommonConfigSnippet,
|
||||||
hasTomlCommonConfigSnippet,
|
hasTomlCommonConfigSnippet,
|
||||||
|
validateJsonConfig,
|
||||||
} from "../utils/providerConfigUtils";
|
} from "../utils/providerConfigUtils";
|
||||||
import { providerPresets } from "../config/providerPresets";
|
import { providerPresets } from "../config/providerPresets";
|
||||||
import { codexProviderPresets } from "../config/codexProviderPresets";
|
import { codexProviderPresets } from "../config/codexProviderPresets";
|
||||||
@@ -77,6 +78,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
|
|
||||||
const setCodexAuth = (value: string) => {
|
const setCodexAuth = (value: string) => {
|
||||||
setCodexAuthState(value);
|
setCodexAuthState(value);
|
||||||
|
setCodexAuthError(validateCodexAuth(value));
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCodexConfig = (value: string) => {
|
const setCodexConfig = (value: string) => {
|
||||||
@@ -123,6 +125,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
return DEFAULT_COMMON_CONFIG_SNIPPET;
|
return DEFAULT_COMMON_CONFIG_SNIPPET;
|
||||||
});
|
});
|
||||||
const [commonConfigError, setCommonConfigError] = useState("");
|
const [commonConfigError, setCommonConfigError] = useState("");
|
||||||
|
const [settingsConfigError, setSettingsConfigError] = useState("");
|
||||||
// 用于跟踪是否正在通过通用配置更新
|
// 用于跟踪是否正在通过通用配置更新
|
||||||
const isUpdatingFromCommonConfig = useRef(false);
|
const isUpdatingFromCommonConfig = useRef(false);
|
||||||
|
|
||||||
@@ -151,12 +154,40 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
showPresets ? -1 : null,
|
showPresets ? -1 : null,
|
||||||
);
|
);
|
||||||
const [apiKey, setApiKey] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
|
const [codexAuthError, setCodexAuthError] = useState("");
|
||||||
|
|
||||||
// Kimi 模型选择状态
|
// Kimi 模型选择状态
|
||||||
const [kimiAnthropicModel, setKimiAnthropicModel] = useState("");
|
const [kimiAnthropicModel, setKimiAnthropicModel] = useState("");
|
||||||
const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] =
|
const [kimiAnthropicSmallFastModel, setKimiAnthropicSmallFastModel] =
|
||||||
useState("");
|
useState("");
|
||||||
|
|
||||||
|
const validateSettingsConfig = (value: string): string => {
|
||||||
|
return validateJsonConfig(value, "配置内容");
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateCodexAuth = (value: string): string => {
|
||||||
|
if (!value.trim()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||||
|
return "auth.json 必须是 JSON 对象";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
} catch {
|
||||||
|
return "auth.json 格式错误,请检查JSON语法";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSettingsConfigValue = (value: string) => {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
settingsConfig: value,
|
||||||
|
}));
|
||||||
|
setSettingsConfigError(validateSettingsConfig(value));
|
||||||
|
};
|
||||||
|
|
||||||
// 初始化自定义模式的默认配置
|
// 初始化自定义模式的默认配置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -175,11 +206,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
// ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name"
|
// ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const templateString = JSON.stringify(customTemplate, null, 2);
|
||||||
|
|
||||||
setFormData((prev) => ({
|
updateSettingsConfigValue(templateString);
|
||||||
...prev,
|
|
||||||
settingsConfig: JSON.stringify(customTemplate, null, 2),
|
|
||||||
}));
|
|
||||||
setApiKey("");
|
setApiKey("");
|
||||||
}
|
}
|
||||||
}, []); // 只在组件挂载时执行一次
|
}, []); // 只在组件挂载时执行一次
|
||||||
@@ -194,6 +223,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
);
|
);
|
||||||
setUseCommonConfig(hasCommon);
|
setUseCommonConfig(hasCommon);
|
||||||
|
setSettingsConfigError(validateSettingsConfig(configString));
|
||||||
|
|
||||||
// 初始化模型配置(编辑模式)
|
// 初始化模型配置(编辑模式)
|
||||||
if (
|
if (
|
||||||
@@ -279,6 +309,12 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
let settingsConfig: Record<string, any>;
|
let settingsConfig: Record<string, any>;
|
||||||
|
|
||||||
if (isCodex) {
|
if (isCodex) {
|
||||||
|
const currentAuthError = validateCodexAuth(codexAuth);
|
||||||
|
setCodexAuthError(currentAuthError);
|
||||||
|
if (currentAuthError) {
|
||||||
|
setError(currentAuthError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Codex: 仅要求 auth.json 必填;config.toml 可为空
|
// Codex: 仅要求 auth.json 必填;config.toml 可为空
|
||||||
if (!codexAuth.trim()) {
|
if (!codexAuth.trim()) {
|
||||||
setError("请填写 auth.json 配置");
|
setError("请填写 auth.json 配置");
|
||||||
@@ -313,6 +349,14 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const currentSettingsError = validateSettingsConfig(
|
||||||
|
formData.settingsConfig,
|
||||||
|
);
|
||||||
|
setSettingsConfigError(currentSettingsError);
|
||||||
|
if (currentSettingsError) {
|
||||||
|
setError(currentSettingsError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Claude: 原有逻辑
|
// Claude: 原有逻辑
|
||||||
if (!formData.settingsConfig.trim()) {
|
if (!formData.settingsConfig.trim()) {
|
||||||
setError("请填写配置内容");
|
setError("请填写配置内容");
|
||||||
@@ -353,10 +397,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
setApiKey(parsedKey);
|
setApiKey(parsedKey);
|
||||||
|
|
||||||
// 不再从 JSON 自动提取或覆盖官网地址,只更新配置内容
|
// 不再从 JSON 自动提取或覆盖官网地址,只更新配置内容
|
||||||
setFormData((prev) => ({
|
updateSettingsConfigValue(value);
|
||||||
...prev,
|
|
||||||
[name]: value,
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
@@ -375,6 +416,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
|
|
||||||
if (snippetError) {
|
if (snippetError) {
|
||||||
setCommonConfigError(snippetError);
|
setCommonConfigError(snippetError);
|
||||||
|
if (snippetError.includes("配置 JSON 解析失败")) {
|
||||||
|
setSettingsConfigError("配置JSON格式错误,请检查语法");
|
||||||
|
}
|
||||||
setUseCommonConfig(false);
|
setUseCommonConfig(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -383,10 +427,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
setUseCommonConfig(checked);
|
setUseCommonConfig(checked);
|
||||||
// 标记正在通过通用配置更新
|
// 标记正在通过通用配置更新
|
||||||
isUpdatingFromCommonConfig.current = true;
|
isUpdatingFromCommonConfig.current = true;
|
||||||
setFormData((prev) => ({
|
updateSettingsConfigValue(updatedConfig);
|
||||||
...prev,
|
|
||||||
settingsConfig: updatedConfig,
|
|
||||||
}));
|
|
||||||
// 在下一个事件循环中重置标记
|
// 在下一个事件循环中重置标记
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isUpdatingFromCommonConfig.current = false;
|
isUpdatingFromCommonConfig.current = false;
|
||||||
@@ -406,32 +447,34 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
// 直接更新 formData,不通过 handleChange
|
// 直接更新 formData,不通过 handleChange
|
||||||
setFormData((prev) => ({
|
updateSettingsConfigValue(updatedConfig);
|
||||||
...prev,
|
|
||||||
settingsConfig: updatedConfig,
|
|
||||||
}));
|
|
||||||
setUseCommonConfig(false);
|
setUseCommonConfig(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证JSON格式
|
// 验证JSON格式
|
||||||
let isValidJson = false;
|
const validationError = validateJsonConfig(value, "通用配置片段");
|
||||||
try {
|
if (validationError) {
|
||||||
JSON.parse(value);
|
setCommonConfigError(validationError);
|
||||||
isValidJson = true;
|
} else {
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
} catch (err) {
|
|
||||||
setCommonConfigError("通用配置片段格式错误,需为合法 JSON");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 若当前启用通用配置且格式正确,需要替换为最新片段
|
// 若当前启用通用配置且格式正确,需要替换为最新片段
|
||||||
if (useCommonConfig && isValidJson) {
|
if (useCommonConfig && !validationError) {
|
||||||
const removeResult = updateCommonConfigSnippet(
|
const removeResult = updateCommonConfigSnippet(
|
||||||
formData.settingsConfig,
|
formData.settingsConfig,
|
||||||
previousSnippet,
|
previousSnippet,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
if (removeResult.error) {
|
||||||
|
setCommonConfigError(removeResult.error);
|
||||||
|
if (removeResult.error.includes("配置 JSON 解析失败")) {
|
||||||
|
setSettingsConfigError("配置JSON格式错误,请检查语法");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const addResult = updateCommonConfigSnippet(
|
const addResult = updateCommonConfigSnippet(
|
||||||
removeResult.updatedConfig,
|
removeResult.updatedConfig,
|
||||||
value,
|
value,
|
||||||
@@ -440,15 +483,15 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
|
|
||||||
if (addResult.error) {
|
if (addResult.error) {
|
||||||
setCommonConfigError(addResult.error);
|
setCommonConfigError(addResult.error);
|
||||||
|
if (addResult.error.includes("配置 JSON 解析失败")) {
|
||||||
|
setSettingsConfigError("配置JSON格式错误,请检查语法");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标记正在通过通用配置更新,避免触发状态检查
|
// 标记正在通过通用配置更新,避免触发状态检查
|
||||||
isUpdatingFromCommonConfig.current = true;
|
isUpdatingFromCommonConfig.current = true;
|
||||||
setFormData((prev) => ({
|
updateSettingsConfigValue(addResult.updatedConfig);
|
||||||
...prev,
|
|
||||||
settingsConfig: addResult.updatedConfig,
|
|
||||||
}));
|
|
||||||
// 在下一个事件循环中重置标记
|
// 在下一个事件循环中重置标记
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isUpdatingFromCommonConfig.current = false;
|
isUpdatingFromCommonConfig.current = false;
|
||||||
@@ -456,7 +499,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 保存通用配置到 localStorage
|
// 保存通用配置到 localStorage
|
||||||
if (isValidJson && typeof window !== "undefined") {
|
if (!validationError && typeof window !== "undefined") {
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem(COMMON_CONFIG_STORAGE_KEY, value);
|
window.localStorage.setItem(COMMON_CONFIG_STORAGE_KEY, value);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -473,6 +516,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
websiteUrl: preset.websiteUrl,
|
websiteUrl: preset.websiteUrl,
|
||||||
settingsConfig: configString,
|
settingsConfig: configString,
|
||||||
});
|
});
|
||||||
|
setSettingsConfigError(validateSettingsConfig(configString));
|
||||||
setCategory(
|
setCategory(
|
||||||
preset.category || (preset.isOfficial ? "official" : undefined),
|
preset.category || (preset.isOfficial ? "official" : undefined),
|
||||||
);
|
);
|
||||||
@@ -527,12 +571,14 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
// ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name"
|
// ANTHROPIC_SMALL_FAST_MODEL: "your-fast-model-name"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const templateString = JSON.stringify(customTemplate, null, 2);
|
||||||
|
|
||||||
setFormData({
|
setFormData({
|
||||||
name: "",
|
name: "",
|
||||||
websiteUrl: "",
|
websiteUrl: "",
|
||||||
settingsConfig: JSON.stringify(customTemplate, null, 2),
|
settingsConfig: templateString,
|
||||||
});
|
});
|
||||||
|
setSettingsConfigError(validateSettingsConfig(templateString));
|
||||||
setApiKey("");
|
setApiKey("");
|
||||||
setBaseUrl("https://your-api-endpoint.com"); // 设置默认的基础 URL
|
setBaseUrl("https://your-api-endpoint.com"); // 设置默认的基础 URL
|
||||||
setUseCommonConfig(false);
|
setUseCommonConfig(false);
|
||||||
@@ -576,6 +622,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
websiteUrl: "",
|
websiteUrl: "",
|
||||||
settingsConfig: "",
|
settingsConfig: "",
|
||||||
});
|
});
|
||||||
|
setSettingsConfigError(validateSettingsConfig(""));
|
||||||
setCodexAuth("");
|
setCodexAuth("");
|
||||||
setCodexConfig("");
|
setCodexConfig("");
|
||||||
setCodexApiKey("");
|
setCodexApiKey("");
|
||||||
@@ -593,10 +640,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 更新表单配置
|
// 更新表单配置
|
||||||
setFormData((prev) => ({
|
updateSettingsConfigValue(configString);
|
||||||
...prev,
|
|
||||||
settingsConfig: configString,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 同步通用配置开关
|
// 同步通用配置开关
|
||||||
const hasCommon = hasCommonConfigSnippet(
|
const hasCommon = hasCommonConfigSnippet(
|
||||||
@@ -617,10 +661,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
}
|
}
|
||||||
config.env.ANTHROPIC_BASE_URL = url.trim();
|
config.env.ANTHROPIC_BASE_URL = url.trim();
|
||||||
|
|
||||||
setFormData((prev) => ({
|
updateSettingsConfigValue(JSON.stringify(config, null, 2));
|
||||||
...prev,
|
|
||||||
settingsConfig: JSON.stringify(config, null, 2),
|
|
||||||
}));
|
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@@ -861,10 +902,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
delete currentConfig.env[field];
|
delete currentConfig.env[field];
|
||||||
}
|
}
|
||||||
|
|
||||||
setFormData((prev) => ({
|
updateSettingsConfigValue(JSON.stringify(currentConfig, null, 2));
|
||||||
...prev,
|
|
||||||
settingsConfig: JSON.stringify(currentConfig, null, 2),
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 如果 JSON 解析失败,不做处理
|
// 如果 JSON 解析失败,不做处理
|
||||||
}
|
}
|
||||||
@@ -888,10 +926,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
currentConfig.env[field] = value;
|
currentConfig.env[field] = value;
|
||||||
|
|
||||||
const updatedConfigString = JSON.stringify(currentConfig, null, 2);
|
const updatedConfigString = JSON.stringify(currentConfig, null, 2);
|
||||||
setFormData((prev) => ({
|
updateSettingsConfigValue(updatedConfigString);
|
||||||
...prev,
|
|
||||||
settingsConfig: updatedConfigString,
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("更新 Kimi 模型配置失败:", err);
|
console.error("更新 Kimi 模型配置失败:", err);
|
||||||
}
|
}
|
||||||
@@ -1144,6 +1179,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
commonConfigSnippet={codexCommonConfigSnippet}
|
commonConfigSnippet={codexCommonConfigSnippet}
|
||||||
onCommonConfigSnippetChange={handleCodexCommonConfigSnippetChange}
|
onCommonConfigSnippetChange={handleCodexCommonConfigSnippetChange}
|
||||||
commonConfigError={codexCommonConfigError}
|
commonConfigError={codexCommonConfigError}
|
||||||
|
authError={codexAuthError}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -1215,6 +1251,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
|
|||||||
commonConfigSnippet={commonConfigSnippet}
|
commonConfigSnippet={commonConfigSnippet}
|
||||||
onCommonConfigSnippetChange={handleCommonConfigSnippetChange}
|
onCommonConfigSnippetChange={handleCommonConfigSnippetChange}
|
||||||
commonConfigError={commonConfigError}
|
commonConfigError={commonConfigError}
|
||||||
|
configError={settingsConfigError}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface ClaudeConfigEditorProps {
|
|||||||
commonConfigSnippet: string;
|
commonConfigSnippet: string;
|
||||||
onCommonConfigSnippetChange: (value: string) => void;
|
onCommonConfigSnippetChange: (value: string) => void;
|
||||||
commonConfigError: string;
|
commonConfigError: string;
|
||||||
|
configError: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
|
const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
|
||||||
@@ -20,6 +21,7 @@ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
|
|||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
onCommonConfigSnippetChange,
|
onCommonConfigSnippetChange,
|
||||||
commonConfigError,
|
commonConfigError,
|
||||||
|
configError,
|
||||||
}) => {
|
}) => {
|
||||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||||
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
||||||
@@ -117,6 +119,11 @@ const ClaudeConfigEditor: React.FC<ClaudeConfigEditorProps> = ({
|
|||||||
}`}
|
}`}
|
||||||
rows={12}
|
rows={12}
|
||||||
/>
|
/>
|
||||||
|
{configError && (
|
||||||
|
<p className="text-xs text-red-500 dark:text-red-400">
|
||||||
|
{configError}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
完整的 Claude Code settings.json 配置内容
|
完整的 Claude Code settings.json 配置内容
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface CodexConfigEditorProps {
|
|||||||
commonConfigSnippet: string;
|
commonConfigSnippet: string;
|
||||||
onCommonConfigSnippetChange: (value: string) => void;
|
onCommonConfigSnippetChange: (value: string) => void;
|
||||||
commonConfigError: string;
|
commonConfigError: string;
|
||||||
|
authError: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
||||||
@@ -25,6 +26,7 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
|||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
onCommonConfigSnippetChange,
|
onCommonConfigSnippetChange,
|
||||||
commonConfigError,
|
commonConfigError,
|
||||||
|
authError,
|
||||||
}) => {
|
}) => {
|
||||||
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
||||||
|
|
||||||
@@ -94,6 +96,11 @@ const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
|
|||||||
data-gramm_editor="false"
|
data-gramm_editor="false"
|
||||||
data-enable-grammarly="false"
|
data-enable-grammarly="false"
|
||||||
/>
|
/>
|
||||||
|
{authError && (
|
||||||
|
<p className="text-xs text-red-500 dark:text-red-400">
|
||||||
|
{authError}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
Codex auth.json 配置内容
|
Codex auth.json 配置内容
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -77,6 +77,22 @@ export interface UpdateCommonConfigResult {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证JSON配置格式
|
||||||
|
export const validateJsonConfig = (value: string, fieldName: string = "配置"): string => {
|
||||||
|
if (!value.trim()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||||
|
return `${fieldName}必须是 JSON 对象`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
} catch {
|
||||||
|
return `${fieldName}JSON格式错误,请检查语法`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 将通用配置片段写入/移除 settingsConfig
|
// 将通用配置片段写入/移除 settingsConfig
|
||||||
export const updateCommonConfigSnippet = (
|
export const updateCommonConfigSnippet = (
|
||||||
jsonString: string,
|
jsonString: string,
|
||||||
@@ -99,22 +115,16 @@ export const updateCommonConfigSnippet = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let snippet: Record<string, any>;
|
// 使用统一的验证函数
|
||||||
try {
|
const snippetError = validateJsonConfig(snippetString, "通用配置片段");
|
||||||
const parsed = JSON.parse(snippetString);
|
if (snippetError) {
|
||||||
if (!isPlainObject(parsed)) {
|
|
||||||
return {
|
|
||||||
updatedConfig: JSON.stringify(config, null, 2),
|
|
||||||
error: "通用配置片段必须是 JSON 对象",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
snippet = parsed;
|
|
||||||
} catch (err) {
|
|
||||||
return {
|
return {
|
||||||
updatedConfig: JSON.stringify(config, null, 2),
|
updatedConfig: JSON.stringify(config, null, 2),
|
||||||
error: "通用配置片段格式错误,需为合法 JSON",
|
error: snippetError,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const snippet = JSON.parse(snippetString) as Record<string, any>;
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
const merged = deepMerge(deepClone(config), snippet);
|
const merged = deepMerge(deepClone(config), snippet);
|
||||||
|
|||||||
Reference in New Issue
Block a user