diff --git a/package.json b/package.json index 44eb421..fdff24e 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,12 @@ "vite": "^5.0.0" }, "dependencies": { + "@codemirror/lang-json": "^6.0.2", + "@codemirror/state": "^6.5.2", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.38.2", "@tauri-apps/api": "^2.8.0", + "codemirror": "^6.0.2", "react": "^18.2.0", "react-dom": "^18.2.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2953447..3ad1cf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,24 @@ importers: .: dependencies: + '@codemirror/lang-json': + specifier: ^6.0.2 + version: 6.0.2 + '@codemirror/state': + specifier: ^6.5.2 + version: 6.5.2 + '@codemirror/theme-one-dark': + specifier: ^6.1.3 + version: 6.1.3 + '@codemirror/view': + specifier: ^6.38.2 + version: 6.38.2 '@tauri-apps/api': specifier: ^2.8.0 version: 2.8.0 + codemirror: + specifier: ^6.0.2 + version: 6.0.2 react: specifier: ^18.2.0 version: 18.3.1 @@ -132,6 +147,33 @@ packages: resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} + '@codemirror/autocomplete@6.18.7': + resolution: {integrity: sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==} + + '@codemirror/commands@6.8.1': + resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} + + '@codemirror/lang-json@6.0.2': + resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + + '@codemirror/language@6.11.3': + resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} + + '@codemirror/lint@6.8.5': + resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==} + + '@codemirror/search@6.5.11': + resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/theme-one-dark@6.1.3': + resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} + + '@codemirror/view@6.38.2': + resolution: {integrity: sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -283,6 +325,21 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -503,9 +560,15 @@ packages: caniuse-lite@1.0.30001731: resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==} + codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -611,6 +674,9 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + typescript@5.9.2: resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} @@ -656,6 +722,9 @@ packages: terser: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -778,6 +847,64 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@codemirror/autocomplete@6.18.7': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + '@lezer/common': 1.2.3 + + '@codemirror/commands@6.8.1': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + '@lezer/common': 1.2.3 + + '@codemirror/lang-json@6.0.2': + dependencies: + '@codemirror/language': 6.11.3 + '@lezer/json': 1.0.3 + + '@codemirror/language@6.11.3': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + style-mod: 4.1.2 + + '@codemirror/lint@6.8.5': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + crelt: 1.0.6 + + '@codemirror/search@6.5.11': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + crelt: 1.0.6 + + '@codemirror/state@6.5.2': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/theme-one-dark@6.1.3': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + '@lezer/highlight': 1.2.1 + + '@codemirror/view@6.38.2': + dependencies: + '@codemirror/state': 6.5.2 + crelt: 1.0.6 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -861,6 +988,24 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 + '@lezer/common@1.2.3': {} + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/json@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/lr@1.4.2': + dependencies: + '@lezer/common': 1.2.3 + + '@marijn/find-cluster-break@1.0.2': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.46.2': @@ -1031,8 +1176,20 @@ snapshots: caniuse-lite@1.0.30001731: {} + codemirror@6.0.2: + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/commands': 6.8.1 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.8.5 + '@codemirror/search': 6.5.11 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + convert-source-map@2.0.0: {} + crelt@1.0.6: {} + csstype@3.1.3: {} debug@4.4.1: @@ -1150,6 +1307,8 @@ snapshots: source-map-js@1.2.1: {} + style-mod@4.1.2: {} + typescript@5.9.2: {} undici-types@6.21.0: {} @@ -1169,4 +1328,6 @@ snapshots: '@types/node': 20.19.9 fsevents: 2.3.3 + w3c-keyname@2.2.8: {} + yallist@3.1.1: {} diff --git a/src/components/JsonEditor.tsx b/src/components/JsonEditor.tsx new file mode 100644 index 0000000..c0d6d6a --- /dev/null +++ b/src/components/JsonEditor.tsx @@ -0,0 +1,97 @@ +import React, { useRef, useEffect } from "react"; +import { EditorView, basicSetup } from "codemirror"; +import { json } from "@codemirror/lang-json"; +import { oneDark } from "@codemirror/theme-one-dark"; +import { EditorState } from "@codemirror/state"; +import { placeholder } from "@codemirror/view"; + +interface JsonEditorProps { + value: string; + onChange: (value: string) => void; + placeholder?: string; + darkMode?: boolean; + rows?: number; +} + +const JsonEditor: React.FC = ({ + value, + onChange, + placeholder: placeholderText = "", + darkMode = false, + rows = 12, +}) => { + const editorRef = useRef(null); + const viewRef = useRef(null); + + useEffect(() => { + if (!editorRef.current) return; + + // 创建编辑器扩展 + const minHeightPx = Math.max(1, rows) * 18; // 降低最小高度以减少抖动 + const sizingTheme = EditorView.theme({ + "&": { minHeight: `${minHeightPx}px` }, + ".cm-scroller": { overflow: "auto" }, + ".cm-content": { + fontFamily: + "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", + fontSize: "14px", + }, + }); + + const extensions = [ + basicSetup, + json(), + placeholder(placeholderText || ""), + sizingTheme, + EditorView.updateListener.of((update) => { + if (update.docChanged) { + const newValue = update.state.doc.toString(); + onChange(newValue); + } + }), + ]; + + // 如果启用深色模式,添加深色主题 + if (darkMode) { + extensions.push(oneDark); + } + + // 创建初始状态 + const state = EditorState.create({ + doc: value, + extensions, + }); + + // 创建编辑器视图 + const view = new EditorView({ + state, + parent: editorRef.current, + }); + + viewRef.current = view; + + // 清理函数 + return () => { + view.destroy(); + viewRef.current = null; + }; + }, [darkMode, rows]); // 依赖项中不包含 onChange 和 placeholder,避免不必要的重建 + + // 当 value 从外部改变时更新编辑器内容 + useEffect(() => { + if (viewRef.current && viewRef.current.state.doc.toString() !== value) { + const transaction = viewRef.current.state.update({ + changes: { + from: 0, + to: viewRef.current.state.doc.length, + insert: value, + }, + }); + viewRef.current.dispatch(transaction); + } + }, [value]); + + return
; +}; + +export default JsonEditor; diff --git a/src/components/ProviderForm.tsx b/src/components/ProviderForm.tsx index 6c188d0..600fcb0 100644 --- a/src/components/ProviderForm.tsx +++ b/src/components/ProviderForm.tsx @@ -4,7 +4,6 @@ import { AppType } from "../lib/tauri-api"; import { updateCoAuthoredSetting, checkCoAuthoredSetting, - extractWebsiteUrl, getApiKeyFromConfig, hasApiKeyField, setApiKeyInConfig, @@ -12,6 +11,7 @@ import { import { providerPresets } from "../config/providerPresets"; import { codexProviderPresets } from "../config/codexProviderPresets"; import "./AddProviderModal.css"; +import JsonEditor from "./JsonEditor"; interface ProviderFormProps { appType?: AppType; @@ -160,9 +160,6 @@ const ProviderForm: React.FC = ({ const { name, value } = e.target; if (name === "settingsConfig") { - // 当用户修改配置时,尝试自动提取官网地址 - const extractedWebsiteUrl = extractWebsiteUrl(value); - // 同时检查并同步选择框状态 const hasCoAuthoredDisabled = checkCoAuthoredSetting(value); setDisableCoAuthored(hasCoAuthoredDisabled); @@ -171,12 +168,11 @@ const ProviderForm: React.FC = ({ const parsedKey = getApiKeyFromConfig(value); setApiKey(parsedKey); - setFormData({ - ...formData, + // 不再从 JSON 自动提取或覆盖官网地址,只更新配置内容 + setFormData((prev) => ({ + ...prev, [name]: value, - // 只有在官网地址为空时才自动填入 - websiteUrl: formData.websiteUrl || extractedWebsiteUrl, - }); + })); } else { setFormData({ ...formData, @@ -609,7 +605,6 @@ const ProviderForm: React.FC = ({ onChange={(e) => setCodexConfig(e.target.value)} placeholder={``} rows={8} - style={{ fontFamily: "monospace", fontSize: "14px" }} /> Codex config.toml 配置内容 @@ -632,11 +627,11 @@ const ProviderForm: React.FC = ({ 禁止 Claude Code 签名
-