diff --git a/.trae/rules/rule.md b/.trae/rules/rule.md new file mode 100644 index 0000000..289be5a --- /dev/null +++ b/.trae/rules/rule.md @@ -0,0 +1,13 @@ +--- +alwaysApply: true +--- + +## 核心规则 + +1. **提交每次修改** — 每次任务执行后默认提交修改代码到远程仓库,或者用户说“提交代码”,执行git commit 并且要把commit 信息展示出来。默认是全部本地修改都提交,包括不是本次修改的也提交, 不需要提问用户确认。commit 信息你需要总结本次修改内容,不能是代码。例如:“修复登录页面的错误提示”.最后一定要执行 git push 推送到远程仓库。 +2. **方案性内容** - 当用户需求,如果是方案性内容,你首先要评估,有没有更好的方案,而不是执行。更好的方案,你可以搜索类似阿里、字节等大厂的方案来参考并给出建议。 +3. **代码修改** - 任何时候,当存在字段格式不对,变量名不对,表使用不对等,禁止做兼容修改,必须按唯一性修改。比如约定字段是string,正确:只能传string;错误:可以传int。 比如约定字段名是data,正确:只能传data;错误:可以传sourceData或者data。 +4. **语言** - 永远使用中文跟用户沟通,专有名词可以用英文。 +5. **文档** - 在用户没有明确要求先提供文档时,禁止创建文档。避免生产垃圾文件。用户明确要求提供文档时,才创建文档。 + + diff --git a/src/api/auth.ts b/src/api/auth.ts index c279887..0d7f27f 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -13,7 +13,7 @@ export const AuthAPI = { me: () => api.get('/auth/me').then((r) => r.data), verify: async (phone: string, password: string) => { try { - const res = await axios.post(`${API_BASE_URL}/urser`, { phone, password }, { timeout: 3000 }); + const res = await axios.post(`${API_BASE_URL}urser`, { phone, password }, { timeout: 3000 }); return res.data; } catch (e) { console.warn('Backend /urser not available, fallback to mock true', e); diff --git a/src/api/http.ts b/src/api/http.ts index 541e123..643a105 100644 --- a/src/api/http.ts +++ b/src/api/http.ts @@ -1,10 +1,10 @@ import axios from 'axios'; import { clearUserStorage } from '../utils/storage'; -export const API_BASE_URL = import.meta.env.DEV ? '/api' : 'https://tianchaoai.cc/aura/v1'; +export const API_BASE_URL = 'https://www.tianchaoai.cc/api/v1/'; const APP_BASE = (import.meta.env.BASE_URL || '/').replace(/\/$/, ''); export const withAppBase = (path: string) => `${APP_BASE}${path.startsWith('/') ? path : `/${path}`}`; -export const withApiBase = (path: string) => `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`; +export const withApiBase = (path: string) => `${API_BASE_URL}${path.replace(/^\//, '')}`; export const api = axios.create({ baseURL: API_BASE_URL, diff --git a/src/api/streamChat.ts b/src/api/streamChat.ts index 18936ce..1bcfc57 100644 --- a/src/api/streamChat.ts +++ b/src/api/streamChat.ts @@ -31,7 +31,7 @@ export async function streamChat( modelId?: string, imageUrls?: string[] ) { - const resp = await fetch(`${API_BASE_URL}/rooms/${roomId}/messages/stream`, { + const resp = await fetch(`${API_BASE_URL}rooms/${roomId}/messages/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' }, body: JSON.stringify({ @@ -55,7 +55,7 @@ export async function regenerateMessage( overrides?: ModelOverrides, attachmentsText?: string ) { - const resp = await fetch(`${API_BASE_URL}/chat/${agentId}/messages/${messageId}/regenerate`, { + const resp = await fetch(`${API_BASE_URL}chat/${agentId}/messages/${messageId}/regenerate`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' }, body: JSON.stringify({ overrides, attachmentsText }), diff --git a/src/components/McpPanel/components/McpPanelForm.tsx b/src/components/McpPanel/components/McpPanelForm.tsx new file mode 100644 index 0000000..a9ca435 --- /dev/null +++ b/src/components/McpPanel/components/McpPanelForm.tsx @@ -0,0 +1,63 @@ +import { useEffect, useState } from 'react'; +import { Button, Form, Input, Select, Space } from 'antd'; +import type { McpServer } from '../../../api'; + +interface Props { + initial: McpServer | null; + onSubmit: (values: any) => void; + onCancel: () => void; +} + +export default function McpPanelForm({ initial, onSubmit, onCancel }: Props) { + const [form] = Form.useForm(); + const [transport, setTransport] = useState<'stdio' | 'sse' | 'http'>(initial?.transport ?? 'stdio'); + + useEffect(() => { + form.setFieldsValue({ + name: initial?.name ?? '', + transport: initial?.transport ?? 'stdio', + command: initial?.command ?? '', + argsText: (initial?.args ?? []).join('\n'), + envText: initial?.env ? JSON.stringify(initial.env, null, 2) : '', + url: initial?.url ?? '', + }); + setTransport(initial?.transport ?? 'stdio'); + }, [initial, form]); + + return ( +
+ + + + + + + + + ) : ( + + )} + + + + + +
+ ); +} diff --git a/src/components/McpPanel/components/McpPanelWebBase.tsx b/src/components/McpPanel/components/McpPanelWebBase.tsx new file mode 100644 index 0000000..1e6eacd --- /dev/null +++ b/src/components/McpPanel/components/McpPanelWebBase.tsx @@ -0,0 +1,96 @@ +import { App as AntApp, Alert, Button, Card, List, Modal, Popconfirm, Space, Tag, Tooltip } from 'antd'; +import type { DesktopViewport } from '../../../hooks/useDesktopViewport'; +import { desktopViewportClass } from '../../../hooks/useDesktopViewport'; +import type { McpPanelLogicOutput } from '../McpPanelLogic'; +import McpPanelForm from './McpPanelForm'; + +export interface McpPanelWebVariantProps { + agentId: string; + logic: McpPanelLogicOutput; + viewport: DesktopViewport; +} + +export default function McpPanelWebBase({ logic, viewport }: McpPanelWebVariantProps) { + const { message } = AntApp.useApp(); + const { servers, statusList, statusLoading, createOpen, importOpen, importJson, PRESETS } = logic; + const { handleImport, handleDelete, openCreate, openEdit, closeCreate, setImportOpen, setImportJson, refreshStatus } = logic; + + return ( + MCP Servers{servers.length} 个} + extra={ + + + + + + } + > + + + { + const status = statusList.find((item) => item.id === server.id); + return ( + openEdit(server)}>编辑, + { await handleDelete(server.id); message.success('已删除'); }}> + + , + ]} + > + + {server.transport} + {server.name} + {!server.enabled && 已停用} + {status?.error && 连接失败} + {status && !status.error && {status.toolCount} 工具就绪} + + } + description={ + + {server.transport === 'stdio' ? `${server.command} ${server.args.join(' ')}` : server.url} + {status?.tools?.length ? ( + + {status.tools.slice(0, 8).map((tool) => {tool.name})} + {status.tools.length > 8 && +{status.tools.length - 8}} + + ) : null} + + } + /> + + ); + }} + /> + + + { + const result = await logic.handleSave(values); + result.success ? message.success(logic.editing ? '已更新' : '已创建') : message.error(result.error); + }} + onCancel={closeCreate} + /> + + + setImportOpen(false)} onOk={async () => { + const result = await handleImport(); + result.success ? message.success(`已导入 ${result.imported} 个 MCP Server`) : message.error(result.error); + }} okText="导入" cancelText="取消"> + + + {PRESETS.map((preset) => )} + +