From b5d3b7df572b53c8e905f819f9d4a631dac209ea Mon Sep 17 00:00:00 2001 From: kurihada Date: Fri, 12 Dec 2025 12:14:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ConfigPanel 组件,支持模型选择、参数调整 - 添加配置 API (getConfig/updateConfig) - 在 App.tsx 中集成配置按钮和面板 --- packages/web/src/App.tsx | 64 +++-- packages/web/src/api/client.ts | 18 ++ packages/web/src/components/ConfigPanel.tsx | 264 ++++++++++++++++++++ 3 files changed, 329 insertions(+), 17 deletions(-) create mode 100644 packages/web/src/components/ConfigPanel.tsx diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 922be81..6530e4c 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -6,12 +6,14 @@ import { useState, useEffect } from 'react'; import { Sidebar } from './components/Sidebar'; import { ChatPage } from './pages/Chat'; import { FileBrowser } from './components/FileBrowser'; +import { ConfigPanel } from './components/ConfigPanel'; import { listSessions, createSession, type Session } from './api/client'; export function App() { const [currentSessionId, setCurrentSessionId] = useState(null); const [isInitializing, setIsInitializing] = useState(true); const [showFileBrowser, setShowFileBrowser] = useState(false); + const [showConfig, setShowConfig] = useState(false); // 初始化:加载或创建会话 useEffect(() => { @@ -64,23 +66,48 @@ export function App() { onCreateSession={handleCreateSession} /> - {/* 文件浏览器切换按钮 */} - + {/* 工具栏按钮 */} +
+ {/* 配置按钮 */} + + + {/* 文件浏览器切换按钮 */} + +
{/* 聊天区域 */} @@ -105,6 +132,9 @@ export function App() {
)} + + {/* 配置面板 */} + {showConfig && setShowConfig(false)} />} ); } diff --git a/packages/web/src/api/client.ts b/packages/web/src/api/client.ts index 1689378..0a5278e 100644 --- a/packages/web/src/api/client.ts +++ b/packages/web/src/api/client.ts @@ -161,3 +161,21 @@ export async function getFileTree(path: string = '.', depth: number = 3): Promis const params = new URLSearchParams({ path, depth: String(depth) }); return request('GET', `/files/tree?${params}`); } + +// Config +export interface ServerConfig { + model: string; + maxTokens: number; + temperature: number; + workdir: string; + allowedPaths: string[]; + deniedPaths: string[]; +} + +export async function getConfig(): Promise<{ success: boolean; data: ServerConfig }> { + return request('GET', '/config'); +} + +export async function updateConfig(config: Partial): Promise<{ success: boolean; data: ServerConfig }> { + return request('PATCH', '/config', config); +} diff --git a/packages/web/src/components/ConfigPanel.tsx b/packages/web/src/components/ConfigPanel.tsx new file mode 100644 index 0000000..b9c11f4 --- /dev/null +++ b/packages/web/src/components/ConfigPanel.tsx @@ -0,0 +1,264 @@ +/** + * ConfigPanel Component + * + * 配置面板组件 + */ + +import { useState, useEffect } from 'react'; +import { getConfig, updateConfig, type ServerConfig } from '../api/client'; + +interface ConfigPanelProps { + onClose: () => void; +} + +// 可用的模型列表 +const AVAILABLE_MODELS = [ + { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4' }, + { id: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet' }, + { id: 'claude-3-opus-20240229', name: 'Claude 3 Opus' }, + { id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku' }, +]; + +export function ConfigPanel({ onClose }: ConfigPanelProps) { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + // 表单状态 + const [formData, setFormData] = useState({ + model: '', + maxTokens: 8192, + temperature: 0.7, + workdir: '', + }); + + // 加载配置 + useEffect(() => { + async function loadConfig() { + try { + const response = await getConfig(); + setConfig(response.data); + setFormData({ + model: response.data.model, + maxTokens: response.data.maxTokens, + temperature: response.data.temperature, + workdir: response.data.workdir, + }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load config'); + } finally { + setLoading(false); + } + } + loadConfig(); + }, []); + + // 保存配置 + const handleSave = async () => { + setSaving(true); + setError(null); + setSuccess(false); + + try { + const response = await updateConfig(formData); + setConfig(response.data); + setSuccess(true); + setTimeout(() => setSuccess(false), 2000); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to save config'); + } finally { + setSaving(false); + } + }; + + // 重置为默认值 + const handleReset = () => { + if (config) { + setFormData({ + model: config.model, + maxTokens: config.maxTokens, + temperature: config.temperature, + workdir: config.workdir, + }); + } + }; + + if (loading) { + return ( +
+
+
Loading configuration...
+
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+

Configuration

+ +
+ + {/* Content */} +
+ {/* Error message */} + {error && ( +
+ {error} +
+ )} + + {/* Success message */} + {success && ( +
+ Configuration saved successfully! +
+ )} + + {/* Model */} +
+ + +

+ Select the AI model to use for conversations +

+
+ + {/* Max Tokens */} +
+ + setFormData({ ...formData, maxTokens: parseInt(e.target.value) })} + className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer" + /> +
+ 1K + 8K + 16K + 32K +
+

+ Maximum number of tokens in the response +

+
+ + {/* Temperature */} +
+ + setFormData({ ...formData, temperature: parseFloat(e.target.value) })} + className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer" + /> +
+ Precise (0) + Balanced (0.5) + Creative (1) +
+

+ Controls randomness in responses +

+
+ + {/* Working Directory */} +
+ + setFormData({ ...formData, workdir: e.target.value })} + className="w-full px-3 py-2 bg-gray-900 border border-gray-700 rounded-lg text-white focus:outline-none focus:border-blue-500 font-mono text-sm" + placeholder="/path/to/project" + /> +

+ Root directory for file operations +

+
+ + {/* Server Info (Read-only) */} + {config && ( +
+

Server Information

+
+
+ Allowed Paths: + + {config.allowedPaths.length || 'All'} + +
+
+ Denied Paths: + + {config.deniedPaths.length || 'None'} + +
+
+
+ )} +
+ + {/* Footer */} +
+ + + +
+
+
+ ); +}