# Magic.Api.Web **Repository Path**: sanhenlei/Magic.Api.Web ## Basic Information - **Project Name**: Magic.Api.Web - **Description**: 我用AI写的Magic-Api前端项目,模仿官网的前端,增加个性化的东西。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2026-04-19 - **Last Updated**: 2026-07-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Magic API Web 基于 Vue 3 的 Magic API 脚本管理前端,提供接口/函数/任务的在线编辑、调试与管理功能。集成 AI 助手,支持脚本审查、代码生成与错误分析。 ## 技术栈 ### 前端 - **Vue 3** + TypeScript + Composition API - **Pinia** 状态管理 - **Vue Router** 路由 - **Element Plus** UI 组件库 - **Monaco Editor** 代码编辑器 - **Vite** 构建工具 - **SCSS** 样式预处理 - **marked** + **highlight.js** Markdown 渲染 ### AI 后端 (magic-ai-backend) - **Python** + **FastAPI** + **Uvicorn** - **Claude Code CLI** 作为 AI Provider(复用现有订阅) - **SSE (Server-Sent Events)** 流式输出 - **CORS** 跨域支持 #### 核心原理 AI 后端通过 FastAPI 接收前端对话请求,调用 Claude Code CLI 子进程生成脚本建议,以 SSE 事件流实时返回结果: 1. **请求接收** — `POST /chat` 端点接收 `ChatRequest`,包含用户消息、当前脚本内容、错误日志等上下文信息 2. **上下文组装** — `build_prompt()` 将用户问题与当前脚本信息、错误日志(最多 20 条)组装为完整 prompt,由 Magic API 脚本专家的 system prompt 引导输出 3. **CLI 调用** — 通过 `Claude CLI` 子进程(`--output-format stream-json`)调用 Claude 模型,逐行解析 JSON 提取 content 字段 4. **流式返回** — 通过 SSE 事件流返回:`token`(内容片段)、`done`(生成完成)、`error`(调用失败) #### Provider 接口 `providers/base.py` 定义抽象接口:`stream_chat()` 返回异步 token 流,`chat()` 聚合为完整字符串。当前仅实现 `claude_cli.py`,通过 Claude Code CLI 子进程调用,可扩展其他 Provider。 #### 长期记忆与 RAG 架构(规划中) 项目有 200+ 脚本,无法全部塞入上下文窗口,设计了**混合检索 + 会话持久化**方案: **三层检索架构**: 1. **元数据层**(SQLite)— 存储脚本名称、分组路径、类型、描述,支持精确过滤 2. **全文检索层**(SQLite FTS5)— 脚本内容关键词匹配,适合精确查询(表名、函数名) 3. **语义检索层**(ChromaDB)— 向量相似度搜索,适合模糊语义查询("处理用户权限的脚本") 三路检索结果通过 **RRF(Reciprocal Rank Fusion,倒数排名融合)** 合并,取 Top-K 结果,裁剪到 token 预算内。 **索引流程**: ```text 前端附带 magic-token → AI 后端调 Magic API 获取脚本树 → 逐个获取脚本内容 → 分块(chunker) → 本地生成 embedding(sentence-transformers 多语言版) → 存入 ChromaDB(向量) + SQLite FTS5(全文) → 按 updateTime 增量更新 ``` **会话持久化**(SQLite): - 对话历史持久化到本地 SQLite,支持多会话列表和上下文恢复 - 每条记录关联 `conversation_id`,按时间排序 - 避免每次对话从零开始,AI 可以引用之前讨论过的脚本上下文 ## 功能特性 - **多项目管理** — 支持配置多个后端服务地址,下拉切换 - **脚本编辑** — Monaco Editor 提供 magicscript 自定义语言高亮,支持 SQL/MyBatis 语法(`"""` 块内嵌高亮) - **实时语法检查** — 自动检测未闭合括号、引号等语法错误(红色波浪线提示) - **接口测试** — 点击执行按钮发送请求,支持 GET/POST 等方法,显示响应结果和耗时 - **资源管理** — 树形结构管理 API、函数、任务,支持分组、拖拽排序、搜索过滤 - **代码自动补全** — 括号/引号自动闭合、MyBatis 标签自动补全 - **全局搜索** — 跨模块脚本关键字搜索,支持结果定位跳转 - **历史记录** — 查看接口调用历史,支持版本对比(Monaco Diff Editor) - **数据源管理** — 查看/编辑/测试数据源连接 - **AI 助手** — 右侧边栏对话面板,支持脚本审查、语法解释、错误日志分析、代码插入编辑器 - **快捷键** — `Ctrl+S` 保存、`Ctrl+Q` 测试、`Ctrl+W` 关闭标签、`Alt+G` 新建分组 ## 项目结构 ``` magic-ai-backend/ # AI 后端服务(Python FastAPI) ├── main.py # FastAPI 入口,/health、/chat SSE 端点 ├── config.py # 配置(监听地址、模型) ├── providers/ │ ├── claude_cli.py # Claude Code CLI subprocess 流式调用 │ └── base.py # Provider 基类 ├── chat/ │ ├── context.py # 上下文组装(脚本 + 元数据) │ └── prompts.py # Magic API 语法 system prompt └── requirements.txt src/ # 前端源码 ├── api/ # API 请求层 ├── composables/ # 组合式函数 ├── stores/ # Pinia 状态管理 ├── components/ │ ├── layout/ # 布局组件 │ ├── editor/ # 编辑器组件(ScriptEditor + Monaco) │ ├── resource/ # 资源组件(树列表、信息面板) │ ├── bottom/ # 底部面板(日志、历史、结果) │ ├── ai/ # AI 聊天面板 │ └── common/ # 通用组件 ├── views/ # 页面视图 ├── router/ # 路由配置 ├── types/ # TypeScript 类型定义 ├── utils/ # 工具函数 ├── styles/ # 全局样式 └── mock/ # Mock 数据 ``` ## 开发 ### 启动前端 ```bash # 安装依赖 pnpm install # 启动开发服务器(端口 3000) pnpm dev # 构建生产版本 pnpm build ``` ### 启动 AI 后端 ```bash cd magic-ai-backend # 创建虚拟环境并安装依赖 python setup.py # 启动 python main.py ``` > 前置条件:需要安装 [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)(`npm install -g @anthropic-ai/claude-code`),AI 后端通过它调用 Claude 模型。 ## 配置 ### 项目配置 编辑 `public/projects.json` 配置后端服务地址: ```json { "useMock": false, "projects": [ { "name": "项目名称", "apiBase": "http://host:port/context-path", "webApi": "/api/config/web", "proxyPathPrefix": "/magic-api-project", "runtimeProxyPrefix": "/magic-runtime-project" } ] } ``` - `useMock=true` — 启用静态 Mock 数据模式 - `apiBase` — 后端服务根地址(用于接口测试直接请求) - `webApi` — 管理 API 路径前缀(用于资源 CRUD 操作) ### AI 助手配置 AI 后端地址在聊天面板设置中配置(存 localStorage),填写局域网 IP 如 `http://192.168.1.100:8000`。 ### 环境变量 | 变量 | 说明 | 默认值 | | ---- | ---- | ------- | | `VITE_APP_BASE_API` | 前端 API 代理前缀 | `/magic-api` | | `HOST` / `AI_HOST` | AI 后端监听地址 | `0.0.0.0` | | `PORT` / `AI_PORT` | AI 后端监听端口 | `8000` | | `DEFAULT_MODEL` / `AI_DEFAULT_MODEL` | 默认 Claude 模型 | `sonnet` | | `CLAUDE_PATH` / `AI_CLAUDE_PATH` | Claude CLI 路径 | `claude` | ## 多项目认证逻辑 ### Token 存储机制 每个项目使用独立的 localStorage key 存储 token,key 格式: ```text magic-token__{proxyPathPrefix} ``` 例如项目配置 `"proxyPathPrefix": "/magic-api-prod"`,则 token key 为 `magic-token__/magic-api-prod`。无 prefix 时 fallback 到 `magic-token`。 **核心代码** — `src/stores/auth.ts`: ```typescript function getTokenKey(): string { const projectStore = useProjectStore() const prefix = projectStore.currentProxyPathPrefix return prefix ? `${TOKEN_KEY_PREFIX}__${prefix}` : TOKEN_KEY_PREFIX } ``` 请求拦截器 `src/api/request.ts` 使用相同的 key 逻辑,每次请求自动注入对应项目的 token: ```typescript config.headers['magic-token'] = localStorage.getItem(getProjectTokenKey()) || 'unauthorization' ``` ### 启动初始化流程 `App.vue` 的 `onMounted` 顺序至关重要: ```text 1. projectStore.initProjects() ← 必须先完成,确定 currentProjectId 2. authStore.loadToken() ← 依赖 currentProxyPathPrefix(由 project 决定) 3. 设置 document.title 4. 如果有 token → initClasses() + WebSocket connect ``` **关键约束**:`loadToken()` 读取 localStorage 的 key 依赖 `currentProxyPathPrefix`,所以 `initProjects()` 必须先执行。 `initProjects()` (`src/stores/project.ts`) 的处理逻辑: 1. 从 `public/projects.json` 加载项目列表 2. 优先使用 URL `?project=xxx` 参数 → 写入 `currentProjectId` + localStorage 3. 无 URL 参数时用 localStorage 中的 `magic-current-project` 4. 都没有则取第一个项目 ### 登录流程 `LoginView.vue` 的登录流程: ```text 1. onMounted → initProjects() → 如果 URL 有 ?project=xxx 则切换到该项目 2. 用户选择项目(el-select → projectStore.currentProjectId) 3. 输入用户名密码 → authStore.login() 4. login() 内部: a. 调用 POST /login 接口 b. 从响应 header 读取 magic-token c. saveToken() → 写入 localStorage(key = magic-token__{prefix}) d. initClasses() + WebSocket connect e. 重新 initProjects()(刷新项目配置) 5. 登录成功 → router.push({ path: redirect, query: { project: currentProjectId } }) ``` 登录后的重定向始终带 `?project=xxx` 参数,确保刷新后不会丢失项目上下文。 ### 认证检查流程 `EditorView.vue` 的 `onMounted` 检查逻辑: ```text 1. 检测 URL ?project=xxx 是否与 currentProjectId 不匹配 → 不匹配则调用 switchProject() + loadToken()(切换项目并加载对应 token) 2. authStore.checkAuth() a. loadToken() → 从 localStorage 读取当前项目的 token b. 调用 POST /check-login 验证 token 有效性 c. 有效 → 获取用户名,返回 true d. 无效 → 返回 false → 跳转 /login?redirect=当前路径&project=项目名 3. 认证通过 → 加载资源树、恢复 tab 等 ``` ### 退出登录流程 ```text 1. 断开 WebSocket 2. 调用 POST /logout 3. 清除 token:localStorage.removeItem(getTokenKey()) 4. 清空内存中的 token 和 username 5. 跳转 /login ``` `logout()` 只清除当前项目的 token,不影响其他项目。 ### 项目切换 两种触发方式: - **方式一:登录页手动选择** — 用户在登录页 el-select 切换项目 → `currentProjectId` 变化 → watch 同步到 URL 和 localStorage → 登录后存储该项目的 token。 - **方式二:URL 参数直接访问** — 访问 `/?project=ProjectB`: ```text EditorView.onMounted: 检测 projectFromUrl !== currentProjectId → projectStore.switchProject('ProjectB') → currentProjectId = 'ProjectB' → localStorage 更新 → WebSocket disconnect + reconnect → authStore.loadToken()(加载 ProjectB 的 token) → checkAuth() ``` ### 防止刷新后项目漂移 关键保护机制: 1. **URL 参数优先**:`initProjects()` 和 EditorView 都优先读 URL 的 `?project=xxx` 2. **localStorage 持久化**:`watch(currentProjectId)` 自动同步到 localStorage 3. **EditorView 兜底检查**:即使 App.vue 初始化时项目正确,EditorView 仍会二次检查 URL 参数是否匹配 4. **登录重定向带 project**:登录成功后 redirect URL 始终带 `?project=xxx` ### 数据流总览 ```text ┌─────────────────────────────────────────────────────────────────┐ │ App.vue onMounted │ │ initProjects() → loadToken() → initClasses() → WS connect │ └────────────────────────────┬────────────────────────────────────┘ │ ┌──────────────────┼──────────────────┐ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │ LoginView │ │ EditorView │ │ Request 拦截 │ │ │ │ │ │ │ │ 选择项目 │ │ URL 项目检查│ │ 自动注入 │ │ 输入账密 │ │ checkAuth() │ │ magic-token │ │ 保存 token │ │ 加载资源树 │ │ + baseURL │ │ 跳转+project│ │ │ │ │ └─────────────┘ └─────────────┘ └──────────────┘ localStorage keys: ├── magic-current-project → "项目名" ├── magic-token__/magic-api-a → "token-for-project-a" └── magic-token__/magic-api-b → "token-for-project-b" ```