diff --git a/.gitignore b/.gitignore index 95f19871372694b323daaf71e6df9c4d3ddb8b5a..0be35195be24f29d2d40d4930eaabb118119dd79 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ .env +# uv +uv.lock + # ide .idea diff --git a/README.md b/README.md index 2568109b36964501dffcdf002513b4935ba34cd4..6e299b2fde9bfafdc425671bf4cd1168cea2317d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# openEuler Intelligence Smart Shell +# OE-CLI 命令行助手 -一个基于 Python Textual 构建的智能终端应用程序,提供 AI 驱动的命令行交互体验。支持多种 LLM 后端,集成 MCP 协议,提供现代化的 TUI 界面。 +OE-CLI 是 openEuler Intelligence 的命令行客户端,提供 AI 驱动的命令行交互体验。支持多种 LLM 后端,集成 MCP 协议,提供现代化的 TUI 界面。 ## 核心特性 @@ -8,6 +8,7 @@ - **智能终端界面**: 基于 Textual 的现代化 TUI 界面 - **流式响应**: 实时显示 AI 回复内容 - **部署助手**: 内置 openEuler Intelligence 自动部署功能 +- **一键认证**: 提供浏览器登录流程,自动获得并保存 API Key ## 安装说明 @@ -70,6 +71,14 @@ oi --init oi --agent ``` +通过浏览器登录并自动保存 API Key(需要已配置的 openEuler Intelligence 后端): + +```sh +oi --login +``` + +运行前请确保运行环境具备图形界面并能启动默认浏览器;在无图形界面的场景下可使用 X11 转发或直接在目标服务器上执行该命令。 + 应用启动后,您可以直接在输入框中输入命令。如果命令无效或无法执行,应用程序将基于您的输入提供智能建议。 ### 界面操作快捷键 @@ -179,6 +188,23 @@ oi --agent 3. 如果系统配置文件不存在或权限不足,工具会显示相应错误信息并退出; 4. 建议在修改配置前备份原有的配置文件。 +### `--login` 命令说明 + +`--login` 命令用于通过默认浏览器获取 openEuler Intelligence 的 API Key,并自动写入到本地配置: + +1. **获取授权地址**: 从已配置的 openEuler Intelligence 后端读取登录跳转链接; +2. **本地回调服务**: 在本地启动一个临时回调服务器,处理浏览器回传的数据; +3. **打开浏览器**: 自动打开登录页面,用户按提示完成账号登录; +4. **保存凭证**: 登录成功后自动将返回的访问令牌写入配置文件。 + +**使用要求**: + +- 已通过 `--init` 或手动方式配置 openEuler Intelligence URL; +- 运行环境具备可用浏览器(无头服务器需使用图形转发或在本地执行); +- 登录过程需要网络访问 openEuler Intelligence 后端。 + +遇到错误时,命令会输出详细的提示信息帮助排查,例如未检测到浏览器或 URL 未配置等情况。 + ## 国际化支持 应用程序内置多语言支持,提供英文和中文界面。 diff --git a/distribution/linux/euler-copilot-shell.spec b/distribution/linux/euler-copilot-shell.spec index b0a27fc1d5db6540d2ffc758b88e607ae22f8bc3..d1971e2e2c56510e2fa92d8f862c882c3f475e67 100644 --- a/distribution/linux/euler-copilot-shell.spec +++ b/distribution/linux/euler-copilot-shell.spec @@ -3,7 +3,7 @@ %global debug_package %{nil} Name: euler-copilot-shell -Version: 0.10.2 +Version: 2.0.0 Release: 1%{?dev_timestamp:.dev%{dev_timestamp}}%{?dist} Summary: openEuler Intelligence 智能命令行工具集 License: MulanPSL-2.0 @@ -12,9 +12,8 @@ Source0: %{name}-%{version}.tar.gz ExclusiveArch: x86_64 aarch64 riscv64 loongarch64 -BuildRequires: python3-devel -BuildRequires: python3-virtualenv -BuildRequires: python3-pip +BuildRequires: python3-devel python3-virtualenv python3-pip +BuildRequires: gettext %description openEuler Intelligence 智能命令行工具集,包含智能 Shell 命令行程序和部署安装工具。 diff --git "a/docs/development/TUI\345\272\224\347\224\250\346\250\241\345\235\227\350\256\276\350\256\241.md" "b/docs/development/design/TUI\345\272\224\347\224\250\346\250\241\345\235\227\350\256\276\350\256\241.md" similarity index 100% rename from "docs/development/TUI\345\272\224\347\224\250\346\250\241\345\235\227\350\256\276\350\256\241.md" rename to "docs/development/design/TUI\345\272\224\347\224\250\346\250\241\345\235\227\350\256\276\350\256\241.md" diff --git "a/docs/development/\345\220\216\347\253\257\351\200\202\351\205\215\346\250\241\345\235\227\350\256\276\350\256\241.md" "b/docs/development/design/\345\220\216\347\253\257\351\200\202\351\205\215\346\250\241\345\235\227\350\256\276\350\256\241.md" similarity index 100% rename from "docs/development/\345\220\216\347\253\257\351\200\202\351\205\215\346\250\241\345\235\227\350\256\276\350\256\241.md" rename to "docs/development/design/\345\220\216\347\253\257\351\200\202\351\205\215\346\250\241\345\235\227\350\256\276\350\256\241.md" diff --git "a/docs/development/\346\227\245\345\277\227\347\256\241\347\220\206\346\250\241\345\235\227\350\256\276\350\256\241.md" "b/docs/development/design/\346\227\245\345\277\227\347\256\241\347\220\206\346\250\241\345\235\227\350\256\276\350\256\241.md" similarity index 100% rename from "docs/development/\346\227\245\345\277\227\347\256\241\347\220\206\346\250\241\345\235\227\350\256\276\350\256\241.md" rename to "docs/development/design/\346\227\245\345\277\227\347\256\241\347\220\206\346\250\241\345\235\227\350\256\276\350\256\241.md" diff --git "a/docs/development/\351\203\250\347\275\262\345\212\251\346\211\213\346\250\241\345\235\227\350\256\276\350\256\241.md" "b/docs/development/design/\351\203\250\347\275\262\345\212\251\346\211\213\346\250\241\345\235\227\350\256\276\350\256\241.md" similarity index 99% rename from "docs/development/\351\203\250\347\275\262\345\212\251\346\211\213\346\250\241\345\235\227\350\256\276\350\256\241.md" rename to "docs/development/design/\351\203\250\347\275\262\345\212\251\346\211\213\346\250\241\345\235\227\350\256\276\350\256\241.md" index d6f8dee94484ab8b5dc9e5d23646b8d362a62f99..3e468375de73b49b00f082c314db13b100a7839c 100644 --- "a/docs/development/\351\203\250\347\275\262\345\212\251\346\211\213\346\250\241\345\235\227\350\256\276\350\256\241.md" +++ "b/docs/development/design/\351\203\250\347\275\262\345\212\251\346\211\213\346\250\241\345\235\227\350\256\276\350\256\241.md" @@ -79,7 +79,6 @@ graph TB ```mermaid classDiagram class DeploymentConfig { - +server_ip: str +deployment_mode: str +llm: LLMConfig +embedding: EmbeddingConfig diff --git "a/docs/development/\351\205\215\347\275\256\347\256\241\347\220\206\346\250\241\345\235\227\350\256\276\350\256\241.md" "b/docs/development/design/\351\205\215\347\275\256\347\256\241\347\220\206\346\250\241\345\235\227\350\256\276\350\256\241.md" similarity index 100% rename from "docs/development/\351\205\215\347\275\256\347\256\241\347\220\206\346\250\241\345\235\227\350\256\276\350\256\241.md" rename to "docs/development/design/\351\205\215\347\275\256\347\256\241\347\220\206\346\250\241\345\235\227\350\256\276\350\256\241.md" diff --git "a/docs/development/\351\241\271\347\233\256\346\225\264\344\275\223\350\256\276\350\256\241.md" "b/docs/development/design/\351\241\271\347\233\256\346\225\264\344\275\223\350\256\276\350\256\241.md" similarity index 99% rename from "docs/development/\351\241\271\347\233\256\346\225\264\344\275\223\350\256\276\350\256\241.md" rename to "docs/development/design/\351\241\271\347\233\256\346\225\264\344\275\223\350\256\276\350\256\241.md" index b4179d6ff681c57b0c330dcc5689c6738603c2d8..2eefdedad36be3ad18c26cf0a83d2943131fc601 100644 --- "a/docs/development/\351\241\271\347\233\256\346\225\264\344\275\223\350\256\276\350\256\241.md" +++ "b/docs/development/design/\351\241\271\347\233\256\346\225\264\344\275\223\350\256\276\350\256\241.md" @@ -403,7 +403,6 @@ classDiagram } class DeploymentConfig { - +server_ip: str +enable_web: bool +enable_rag: bool +llm: LLMConfig diff --git "a/docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" "b/docs/development/i18n/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" similarity index 93% rename from "docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" rename to "docs/development/i18n/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" index 6c545f27fc918ef1b8975875f9e842a63609ac0a..d4c865fa1f54d78114ce04cfcce64c2e6c1c17d4 100644 --- "a/docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" +++ "b/docs/development/i18n/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" @@ -133,6 +133,26 @@ msgstr[0] "找到 {n} 个文件" 编译后的 `.mo` 文件会被应用程序加载和使用。 +#### 5. 去除重复条目(可选) + +如果翻译文件中存在重复的条目,可以使用 `uniq` 命令清理: + +```bash +./scripts/tools/i18n-manager.sh uniq +``` + +这会使用 `msguniq` 工具去除重复的翻译条目,保留第一次出现的条目。 + +#### 6. 查看翻译统计 + +查看翻译文件的完成情况和统计信息: + +```bash +./scripts/tools/i18n-manager.sh stats +``` + +这会显示每种语言的翻译进度,包括已翻译、未翻译和模糊翻译的数量。 + #### 完整流程(推荐) 你也可以一次执行所有步骤: @@ -363,7 +383,10 @@ oi --help # 应显示英文 ### 验证翻译完整性 ```bash -# 检查未翻译的字符串 +# 使用 i18n 管理工具查看所有语言的翻译统计 +./scripts/tools/i18n-manager.sh stats + +# 或直接使用 msgfmt 检查特定语言 msgfmt --check --statistics src/i18n/locales/zh_CN/LC_MESSAGES/messages.po ``` diff --git "a/docs/development/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/development/i18n/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" similarity index 67% rename from "docs/development/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" rename to "docs/development/i18n/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" index 3d8459d650a270c7bfd769e6d78dcd9c966dcccd..01fe7a61ac12911653f1ba0a4a9993c0ff9d568a 100644 --- "a/docs/development/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/development/i18n/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -52,9 +52,36 @@ greeting = _("Hello, {name}!").format(name=username) ./scripts/tools/i18n-manager.sh extract # 提取字符串 ./scripts/tools/i18n-manager.sh update # 更新翻译文件 # 编辑 .po 文件... +./scripts/tools/i18n-manager.sh uniq # 去除重复条目(可选) +./scripts/tools/i18n-manager.sh stats # 查看翻译统计(可选) ./scripts/tools/i18n-manager.sh compile # 编译翻译 ``` +### 管理工具命令 + +```bash +# 查看帮助 +./scripts/tools/i18n-manager.sh help + +# 提取可翻译字符串 +./scripts/tools/i18n-manager.sh extract + +# 更新翻译文件 +./scripts/tools/i18n-manager.sh update + +# 去除重复条目 +./scripts/tools/i18n-manager.sh uniq + +# 查看翻译统计 +./scripts/tools/i18n-manager.sh stats + +# 编译翻译文件 +./scripts/tools/i18n-manager.sh compile + +# 完整流程(提取→更新→编译) +./scripts/tools/i18n-manager.sh all +``` + ### 测试翻译 ```bash diff --git "a/docs/development/server-side/openEuler Intelligence \346\241\206\346\236\266\344\272\244\344\272\222/openEuler Intelligence \346\241\206\346\236\266\344\272\244\344\272\222\345\275\242\345\274\217.md" "b/docs/development/server-side/openEuler Intelligence \346\241\206\346\236\266\344\272\244\344\272\222/openEuler Intelligence \346\241\206\346\236\266\344\272\244\344\272\222\345\275\242\345\274\217.md" deleted file mode 100644 index a8df3528bede7ae19a56dfd91c269905543f0321..0000000000000000000000000000000000000000 --- "a/docs/development/server-side/openEuler Intelligence \346\241\206\346\236\266\344\272\244\344\272\222/openEuler Intelligence \346\241\206\346\236\266\344\272\244\344\272\222\345\275\242\345\274\217.md" +++ /dev/null @@ -1,708 +0,0 @@ -# openEuler Intelligence 框架交互 - -## 部署形态 - -在本地环境下 openEuler Intelligence 的部署形态如下图所示: - -![openEuler Inteligence 部署形态](.\openEuler_Inteligence_部署形态.png) - -首先,当前有一套(基于rpm的)一键部署的脚本来用于 openEuler Intelligence 的部署,如上图所示,运行 openEuelr Intelligence 的最小单位是框架 + MongoDB,并且在部署之前可以在框架指定的目录下存放对应的 mcp config 并且在一键部署的脚本中填写 Agent 的配置,那么会在框架服务启动的时候将 mcp 服务进行注册和构建默认的 Agent。 - -其次,为了轻量化部署,降低本地运行消耗,在这套部署体系下 Web 和知识库是可选的(PS:Web 为了便携用户后续 MCP 注册和 Agent 的构建,知识库则是提供了文档治理和知识检索的能力,可以让框架在基本问答模式下更好的运作)。 - -接着,用户要使用 openEuler Intelligence 服务,需要和安装服务的人在同一个组;在本地环境下 openEuler Intelligence 不需要鉴权即可访问,通过识别当前操作系统用户名称作为当前用户的 user_sub,后续只要使用同一个用户进行登入那么即可访问用户使用的一些列历史记录。 - -最后,openEuler 提供了几个 API 用于对外提供 Agent 的能力,具体如下,在后文对其中的 chat 接口返回的信息中会更详细的讲解: - -```text -GET /api/conversation (获取用户当前所有对话记录) - -POST /api/conversation(创建新对话) - -GET /api/record/{conversation_id}(获取会话下的所有用户和智能体的交互历史) - -GET /api/chat(在会话中通过流式的形式与智能体会话) - -GET /api/app(获取当前用户可用的智能体) -``` - -接口的输入&输出及详情可以通过加入下面团队进行访问 - - - -## openEuler Intelligence 框架交互逻辑 - -### 用户整体的使用逻辑如下图所示 - -```mermaid -flowchart TB - user[用户] - state1[激活mcp] - state2[构建Agent应用] - state3[使用Agent应用] - state4[确认执行工具] - state5[工具执行] - state7[用户手动填参] - state8[执行结束] - user-->state1 - state1-->state2 - state2-->state3 - state3-->conda1{用户是否开启自动执行} - conda1-->|yes|state5 - conda1-->|no|state4 - state4-->conda2{是否执行} - conda2-->|yes|state5 - conda2-->|no|state8 - state5-->conda4{是否达到最后一步} - conda4-->|yes|state8 - conda4-->|no|conda3{工具执行是否报错} - conda3-->|yes|state7 - conda3-->|no|conda1 - state7-->state5 -``` - -### MCP 注册 - -```mermaid -flowchart TB - user[用户] - web[openEuler intelligence前端] - process[进程管理] - framework[框架] - os[操作系统] - user--[1]注册mcp-->web - web--[9]可视化展示mcp安装状态-->user - web--[2]传输mcp config+mcp env-->framework - framework--[3]存储 mcp config-->os - os--[4]存储成功/失败-->framework - framework--[5]创建进程安装mcp相关的依赖-->process - process--[6]返回mcp相关的依赖安装结果-->framework - web--[7]轮询mcp安装状态-->framework - framework--[8]返回mcp安装状态-->web -``` - -### Agent 构建 - -```mermaid -flowchart TB - user[用户] - web[openEuler intelligence前端] - framework[框架] - mongodb[数据库] - user--[1]创建基于mcp的Agent应用-->web - web--[2]发送用户选择的mcp的列表和Agent应用的基本参数-->framwork - framwork--[3]存储Agent信息-->mongodb - mongodb--[4]存储成功/失败-->framework - framework--[5]返回Agent创建结果-->web - web--[6]可视化Agent创建结果-->user -``` - -### 交互 - -这里的交互是指用户与Agent的交互,下面两个主要的交互示意图,一个主要是描述用户行为一个主要描述后端行为 - -#### 用户交互示意图 - -```mermaid -flowchart TB - user[用户] - state1[流程(重)规划] - state2[询问用户是否执行工具] - state3[用户补充参数] - state4[执行工具前的描述] - state8[(重)生成参数] - state5[执行工具] - state6[总结] - state7[流结束执行] - user--使用Agent应用-->state1 - state1-->state2 - state2-->conda1{yes/no} - conda1-->|yes|state4 - state4-->state8 - state8-->state5 - conda1-->|no|state7 - state6-->state7 - state5-->conda2{执行成功或者失败} - conda2-->|成功|conda5{流程是否执行完毕} - conda5-->|是|state6 - conda5-->|否|state2 - conda2-->|失败|conda3{当前计划是否可以修复} - conda3-->|是|conda4{是否是因为当前步骤缺失参数引起的执行错误} - conda3-->|否|state7 - conda4-->|是|state3 - conda4-->|否|conda6{是否需要重新规划} - conda6-->|是|state1 - conda6-->|否|state8 - state3-->state8 -``` - -在使用chat接口与后端交互的时候会接收到流式返回的信息,单条流式消息内容模板如下(后续将与Agent的交互流程称为流): - -```json -{ - "event": "heartbeat", - "id": "550e8400-e29b-41d4-a716-446655440000", - "groupId": "550e8400-e29b-41d4-a716-446655440001", - "conversationId": "550e8400-e29b-41d4-a716-446655440002", - "taskId": "550e8400-e29b-41d4-a716-446655440003", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "stepId": "23641ffd-a8db-403a-9cf8-5ca305db3a0d", - "stepName": "大模型", - "stepStatus": "running" - } - "content": { - "text": "这是一条心跳消息内容", - "type": "heartbeat_info" - }, - "metadata": { - "inputTokens": 0, - "outputTokens": 0, - "timeCost": 0.05, - "feature": null, - "footNoteMetadataList": [] - } -} -``` - -event 代表这条流式消息的类型 - -groupId 代表这条流式消息关联的消息组 id - -id 代表这条流式消息关联的消息记录的 id - -conversationId 代表这条流式消息关联的对话的 id - -taskId 代表这条流式消息关联的任务的 id - -flow 代表这条流式消息关联的执行流的信息(内部含有当前执行的流来自的应用以及流的状态和基本等信息) - -context 存放了前端真实要渲染的内容步骤的出参、入参和回显的文字等 - -节下面的事件类型和上面的模板下面有几种衍生信息 - -## 一、流状态转换关系 - -**初始化(init)→ 运行(running)** -触发条件:流初始化完成后开始工作 -对应消息:init 消息后触发 flow.start 消息 -**运行(running)→ 暂停(stop)** -触发条件:步骤需要用户确认执行(step.waiting_for_start)或等待补充参数(step.waiting_for_param) -对应消息:flow.stop 消息随步骤等待消息一同发送 -**暂停(stop)→ 运行(running)** -触发条件:用户确认执行步骤或补充参数后,流恢复执行 -对应消息:无单独流启动消息,通过步骤状态变更(step.input)体现流恢复运行 -**运行(running)→ 失败(error)** -触发条件:单个步骤多次执行失败或其他异常 -对应消息:flow.failed 消息,包含失败步骤信息 -**运行 / 暂停(running/stop)→ 取消(cancel)** -触发条件:用户主动暂停对话或取消步骤执行 -对应消息:flow.cancel 消息,同步更新流和步骤状态 -**运行(running)→ 成功(success)** -触发条件:所有步骤执行完成且无异常 -对应消息:flow.success 消息,标识流执行结束 -**成功 / 失败(success/error)→ 通道关闭** -触发条件:流执行完成(无论成功或失败) -对应消息:成功时输出 [DONE],失败时输出 [ERROR] - -## 二、步骤状态转换关系 - -**初始化(init)→ 等待确认(waiting_for_start)** -触发条件:步骤初始化后,需用户确认是否执行(非自动执行模式) -对应消息:step.waiting_for_start 消息,包含风险提示 -**初始化(init)→ 运行中(running)** -触发条件:步骤初始化后,无需用户确认(自动执行模式) -对应消息:无单独步骤运行消息,通过 step.input 消息体现 -**等待确认(waiting_for_start)→ 运行中(running)** -触发条件:用户确认执行步骤 -对应消息:step.input 消息,输出步骤入参 -**等待确认(waiting_for_start)→ 取消(cancel)** -触发条件:用户拒绝执行步骤 -对应消息:step.cancel 消息,同步终止流和步骤 -**运行中(running)→ 成功(success)** -触发条件:步骤执行完成且无错误 -对应消息:step.output 消息,输出步骤出参 -**运行中(running)→ 等待参数(waiting_for_param)** -触发条件:步骤执行时发现参数缺失,需用户确认(非自动执行模式) -对应消息:step.waiting_for_param 消息,提示需补充的参数 -**等待参数(waiting_for_param)→ 运行中(running)** -触发条件:用户补充参数后,步骤重新执行 -对应消息:step.input 消息,使用补充后的参数执行 -**运行中 / 等待确认 / 等待参数(running/waiting_for_start/waiting_for_param)→ 失败(error)** -触发条件:步骤执行报错且重试后仍失败 -对应消息:无单独步骤失败消息,通过 flow.failed 消息体现 -三、辅助状态转换关系 -心跳状态(heartbeat):无转换关系,在流运行期间定期发送,不影响其他状态 -文本 / 文档输出状态(text.add/document.add):仅在流和步骤处于 running 状态时触发,不改变主状态 -步骤成功(success)→ 下步骤初始化(init):当前步骤成功后,自动触发下一步骤初始化,对应 step.init 消息 - -```python -class EventType(str, Enum): - """事件类型""" - - HEARTBEAT = "heartbeat" - INIT = "init", - TEXT_ADD = "text.add" - GRAPH = "graph" - DOCUMENT_ADD = "document.add" - STEP_WAITING_FOR_START = "step.waiting_for_start" - STEP_WAITING_FOR_PARAM = "step.waiting_for_param" - FLOW_START = "flow.start" - STEP_INIT = "step.init" - STEP_INPUT = "step.input" - STEP_OUTPUT = "step.output" - STEP_CANCEL = "step.cancel" - STEP_ERROR = "step.error" - FLOW_STOP = "flow.stop" - FLOW_FAILED = "flow.failed" - FLOW_SUCCESS = "flow.success" - FLOW_CANCEL = "flow.cancel" - DONE = "done" -``` - -**流初始化信息**:代表当前流初始化完成要开始工作了 - -```json -data: { - "event": "init", - "id": "99473b7c-e158-49c6-b075-8232ad68d7a2", - "groupId": "154054b1-5237-4cb4-adbe-148d1f395ec5", - "conversationId": "b5c690b9-4232-4fb8-b0da-15ead469589c", - "taskId": "31e66cf6-9d60-445c-9bad-191c0f16c29f", - "content": { - "feature": { - "maxTokens": 8192, - "contextNum": 3, - "enableFeedback": false, - "enableRegenerate": false - }, - "createdAt": 1753621532.719 - }, - "metadata": { - "inputTokens": 0, - "outputTokens": 0, - "timeCost": 0.011, - "footNoteMetadataList": [] - } -} -``` - -**流开始信息**:代表流(重新)开始工作 - -```json -data: { - "event": "flow.start", - "id": "99473b7c-e158-49c6-b075-8232ad68d7a2", - "groupId": "154054b1-5237-4cb4-adbe-148d1f395ec5", - "conversationId": "b5c690b9-4232-4fb8-b0da-15ead469589c", - "taskId": "31e66cf6-9d60-445c-9bad-191c0f16c29f", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "running", - "stepId": "", - "stepName": "", - "stepStatus": "unkown" - }, - "content": {}, - "metadata": { - "inputTokens": 0, - "outputTokens": 0, - "timeCost": 0.942, - "footNoteMetadataList": [] - } -} -``` - -**流运行失败的消息**:单个步骤多次执行失败或其他异常清空流进入失败状态 - -```json -data: { - "event": "flow.failed", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "error", - "stepId": "722f636a-4a66-4feb-80dd-7b2ea50ab494", - "stepName": "shell工具执行", - "stepStatus": "error", - }, - "content": {}, - "metadata": { - "inputTokens": 1004, - "outputTokens": 62, - "timeCost": 7.069, - "footNoteMetadataList": [] - } -} -``` - -**流成功暂停运行的消息**:在步骤等待确认执行和待用户手填参数的时候会推出这个消息 - -```json -data: { - "event": "flow.stop", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "stop", - "stepId": "722f636a-4a66-4feb-80dd-7b2ea50ab494", - "stepName": "shell工具执行", - "stepStatus": "waiting" - }, - "content": {}, - "metadata": { - "inputTokens": 1004, - "outputTokens": 62, - "timeCost": 7.069, - "footNoteMetadataList": [] - } -} -``` - -**流取消运行的消息**:用户暂停对话或者取消某个步骤的执行 - -```json -data: { - "event": "flow.cancel", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "cancel", - "stepId": "722f636a-4a66-4feb-80dd-7b2ea50ab494", - "stepName": "记忆存储", - "stepStatus": "cancel" - }, - "content": {}, - "metadata": { - "inputTokens": 1004, - "outputTokens": 62, - "timeCost": 7.069, - "footNoteMetadataList": [] - } -} -``` - -**流成功运行的消息**:流执行成功的消息 - -```json -data: { - "event": "flow.success", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "success", - "stepId": "722f636a-4a66-4feb-80dd-7b2ea50ab494", - "stepName": "记忆存储", - "stepStatus": "success" - }, - "content": {}, - "metadata": { - "inputTokens": 1004, - "outputTokens": 62, - "timeCost": 7.069, - "footNoteMetadataList": [] - } -} -``` - -**MCP tool 初始化消息:** - -```json -data: { - "event": "step.init", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "running", - "stepId": "b61aae5a-ed17-40ba-9b2c-6a96a0f0878a", - "stepName": "知识库", - "stepStatus": "init" - }, - "content": { - }, - "metadata": { - "inputTokens": 195, - "outputTokens": 15, - "timeCost": 0.002, - "footNoteMetadataList": [] - } -} -``` - -**MCP tool等待用户确认执行的信息**: - -```json -data: { - "event": "step.waiting_for_start", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "stop", - "stepId": "b61aae5a-ed17-40ba-9b2c-6a96a0f0878a", - "stepName": "知识库", - "stepStatus": "waiting" - }, - "content": { - "risk": "low", -​ "reason": "知识库查询可能会造成时延!" -​ }, -​ "metadata": { -​ "inputTokens": 195, -​ "outputTokens": 15, -​ "timeCost": 0.002, -​ "footNoteMetadataList": [] -​ } -} -``` - -**MCP tool等待用户补充参数:** - -```json -data: { - "event": "step.waiting_for_param", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "stop", - "stepId": "b61aae5a-ed17-40ba-9b2c-6a96a0f0878a", - "stepName": "高德地图", - "stepStatus": "param" - }, - "content": { - "message": "当运行产生如下报错:\n缺少access key" , - "params": params_with_null - }, - "metadata": { - "inputTokens": 195, - "outputTokens": 15, - "timeCost": 0.002, - "footNoteMetadataList": [] - } -} -``` - -**MCP tool 取消执行:** - -```json -data: { - "event": "step.cancal", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowName": "test", - "flowStatus": "cancel", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "stepId": "b61aae5a-ed17-40ba-9b2c-6a96a0f0878a", - "stepName": "高德地图", - "stepStatus": "cancel" - }, - "content": { - }, - "metadata": { - "inputTokens": 195, - "outputTokens": 15, - "timeCost": 0.002, - "footNoteMetadataList": [] - } -} -``` - -**MCP tool执行的入参信息**:content中是入参的真实内容 - -```json -data: { - "event": "step.input", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "running", - "stepId": "b61aae5a-ed17-40ba-9b2c-6a96a0f0878a", - "stepName": "end", - "stepStatus": "running" - }, - "content": { - "session_id": "fcdbca3513c8e61de58eeeef88c8ada6", - "kbIds": [], - "topK": 5, - "query": "231", - "searchMethod": "keyword_and_vector", - "isRelatedSurrounding": true, - "isClassifyByDoc": false, - "isRerank": false, - "isCompress": false, - "tokensLimit": 8192 - }, - "metadata": { - "inputTokens": 195, - "outputTokens": 15, - "timeCost": 0.002, - "footNoteMetadataList": [] - } -} - - -``` - -**Mcp tool执行的出参信息**:content中是入参的真实内容 - -```json -data: { - "event": "step.output", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "running", - "stepId": "b61aae5a-ed17-40ba-9b2c-6a96a0f0878a", - "stepName": "知识库", - "stepStatus": "success" - }, - "content": { - "question": "231", - "corpus": [] - }, - "metadata": { - "inputTokens": 590, - "outputTokens": 26, - "timeCost": 1.855, - "footNoteMetadataList": [] - } -} -``` - -**Agent运行过程中输出文本消息**:交互过程中大模型的总结 - -```json -data: { - "event": "text.add", - "id": "d6f49ddf-0599-4950-89bf-cffec4bddc72", - "groupId": "68bcb0aa-a1db-4be0-bef7-a70a8ba2360e", - "conversationId": "5cdc5c08-5118-468d-80b4-27f906961266", - "taskId": "0c0ff56f-8352-4835-a278-cf39ea943b15", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "running", - "stepId": "23641ffd-a8db-403a-9cf8-5ca305db3a0d", - "stepName": "总结", - "stepStatus": "running" - }, - "content": { - "text": "你好" - }, - "metadata": { - "inputTokens": 590, - "outputTokens": 26, - "timeCost": 1.351, - "footNoteMetadataList": [] - } -} -``` - -**Agent运行过程中输出文档消息**:当前文档来自于rag,后续如果有来自于网上或者实时生成可以考虑在content加入documentSource字段来动态获取文档本体 - -```json -data: { - "event": "document.add", - "id": "d9582240-b7e4-4e7c-8443-02385f9ef178", - "groupId": "a619941d-12f6-49d3-a8c0-95e089932539", - "conversationId": "ba3c4e4f-1ae3-42cd-878f-d3261618cf29", - "taskId": "db458861-9e1c-4de0-aab2-48c2c285cc96", - "flow": { - "appId": "3514eec9-699c-4ebd-85cc-e92caa24e141", - "flowId": "0b7b5949-01f4-4e41-871e-d78fe01ec455", - "flowName": "test", - "flowStatus": "running", - "stepId": "23641ffd-a8db-403a-9cf8-5ca305db3a0d", - "stepName": "文档生成", - "stepStatus": "running" - }, - "content": { - "documentId": "3760ae3e-b55a-4c30-8785-58f5e4a4e0dd", - "documentOrder": 1, - "documentAuthor": "42497", - "documentName": "openEuler 24.03 LTS SP1 技术白皮书-1-13.pdf", - "documentAbstract": "openEuler 技术白皮书\nopenEuler 24.03 LTS SP1 技术白皮书\n1.概述\nOpenAtom openEuler(简称“openEuler”) 社区是一个面向数字基础设施操作系统的开源社区。由开\n放原子开源基金会(以下简称“基金会", - "documentType": "pdf", - "documentSize": 778615.0, - "createdAt": 1753623806.708 - }, - "metadata": { - "inputTokens": 8306, - "outputTokens": 0, - "timeCost": 2.191, - "footNoteMetadataList": [] - } -} -``` - -**流运行成功消息通道关闭的特殊消息**:意味着前端循环可以break了 - -```text -data: [DONE] -``` - -**流运行失败消息通道关闭的特殊消息**:意味着前端循环可以break了 - -```text -data: [ERROR] -``` - -**心跳消息**:保活 - -```json -data: {"event": "heartbeat"} -``` - -状态转换关系描述 diff --git "a/docs/development/server-side/openEuler Intelligence \346\241\206\346\236\266\344\272\244\344\272\222/openEuler_Inteligence_\351\203\250\347\275\262\345\275\242\346\200\201.png" "b/docs/development/server-side/openEuler Intelligence \346\241\206\346\236\266\344\272\244\344\272\222/openEuler_Inteligence_\351\203\250\347\275\262\345\275\242\346\200\201.png" deleted file mode 100644 index b7f58d0f3ae980ea7bd6ec33c6d6fc7e69c9e437..0000000000000000000000000000000000000000 Binary files "a/docs/development/server-side/openEuler Intelligence \346\241\206\346\236\266\344\272\244\344\272\222/openEuler_Inteligence_\351\203\250\347\275\262\345\275\242\346\200\201.png" and /dev/null differ diff --git a/docs/development/server-side/openHermes.openapi.json b/docs/development/server-side/openHermes.openapi.json deleted file mode 100644 index 4c898ca31d8524a895da54cfb1082448882e949f..0000000000000000000000000000000000000000 --- a/docs/development/server-side/openHermes.openapi.json +++ /dev/null @@ -1,5219 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { "title": "FastAPI", "version": "0.1.0" }, - "paths": { - "/api/conversation": { - "get": { - "tags": ["conversation"], - "summary": "Get Conversation List", - "description": "获取对话列表", - "operationId": "get_conversation_list_api_conversation_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ConversationListRsp" } - } - } - }, - "500": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Internal Server Error" - } - } - }, - "post": { - "tags": ["conversation"], - "summary": "Add Conversation", - "description": "手动创建新对话", - "operationId": "add_conversation_api_conversation_post", - "parameters": [ - { - "name": "appId", - "in": "query", - "required": false, - "schema": { "type": "string", "default": "", "title": "Appid" } - }, - { - "name": "debug", - "in": "query", - "required": false, - "schema": { "type": "boolean", "default": false, "title": "Debug" } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_add_conversation_api_conversation_post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/AddConversationRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "put": { - "tags": ["conversation"], - "summary": "Update Conversation", - "description": "更新特定Conversation的数据", - "operationId": "update_conversation_api_conversation_put", - "parameters": [ - { - "name": "conversationId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Conversationid" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModifyConversationData" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateConversationRsp" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "delete": { - "tags": ["conversation"], - "summary": "Delete Conversation", - "description": "删除特定对话", - "operationId": "delete_conversation_api_conversation_delete", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeleteConversationData" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/auth/login": { - "get": { - "tags": ["auth"], - "summary": "Oidc Login", - "description": "OIDC login\n\n:param request: Request object\n:param code: OIDC code\n:return: HTMLResponse", - "operationId": "oidc_login_api_auth_login_get", - "parameters": [ - { - "name": "code", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Code" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/auth/logout": { - "get": { - "tags": ["auth"], - "summary": "Logout", - "description": "用户登出EulerCopilot", - "operationId": "logout_api_auth_logout_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - } - } - }, - "post": { - "tags": ["auth"], - "summary": "Oidc Logout", - "description": "OIDC主动触发登出", - "operationId": "oidc_logout_api_auth_logout_post", - "parameters": [ - { - "name": "token", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Token" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/auth/redirect": { - "get": { - "tags": ["auth"], - "summary": "Oidc Redirect", - "description": "OIDC重定向URL", - "operationId": "oidc_redirect_api_auth_redirect_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/OidcRedirectRsp" } - } - } - } - } - } - }, - "/api/auth/user": { - "get": { - "tags": ["auth"], - "summary": "Userinfo", - "description": "获取用户信息", - "operationId": "userinfo_api_auth_user_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/AuthUserRsp" } - } - } - } - } - } - }, - "/api/auth/update_revision_number": { - "post": { - "tags": ["auth"], - "summary": "Update Revision Number", - "description": "更新用户协议信息", - "operationId": "update_revision_number_api_auth_update_revision_number_post", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/AuthUserRsp" } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - } - } - } - }, - "/api/auth/key": { - "get": { - "tags": ["key"], - "summary": "Check Api Key Existence", - "description": "检查API密钥是否存在", - "operationId": "check_api_key_existence_api_auth_key_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/GetAuthKeyRsp" } - } - } - } - } - }, - "post": { - "tags": ["key"], - "summary": "Manage Api Key", - "description": "管理用户的API密钥", - "operationId": "manage_api_key_api_auth_key_post", - "parameters": [ - { - "name": "action", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Action" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/PostAuthKeyRsp" } - } - } - }, - "400": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Bad Request" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/app": { - "get": { - "tags": ["appcenter"], - "summary": "Get Applications", - "description": "获取应用列表", - "operationId": "get_applications_api_app_get", - "parameters": [ - { - "name": "createdByMe", - "in": "query", - "required": false, - "schema": { - "type": "boolean", - "description": "筛选我创建的", - "default": false, - "title": "Createdbyme" - }, - "description": "筛选我创建的" - }, - { - "name": "favorited", - "in": "query", - "required": false, - "schema": { - "type": "boolean", - "description": "筛选我收藏的", - "default": false, - "title": "Favorited" - }, - "description": "筛选我收藏的" - }, - { - "name": "keyword", - "in": "query", - "required": false, - "schema": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "description": "搜索关键字", - "title": "Keyword" - }, - "description": "搜索关键字" - }, - { - "name": "appType", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { "$ref": "#/components/schemas/AppType" }, - { "type": "null" } - ], - "description": "应用类型", - "title": "Apptype" - }, - "description": "应用类型" - }, - { - "name": "page", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "description": "页码", - "default": 1, - "title": "Page" - }, - "description": "页码" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { "$ref": "#/components/schemas/GetAppListRsp" }, - { "$ref": "#/components/schemas/ResponseData" } - ], - "title": "Response Get Applications Api App Get" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "post": { - "tags": ["appcenter"], - "summary": "Create Or Update Application", - "description": "创建或更新应用", - "operationId": "create_or_update_application_api_app_post", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/CreateAppRequest" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { "$ref": "#/components/schemas/BaseAppOperationRsp" }, - { "$ref": "#/components/schemas/ResponseData" } - ], - "title": "Response Create Or Update Application Api App Post" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/app/recent": { - "get": { - "tags": ["appcenter"], - "summary": "Get Recently Used Applications", - "description": "获取最近使用的应用", - "operationId": "get_recently_used_applications_api_app_recent_get", - "parameters": [ - { - "name": "count", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "maximum": 10, - "minimum": 1, - "default": 5, - "title": "Count" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { "$ref": "#/components/schemas/GetRecentAppListRsp" }, - { "$ref": "#/components/schemas/ResponseData" } - ], - "title": "Response Get Recently Used Applications Api App Recent Get" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/app/{appId}": { - "get": { - "tags": ["appcenter"], - "summary": "Get Application", - "description": "获取应用详情", - "operationId": "get_application_api_app__appId__get", - "parameters": [ - { - "name": "appId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "应用ID", - "title": "Appid" - }, - "description": "应用ID" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { "$ref": "#/components/schemas/GetAppPropertyRsp" }, - { "$ref": "#/components/schemas/ResponseData" } - ], - "title": "Response Get Application Api App Appid Get" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "delete": { - "tags": ["appcenter"], - "summary": "Delete Application", - "description": "删除应用", - "operationId": "delete_application_api_app__appId__delete", - "parameters": [ - { - "name": "appId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "应用ID", - "title": "Appid" - }, - "description": "应用ID" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { "$ref": "#/components/schemas/BaseAppOperationRsp" }, - { "$ref": "#/components/schemas/ResponseData" } - ], - "title": "Response Delete Application Api App Appid Delete" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "post": { - "tags": ["appcenter"], - "summary": "Publish Application", - "description": "发布应用", - "operationId": "publish_application_api_app__appId__post", - "parameters": [ - { - "name": "appId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "应用ID", - "title": "Appid" - }, - "description": "应用ID" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/BaseAppOperationRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "put": { - "tags": ["appcenter"], - "summary": "Modify Favorite Application", - "description": "更改应用收藏状态", - "operationId": "modify_favorite_application_api_app__appId__put", - "parameters": [ - { - "name": "appId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "应用ID", - "title": "Appid" - }, - "description": "应用ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ModFavAppRequest" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { "$ref": "#/components/schemas/ModFavAppRsp" }, - { "$ref": "#/components/schemas/ResponseData" } - ], - "title": "Response Modify Favorite Application Api App Appid Put" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/service": { - "get": { - "tags": ["service-center"], - "summary": "Get Service List", - "description": "获取服务列表", - "operationId": "get_service_list_api_service_get", - "parameters": [ - { - "name": "createdByMe", - "in": "query", - "required": false, - "schema": { - "type": "boolean", - "description": "筛选我创建的", - "default": false, - "title": "Createdbyme" - }, - "description": "筛选我创建的" - }, - { - "name": "favorited", - "in": "query", - "required": false, - "schema": { - "type": "boolean", - "description": "筛选我收藏的", - "default": false, - "title": "Favorited" - }, - "description": "筛选我收藏的" - }, - { - "name": "searchType", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/SearchType", - "description": "搜索类型", - "default": "all" - }, - "description": "搜索类型" - }, - { - "name": "keyword", - "in": "query", - "required": false, - "schema": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "description": "搜索关键字", - "title": "Keyword" - }, - "description": "搜索关键字" - }, - { - "name": "page", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "description": "页码", - "default": 1, - "title": "Page" - }, - "description": "页码" - }, - { - "name": "pageSize", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "maximum": 100, - "minimum": 1, - "description": "每页数量", - "default": 16, - "title": "Pagesize" - }, - "description": "每页数量" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { "$ref": "#/components/schemas/GetServiceListRsp" }, - { "$ref": "#/components/schemas/ResponseData" } - ], - "title": "Response Get Service List Api Service Get" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "post": { - "tags": ["service-center"], - "summary": "Update Service", - "description": "上传并解析服务", - "operationId": "update_service_api_service_post", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateServiceRequest", - "description": "上传 YAML 文本对应数据对象" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/UpdateServiceRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/service/{serviceId}": { - "get": { - "tags": ["service-center"], - "summary": "Get Service Detail", - "description": "获取服务详情", - "operationId": "get_service_detail_api_service__serviceId__get", - "parameters": [ - { - "name": "serviceId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "服务ID", - "title": "Serviceid" - }, - "description": "服务ID" - }, - { - "name": "edit", - "in": "query", - "required": false, - "schema": { - "type": "boolean", - "description": "是否为编辑模式", - "default": false, - "title": "Edit" - }, - "description": "是否为编辑模式" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/GetServiceDetailRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "delete": { - "tags": ["service-center"], - "summary": "Delete Service", - "description": "删除服务", - "operationId": "delete_service_api_service__serviceId__delete", - "parameters": [ - { - "name": "serviceId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "服务ID", - "title": "Serviceid" - }, - "description": "服务ID" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/DeleteServiceRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "put": { - "tags": ["service-center"], - "summary": "Modify Favorite Service", - "description": "修改服务收藏状态", - "operationId": "modify_favorite_service_api_service__serviceId__put", - "parameters": [ - { - "name": "serviceId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "服务ID", - "title": "Serviceid" - }, - "description": "服务ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModFavServiceRequest", - "description": "更改收藏状态请求对象" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ModFavServiceRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/comment": { - "post": { - "tags": ["comment"], - "summary": "Add Comment", - "description": "给Record添加评论", - "operationId": "add_comment_api_comment_post", - "requestBody": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/AddCommentData" } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/record/{conversation_id}": { - "get": { - "tags": ["record"], - "summary": "Get Record", - "description": "获取某个对话的所有问答对", - "operationId": "get_record_api_record__conversation_id__get", - "parameters": [ - { - "name": "conversation_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Conversation Id" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/RecordListRsp" } - } - } - }, - "403": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Forbidden" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/health_check": { - "get": { - "tags": ["health_check"], - "summary": "Health Check", - "description": "健康检查接口", - "operationId": "health_check_health_check_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HealthCheckRsp" } - } - } - } - } - } - }, - "/api/chat": { - "post": { - "tags": ["chat"], - "summary": "Chat", - "description": "LLM流式对话接口", - "operationId": "chat_api_chat_post", - "requestBody": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/RequestData" } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/stop": { - "post": { - "tags": ["chat"], - "summary": "Stop Generation", - "description": "停止生成", - "operationId": "stop_generation_api_stop_post", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - } - } - } - }, - "/api/blacklist/user": { - "get": { - "tags": ["blacklist"], - "summary": "Get Blacklist User", - "description": "获取黑名单用户", - "operationId": "get_blacklist_user_api_blacklist_user_get", - "parameters": [ - { - "name": "page", - "in": "query", - "required": false, - "schema": { "type": "integer", "default": 0, "title": "Page" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/GetBlacklistUserRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "post": { - "tags": ["blacklist"], - "summary": "Change Blacklist User", - "description": "操作黑名单用户", - "operationId": "change_blacklist_user_api_blacklist_user_post", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/UserBlacklistRequest" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/blacklist/question": { - "get": { - "tags": ["blacklist"], - "summary": "Get Blacklist Question", - "description": "获取黑名单问题\n\n目前情况下,先直接输出问题,不做用户类型校验", - "operationId": "get_blacklist_question_api_blacklist_question_get", - "parameters": [ - { - "name": "page", - "in": "query", - "required": false, - "schema": { "type": "integer", "default": 0, "title": "Page" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetBlacklistQuestionRsp" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "post": { - "tags": ["blacklist"], - "summary": "Change Blacklist Question", - "description": "黑名单问题检测或操作", - "operationId": "change_blacklist_question_api_blacklist_question_post", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/QuestionBlacklistRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/blacklist/complaint": { - "post": { - "tags": ["blacklist"], - "summary": "Abuse Report", - "description": "用户实施举报", - "operationId": "abuse_report_api_blacklist_complaint_post", - "requestBody": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/AbuseRequest" } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/blacklist/abuse": { - "get": { - "tags": ["blacklist"], - "summary": "Get Abuse Report", - "description": "获取待审核的问答对", - "operationId": "get_abuse_report_api_blacklist_abuse_get", - "parameters": [ - { - "name": "page", - "in": "query", - "required": false, - "schema": { "type": "integer", "default": 0, "title": "Page" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetBlacklistQuestionRsp" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "post": { - "tags": ["blacklist"], - "summary": "Change Abuse Report", - "description": "对被举报问答对进行操作", - "operationId": "change_abuse_report_api_blacklist_abuse_post", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/AbuseProcessRequest" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/document/{conversation_id}": { - "post": { - "tags": ["document"], - "summary": "Document Upload", - "description": "上传文档", - "operationId": "document_upload_api_document__conversation_id__post", - "parameters": [ - { - "name": "conversation_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Conversation Id" } - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_document_upload_api_document__conversation_id__post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "get": { - "tags": ["document"], - "summary": "Get Document List", - "description": "获取文档列表", - "operationId": "get_document_list_api_document__conversation_id__get", - "parameters": [ - { - "name": "conversation_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Conversation Id" } - }, - { - "name": "used", - "in": "query", - "required": false, - "schema": { "type": "boolean", "default": false, "title": "Used" } - }, - { - "name": "unused", - "in": "query", - "required": false, - "schema": { "type": "boolean", "default": true, "title": "Unused" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ConversationDocumentRsp" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/document/{document_id}": { - "delete": { - "tags": ["document"], - "summary": "Delete Single Document", - "description": "删除单个文件", - "operationId": "delete_single_document_api_document__document_id__delete", - "parameters": [ - { - "name": "document_id", - "in": "path", - "required": true, - "schema": { "type": "string", "title": "Document Id" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/knowledge": { - "get": { - "tags": ["knowledge"], - "summary": "List Kb", - "description": "获取当前用户的知识库ID", - "operationId": "list_kb_api_knowledge_get", - "parameters": [ - { - "name": "conversationId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Conversationid" } - }, - { - "name": "kbId", - "in": "query", - "required": false, - "schema": { "type": "string", "title": "Kbid" } - }, - { - "name": "kbName", - "in": "query", - "required": false, - "schema": { "type": "string", "default": "", "title": "Kbname" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListTeamKnowledgeRsp" - } - } - } - }, - "404": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "put": { - "tags": ["knowledge"], - "summary": "Update Conversation Kb", - "description": "更新当前用户的知识库ID", - "operationId": "update_conversation_kb_api_knowledge_put", - "parameters": [ - { - "name": "conversationId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Conversationid" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/UpdateKbReq" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/llm/provider": { - "get": { - "tags": ["llm"], - "summary": "List Llm Provider", - "description": "获取大模型提供商列表", - "operationId": "list_llm_provider_api_llm_provider_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ListLLMProviderRsp" } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - } - } - } - }, - "/api/llm": { - "get": { - "tags": ["llm"], - "summary": "List Llm", - "description": "获取大模型列表", - "operationId": "list_llm_api_llm_get", - "parameters": [ - { - "name": "llmId", - "in": "query", - "required": false, - "schema": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "description": "大模型ID", - "title": "Llmid" - }, - "description": "大模型ID" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ListLLMRsp" } - } - } - }, - "404": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "put": { - "tags": ["llm"], - "summary": "Create Llm", - "description": "创建或更新大模型配置", - "operationId": "create_llm_api_llm_put", - "parameters": [ - { - "name": "llmId", - "in": "query", - "required": false, - "schema": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "description": "大模型ID", - "title": "Llmid" - }, - "description": "大模型ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/UpdateLLMReq" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "404": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "delete": { - "tags": ["llm"], - "summary": "Delete Llm", - "description": "删除大模型配置", - "operationId": "delete_llm_api_llm_delete", - "parameters": [ - { - "name": "llmId", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "大模型ID", - "title": "Llmid" - }, - "description": "大模型ID" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "404": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/llm/conv": { - "put": { - "tags": ["llm"], - "summary": "Update Conv Llm", - "description": "更新对话的知识库", - "operationId": "update_conv_llm_api_llm_conv_put", - "parameters": [ - { - "name": "conversationId", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "对话ID", - "title": "Conversationid" - }, - "description": "对话ID" - }, - { - "name": "llmId", - "in": "query", - "required": false, - "schema": { - "type": "string", - "description": "llm ID", - "default": "empty", - "title": "Llmid" - }, - "description": "llm ID" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "404": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/mcp": { - "get": { - "tags": ["mcp-service"], - "summary": "Get Mcpservice List", - "description": "获取服务列表", - "operationId": "get_mcpservice_list_api_mcp_get", - "parameters": [ - { - "name": "searchType", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/SearchType", - "description": "搜索类型", - "default": "all" - }, - "description": "搜索类型" - }, - { - "name": "keyword", - "in": "query", - "required": false, - "schema": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "description": "搜索关键字", - "title": "Keyword" - }, - "description": "搜索关键字" - }, - { - "name": "page", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "description": "页码", - "default": 1, - "title": "Page" - }, - "description": "页码" - }, - { - "name": "isActive", - "in": "query", - "required": false, - "schema": { - "anyOf": [{ "type": "boolean" }, { "type": "null" }], - "description": "是否激活", - "title": "Isactive" - }, - "description": "是否激活" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { "$ref": "#/components/schemas/GetMCPServiceListRsp" }, - { "$ref": "#/components/schemas/ResponseData" } - ], - "title": "Response Get Mcpservice List Api Mcp Get" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "post": { - "tags": ["mcp-service"], - "summary": "Create Or Update Mcpservice", - "description": "新建或更新MCP服务", - "operationId": "create_or_update_mcpservice_api_mcp_post", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateMCPServiceRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/UpdateMCPServiceRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/mcp/{serviceId}": { - "get": { - "tags": ["mcp-service"], - "summary": "Get Service Detail", - "description": "获取MCP服务详情", - "operationId": "get_service_detail_api_mcp__serviceId__get", - "parameters": [ - { - "name": "serviceId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "服务ID", - "title": "Serviceid" - }, - "description": "服务ID" - }, - { - "name": "edit", - "in": "query", - "required": false, - "schema": { - "type": "boolean", - "description": "是否为编辑模式", - "default": false, - "title": "Edit" - }, - "description": "是否为编辑模式" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetMCPServiceDetailRsp" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "delete": { - "tags": ["mcp-service"], - "summary": "Delete Service", - "description": "删除服务", - "operationId": "delete_service_api_mcp__serviceId__delete", - "parameters": [ - { - "name": "serviceId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "服务ID", - "title": "Serviceid" - }, - "description": "服务ID" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/DeleteMCPServiceRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "post": { - "tags": ["mcp-service"], - "summary": "Active Or Deactivate Mcp Service", - "description": "激活/取消激活mcp", - "operationId": "active_or_deactivate_mcp_service_api_mcp__serviceId__post", - "parameters": [ - { - "name": "serviceId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "服务ID", - "title": "Serviceid" - }, - "description": "服务ID" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ActiveMCPServiceRequest" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ActiveMCPServiceRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/mcp/icon": { - "post": { - "tags": ["mcp-service"], - "summary": "Update Mcp Icon", - "description": "更新MCP服务图标", - "operationId": "update_mcp_icon_api_mcp_icon_post", - "parameters": [ - { - "name": "serviceId", - "in": "path", - "required": true, - "schema": { - "type": "string", - "description": "服务ID", - "title": "Serviceid" - }, - "description": "服务ID" - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_update_mcp_icon_api_mcp_icon_post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/UpdateMCPServiceRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/flow/service": { - "get": { - "tags": ["flow"], - "summary": "Get Services", - "description": "获取用户可访问的节点元数据所在服务的信息", - "operationId": "get_services_api_flow_service_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/NodeServiceListRsp" } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - } - } - } - } - }, - "/api/flow": { - "get": { - "tags": ["flow"], - "summary": "Get Flow", - "description": "获取流拓扑结构", - "operationId": "get_flow_api_flow_get", - "parameters": [ - { - "name": "appId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Appid" } - }, - { - "name": "flowId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Flowid" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/FlowStructureGetRsp" } - } - } - }, - "403": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Forbidden" - }, - "404": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "put": { - "tags": ["flow"], - "summary": "Put Flow", - "description": "修改流拓扑结构", - "operationId": "put_flow_api_flow_put", - "parameters": [ - { - "name": "appId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Appid" } - }, - { - "name": "flowId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Flowid" } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/PutFlowReq" } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/FlowStructurePutRsp" } - } - } - }, - "400": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Bad Request" - }, - "403": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Forbidden" - }, - "404": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Not Found" - }, - "500": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Internal Server Error" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - }, - "delete": { - "tags": ["flow"], - "summary": "Delete Flow", - "description": "删除流拓扑结构", - "operationId": "delete_flow_api_flow_delete", - "parameters": [ - { - "name": "appId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Appid" } - }, - { - "name": "flowId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Flowid" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FlowStructureDeleteRsp" - } - } - } - }, - "404": { - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ResponseData" } - } - }, - "description": "Not Found" - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/user": { - "get": { - "tags": ["user"], - "summary": "Get User Sub", - "description": "查询所有用户接口", - "operationId": "get_user_sub_api_user_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - } - } - }, - "post": { - "tags": ["user"], - "summary": "Update User Info", - "description": "更新用户信息接口", - "operationId": "update_user_info_api_user_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserUpdateRequest", - "description": "用户更新信息" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { "application/json": { "schema": {} } } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/parameter": { - "get": { - "tags": ["parameter"], - "summary": "Get Parameters", - "description": "Get parameters for node choice.", - "operationId": "get_parameters_api_parameter_get", - "parameters": [ - { - "name": "appId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Appid" } - }, - { - "name": "flowId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Flowid" } - }, - { - "name": "stepId", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Stepid" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/GetParamsRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - }, - "/api/parameter/operate": { - "get": { - "tags": ["parameter"], - "summary": "Get Operate Parameters", - "description": "Get parameters for node choice.", - "operationId": "get_operate_parameters_api_parameter_operate_get", - "parameters": [ - { - "name": "ParamType", - "in": "query", - "required": true, - "schema": { "type": "string", "title": "Paramtype" } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/GetOperaRsp" } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "AbuseProcessRequest": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "is_deletion": { "type": "integer", "title": "Is Deletion" } - }, - "type": "object", - "required": ["id", "is_deletion"], - "title": "AbuseProcessRequest", - "description": "POST /api/blacklist/abuse 请求数据结构" - }, - "AbuseRequest": { - "properties": { - "record_id": { "type": "string", "title": "Record Id" }, - "reason": { "type": "string", "title": "Reason" }, - "reason_type": { "type": "string", "title": "Reason Type" } - }, - "type": "object", - "required": ["record_id", "reason", "reason_type"], - "title": "AbuseRequest", - "description": "POST /api/blacklist/complaint 请求数据结构" - }, - "ActiveMCPServiceRequest": { - "properties": { - "active": { - "type": "boolean", - "title": "Active", - "description": "是否激活mcp服务" - }, - "mcpEnv": { - "additionalProperties": true, - "type": "object", - "title": "Mcpenv", - "description": "MCP服务环境变量", - "default": {} - } - }, - "type": "object", - "required": ["active"], - "title": "ActiveMCPServiceRequest", - "description": "POST /api/mcp/{serviceId} 请求数据结构" - }, - "ActiveMCPServiceRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "$ref": "#/components/schemas/BaseMCPServiceOperationMsg", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "ActiveMCPServiceRsp", - "description": "POST /api/mcp/active/{serviceId} 返回数据结构" - }, - "AddCommentData": { - "properties": { - "record_id": { "type": "string", "title": "Record Id" }, - "group_id": { "type": "string", "title": "Group Id" }, - "comment": { "$ref": "#/components/schemas/CommentType" }, - "dislike_reason": { - "type": "string", - "maxLength": 200, - "title": "Dislike Reason", - "default": "" - }, - "reason_link": { - "type": "string", - "maxLength": 200, - "title": "Reason Link", - "default": "" - }, - "reason_description": { - "type": "string", - "maxLength": 500, - "title": "Reason Description", - "default": "" - } - }, - "type": "object", - "required": ["record_id", "group_id", "comment"], - "title": "AddCommentData", - "description": "添加评论" - }, - "AddConversationMsg": { - "properties": { - "conversationId": { "type": "string", "title": "Conversationid" } - }, - "type": "object", - "required": ["conversationId"], - "title": "AddConversationMsg", - "description": "POST /api/conversation Result数据结构" - }, - "AddConversationRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/AddConversationMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "AddConversationRsp", - "description": "POST /api/conversation 返回数据结构" - }, - "AppCenterCardItem": { - "properties": { - "appId": { - "type": "string", - "title": "Appid", - "description": "应用ID" - }, - "appType": { - "$ref": "#/components/schemas/AppType", - "description": "应用类型" - }, - "icon": { - "type": "string", - "title": "Icon", - "description": "应用图标" - }, - "name": { - "type": "string", - "title": "Name", - "description": "应用名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "应用简介" - }, - "author": { - "type": "string", - "title": "Author", - "description": "应用作者" - }, - "favorited": { - "type": "boolean", - "title": "Favorited", - "description": "是否已收藏" - }, - "published": { - "type": "boolean", - "title": "Published", - "description": "是否已发布", - "default": true - } - }, - "type": "object", - "required": [ - "appId", - "appType", - "icon", - "name", - "description", - "author", - "favorited" - ], - "title": "AppCenterCardItem", - "description": "应用中心卡片数据结构" - }, - "AppFlowInfo": { - "properties": { - "id": { "type": "string", "title": "Id", "description": "工作流ID" }, - "name": { - "type": "string", - "title": "Name", - "description": "工作流名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "工作流简介" - }, - "debug": { - "type": "boolean", - "title": "Debug", - "description": "是否经过调试" - } - }, - "type": "object", - "required": ["id", "name", "description", "debug"], - "title": "AppFlowInfo", - "description": "应用工作流数据结构" - }, - "AppLink": { - "properties": { - "title": { - "type": "string", - "title": "Title", - "description": "链接标题" - }, - "url": { - "type": "string", - "pattern": "^(https|http)://.*$", - "title": "Url", - "description": "链接地址" - } - }, - "type": "object", - "required": ["title", "url"], - "title": "AppLink", - "description": "App的相关链接" - }, - "AppMcpServiceInfo": { - "properties": { - "id": { "type": "string", "title": "Id", "description": "MCP服务ID" }, - "name": { - "type": "string", - "title": "Name", - "description": "MCP服务名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "MCP服务简介" - } - }, - "type": "object", - "required": ["id", "name", "description"], - "title": "AppMcpServiceInfo", - "description": "应用关联的MCP服务信息" - }, - "AppPermissionData": { - "properties": { - "visibility": { - "$ref": "#/components/schemas/PermissionType", - "description": "可见性(public/private/protected)", - "default": "private" - }, - "authorizedUsers": { - "anyOf": [ - { "items": { "type": "string" }, "type": "array" }, - { "type": "null" } - ], - "title": "Authorizedusers", - "description": "附加人员名单(如果可见性为部分人可见)" - } - }, - "type": "object", - "title": "AppPermissionData", - "description": "应用权限数据结构" - }, - "AppType": { - "type": "string", - "enum": ["flow", "agent"], - "title": "AppType", - "description": "应用中心应用类型" - }, - "AuthUserMsg": { - "properties": { - "user_sub": { "type": "string", "title": "User Sub" }, - "revision": { "type": "boolean", "title": "Revision" }, - "is_admin": { "type": "boolean", "title": "Is Admin" }, - "auto_execute": { "type": "boolean", "title": "Auto Execute" } - }, - "type": "object", - "required": ["user_sub", "revision", "is_admin", "auto_execute"], - "title": "AuthUserMsg", - "description": "GET /api/auth/user Result数据结构" - }, - "AuthUserRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/AuthUserMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "AuthUserRsp", - "description": "GET /api/auth/user 返回数据结构" - }, - "BaseAppOperationMsg": { - "properties": { - "appId": { - "type": "string", - "title": "Appid", - "description": "应用ID" - } - }, - "type": "object", - "required": ["appId"], - "title": "BaseAppOperationMsg", - "description": "基础应用操作Result数据结构" - }, - "BaseAppOperationRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/BaseAppOperationMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "BaseAppOperationRsp", - "description": "基础应用操作返回数据结构" - }, - "BaseMCPServiceOperationMsg": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "服务ID" - } - }, - "type": "object", - "required": ["serviceId"], - "title": "BaseMCPServiceOperationMsg", - "description": "插件中心:MCP服务操作Result数据结构" - }, - "BaseServiceOperationMsg": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "服务ID" - } - }, - "type": "object", - "required": ["serviceId"], - "title": "BaseServiceOperationMsg", - "description": "语义接口中心:基础服务操作Result数据结构" - }, - "Blacklist": { - "properties": { - "_id": { "type": "string", "title": "Id" }, - "question": { "type": "string", "title": "Question" }, - "answer": { "type": "string", "title": "Answer" }, - "is_audited": { - "type": "boolean", - "title": "Is Audited", - "default": false - }, - "reason_type": { - "type": "string", - "title": "Reason Type", - "default": "" - }, - "reason": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Reason" - }, - "updated_at": { "type": "number", "title": "Updated At" } - }, - "type": "object", - "required": ["question", "answer"], - "title": "Blacklist", - "description": "黑名单\n\nCollection: blacklist" - }, - "Body_add_conversation_api_conversation_post": { - "properties": { - "llm_id": { "type": "string", "title": "Llm Id", "default": "empty" }, - "kb_ids": { - "anyOf": [ - { "items": { "type": "string" }, "type": "array" }, - { "type": "null" } - ], - "title": "Kb Ids" - } - }, - "type": "object", - "title": "Body_add_conversation_api_conversation_post" - }, - "Body_document_upload_api_document__conversation_id__post": { - "properties": { - "documents": { - "items": { "type": "string", "format": "binary" }, - "type": "array", - "title": "Documents" - } - }, - "type": "object", - "required": ["documents"], - "title": "Body_document_upload_api_document__conversation_id__post" - }, - "Body_update_mcp_icon_api_mcp_icon_post": { - "properties": { - "icon": { - "type": "string", - "format": "binary", - "title": "Icon", - "description": "图标文件" - } - }, - "type": "object", - "required": ["icon"], - "title": "Body_update_mcp_icon_api_mcp_icon_post" - }, - "BoolOperate": { - "type": "string", - "enum": ["bool_equal", "bool_not_equal"], - "title": "BoolOperate", - "description": "Choice 工具支持的布尔运算符" - }, - "CommentType": { - "type": "string", - "enum": ["liked", "disliked", "none"], - "title": "CommentType", - "description": "点赞点踩类型" - }, - "ConversationDocumentItem": { - "properties": { - "_id": { "type": "string", "title": "Id", "default": "" }, - "user_sub": { "type": "null", "title": "User Sub" }, - "name": { "type": "string", "title": "Name" }, - "type": { "type": "string", "title": "Type" }, - "size": { "type": "number", "title": "Size" }, - "created_at": { "type": "number", "title": "Created At" }, - "conversation_id": { "type": "null", "title": "Conversation Id" }, - "status": { "$ref": "#/components/schemas/DocumentStatus" } - }, - "type": "object", - "required": ["name", "type", "size", "status"], - "title": "ConversationDocumentItem", - "description": "GET /api/document/{conversation_id} Result内元素数据结构" - }, - "ConversationDocumentMsg": { - "properties": { - "documents": { - "items": { - "$ref": "#/components/schemas/ConversationDocumentItem" - }, - "type": "array", - "title": "Documents", - "default": [] - } - }, - "type": "object", - "title": "ConversationDocumentMsg", - "description": "GET /api/document/{conversation_id} Result数据结构" - }, - "ConversationDocumentRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/ConversationDocumentMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "ConversationDocumentRsp", - "description": "GET /api/document/{conversation_id} 返回数据结构" - }, - "ConversationListItem": { - "properties": { - "conversationId": { "type": "string", "title": "Conversationid" }, - "title": { "type": "string", "title": "Title" }, - "docCount": { "type": "integer", "title": "Doccount" }, - "createdTime": { "type": "string", "title": "Createdtime" }, - "appId": { "type": "string", "title": "Appid" }, - "debug": { "type": "boolean", "title": "Debug" }, - "llm": { - "anyOf": [ - { "$ref": "#/components/schemas/LLMIteam" }, - { "type": "null" } - ] - }, - "kbList": { - "items": { "$ref": "#/components/schemas/KbIteam" }, - "type": "array", - "title": "Kblist", - "default": [] - } - }, - "type": "object", - "required": [ - "conversationId", - "title", - "docCount", - "createdTime", - "appId", - "debug" - ], - "title": "ConversationListItem", - "description": "GET /api/conversation Result数据结构" - }, - "ConversationListMsg": { - "properties": { - "conversations": { - "items": { "$ref": "#/components/schemas/ConversationListItem" }, - "type": "array", - "title": "Conversations" - } - }, - "type": "object", - "required": ["conversations"], - "title": "ConversationListMsg", - "description": "GET /api/conversation Result数据结构" - }, - "ConversationListRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/ConversationListMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "ConversationListRsp", - "description": "GET /api/conversation 返回数据结构" - }, - "CreateAppRequest": { - "properties": { - "appType": { - "$ref": "#/components/schemas/AppType", - "description": "应用类型" - }, - "icon": { - "type": "string", - "title": "Icon", - "description": "图标", - "default": "" - }, - "name": { - "type": "string", - "maxLength": 20, - "title": "Name", - "description": "应用名称" - }, - "description": { - "type": "string", - "maxLength": 150, - "title": "Description", - "description": "应用简介" - }, - "links": { - "items": { "$ref": "#/components/schemas/AppLink" }, - "type": "array", - "maxItems": 5, - "title": "Links", - "description": "相关链接", - "default": [] - }, - "recommendedQuestions": { - "items": { "type": "string" }, - "type": "array", - "maxItems": 3, - "title": "Recommendedquestions", - "description": "推荐问题", - "default": [] - }, - "dialogRounds": { - "type": "integer", - "maximum": 10.0, - "minimum": 1.0, - "title": "Dialogrounds", - "description": "对话轮次(1~10)", - "default": 3 - }, - "permission": { - "$ref": "#/components/schemas/AppPermissionData", - "description": "权限配置" - }, - "workflows": { - "items": { "$ref": "#/components/schemas/AppFlowInfo" }, - "type": "array", - "title": "Workflows", - "description": "工作流信息列表", - "default": [] - }, - "mcpService": { - "items": { "$ref": "#/components/schemas/AppMcpServiceInfo" }, - "type": "array", - "title": "Mcpservice", - "description": "MCP服务id列表", - "default": [] - }, - "appId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Appid", - "description": "应用ID" - } - }, - "type": "object", - "required": ["appType", "name", "description"], - "title": "CreateAppRequest", - "description": "POST /api/app 请求数据结构" - }, - "DeleteConversationData": { - "properties": { - "conversationList": { - "items": { "type": "string" }, - "type": "array", - "title": "Conversationlist" - } - }, - "type": "object", - "required": ["conversationList"], - "title": "DeleteConversationData", - "description": "删除会话" - }, - "DeleteMCPServiceRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "$ref": "#/components/schemas/BaseMCPServiceOperationMsg", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "DeleteMCPServiceRsp", - "description": "DELETE /api/service/{serviceId} 返回数据结构" - }, - "DeleteServiceRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "$ref": "#/components/schemas/BaseServiceOperationMsg", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "DeleteServiceRsp", - "description": "DELETE /api/service/{serviceId} 返回数据结构" - }, - "DependencyItem": { - "properties": { - "nodeId": { "type": "string", "title": "Nodeid" }, - "type": { "type": "string", "title": "Type" } - }, - "type": "object", - "required": ["nodeId", "type"], - "title": "DependencyItem", - "description": "请求/响应中的节点依赖变量类" - }, - "DictOperate": { - "type": "string", - "enum": [ - "dict_equal", - "dict_not_equal", - "dict_contains_key", - "dict_not_contains_key" - ], - "title": "DictOperate", - "description": "Choice 工具支持的字典运算符" - }, - "DocumentStatus": { - "type": "string", - "enum": ["used", "unused", "processing", "failed"], - "title": "DocumentStatus", - "description": "文档状态" - }, - "EdgeItem": { - "properties": { - "edgeId": { "type": "string", "title": "Edgeid" }, - "sourceNode": { "type": "string", "title": "Sourcenode" }, - "targetNode": { "type": "string", "title": "Targetnode" }, - "type": { "type": "string", "title": "Type", "default": "normal" }, - "branchId": { "type": "string", "title": "Branchid" } - }, - "type": "object", - "required": ["edgeId", "sourceNode", "targetNode", "branchId"], - "title": "EdgeItem", - "description": "请求/响应中的边变量类" - }, - "EditMCPServiceMsg": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "MCP服务ID" - }, - "icon": { - "type": "string", - "title": "Icon", - "description": "图标", - "default": "" - }, - "name": { - "type": "string", - "title": "Name", - "description": "MCP服务名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "MCP服务描述" - }, - "overview": { - "type": "string", - "title": "Overview", - "description": "MCP服务概述" - }, - "data": { - "type": "string", - "title": "Data", - "description": "MCP服务配置" - }, - "mcpType": { "$ref": "#/components/schemas/MCPType" } - }, - "type": "object", - "required": [ - "serviceId", - "name", - "description", - "overview", - "data", - "mcpType" - ], - "title": "EditMCPServiceMsg", - "description": "编辑MCP服务" - }, - "FlowItem-Input": { - "properties": { - "flowId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Flowid", - "default": "工作流ID" - }, - "name": { - "type": "string", - "title": "Name", - "default": "工作流名称" - }, - "description": { - "type": "string", - "title": "Description", - "default": "工作流描述" - }, - "enable": { "type": "boolean", "title": "Enable", "default": true }, - "editable": { - "type": "boolean", - "title": "Editable", - "default": true - }, - "nodes": { - "items": { "$ref": "#/components/schemas/NodeItem" }, - "type": "array", - "title": "Nodes", - "default": [] - }, - "edges": { - "items": { "$ref": "#/components/schemas/EdgeItem" }, - "type": "array", - "title": "Edges", - "default": [] - }, - "createdAt": { - "anyOf": [{ "type": "number" }, { "type": "null" }], - "title": "Createdat", - "default": 0 - }, - "connectivity": { - "type": "boolean", - "title": "Connectivity", - "description": "图的开始节点和结束节点是否联通,并且除结束节点都有出边", - "default": false - }, - "focusPoint": { - "$ref": "#/components/schemas/PositionItem", - "default": { "x": 0.0, "y": 0.0 } - }, - "debug": { "type": "boolean", "title": "Debug", "default": false } - }, - "type": "object", - "title": "FlowItem", - "description": "请求/响应中的流变量类" - }, - "FlowItem-Output": { - "properties": { - "flowId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Flowid", - "default": "工作流ID" - }, - "name": { - "type": "string", - "title": "Name", - "default": "工作流名称" - }, - "description": { - "type": "string", - "title": "Description", - "default": "工作流描述" - }, - "enable": { "type": "boolean", "title": "Enable", "default": true }, - "editable": { - "type": "boolean", - "title": "Editable", - "default": true - }, - "nodes": { - "items": { "$ref": "#/components/schemas/NodeItem" }, - "type": "array", - "title": "Nodes", - "default": [] - }, - "edges": { - "items": { "$ref": "#/components/schemas/EdgeItem" }, - "type": "array", - "title": "Edges", - "default": [] - }, - "createdAt": { - "anyOf": [{ "type": "number" }, { "type": "null" }], - "title": "Createdat", - "default": 0 - }, - "connectivity": { - "type": "boolean", - "title": "Connectivity", - "description": "图的开始节点和结束节点是否联通,并且除结束节点都有出边", - "default": false - }, - "focusPoint": { - "$ref": "#/components/schemas/PositionItem", - "default": { "x": 0.0, "y": 0.0 } - }, - "debug": { "type": "boolean", "title": "Debug", "default": false } - }, - "type": "object", - "title": "FlowItem", - "description": "请求/响应中的流变量类" - }, - "FlowStructureDeleteMsg": { - "properties": { - "flowId": { "type": "string", "title": "Flowid", "default": "" } - }, - "type": "object", - "title": "FlowStructureDeleteMsg", - "description": "DELETE /api/flow/ result" - }, - "FlowStructureDeleteRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/FlowStructureDeleteMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "FlowStructureDeleteRsp", - "description": "DELETE /api/flow/ 返回数据结构" - }, - "FlowStructureGetMsg": { - "properties": { - "flow": { - "$ref": "#/components/schemas/FlowItem-Output", - "default": { - "flowId": "工作流ID", - "name": "工作流名称", - "description": "工作流描述", - "enable": true, - "editable": true, - "nodes": [], - "edges": [], - "createdAt": 0.0, - "connectivity": false, - "focusPoint": { "x": 0.0, "y": 0.0 }, - "debug": false - } - } - }, - "type": "object", - "title": "FlowStructureGetMsg", - "description": "GET /api/flow result" - }, - "FlowStructureGetRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/FlowStructureGetMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "FlowStructureGetRsp", - "description": "GET /api/flow 返回数据结构" - }, - "FlowStructurePutMsg": { - "properties": { - "flow": { - "$ref": "#/components/schemas/FlowItem-Output", - "default": { - "flowId": "工作流ID", - "name": "工作流名称", - "description": "工作流描述", - "enable": true, - "editable": true, - "nodes": [], - "edges": [], - "createdAt": 0.0, - "connectivity": false, - "focusPoint": { "x": 0.0, "y": 0.0 }, - "debug": false - } - } - }, - "type": "object", - "title": "FlowStructurePutMsg", - "description": "PUT /api/flow result" - }, - "FlowStructurePutRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/FlowStructurePutMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "FlowStructurePutRsp", - "description": "PUT /api/flow 返回数据结构" - }, - "FootNoteMetaData": { - "properties": { - "releatedId": { - "type": "string", - "title": "Releatedid", - "description": "相关ID", - "default": "" - }, - "insertPosition": { - "type": "integer", - "title": "Insertposition", - "description": "插入位置", - "default": 0 - }, - "footSource": { - "type": "string", - "title": "Footsource", - "description": "脚注来源", - "default": "" - }, - "footType": { - "type": "string", - "title": "Foottype", - "description": "脚注类型", - "default": "" - } - }, - "type": "object", - "title": "FootNoteMetaData", - "description": "Record表子项:Record的脚注元信息" - }, - "GetAppListMsg": { - "properties": { - "currentPage": { - "type": "integer", - "title": "Currentpage", - "description": "当前页码" - }, - "totalApps": { - "type": "integer", - "title": "Totalapps", - "description": "总应用数" - }, - "applications": { - "items": { "$ref": "#/components/schemas/AppCenterCardItem" }, - "type": "array", - "title": "Applications", - "description": "应用列表" - } - }, - "type": "object", - "required": ["currentPage", "totalApps", "applications"], - "title": "GetAppListMsg", - "description": "GET /api/app Result数据结构" - }, - "GetAppListRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/GetAppListMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetAppListRsp", - "description": "GET /api/app 返回数据结构" - }, - "GetAppPropertyMsg": { - "properties": { - "appType": { - "$ref": "#/components/schemas/AppType", - "description": "应用类型" - }, - "icon": { - "type": "string", - "title": "Icon", - "description": "图标", - "default": "" - }, - "name": { - "type": "string", - "maxLength": 20, - "title": "Name", - "description": "应用名称" - }, - "description": { - "type": "string", - "maxLength": 150, - "title": "Description", - "description": "应用简介" - }, - "links": { - "items": { "$ref": "#/components/schemas/AppLink" }, - "type": "array", - "maxItems": 5, - "title": "Links", - "description": "相关链接", - "default": [] - }, - "recommendedQuestions": { - "items": { "type": "string" }, - "type": "array", - "maxItems": 3, - "title": "Recommendedquestions", - "description": "推荐问题", - "default": [] - }, - "dialogRounds": { - "type": "integer", - "maximum": 10.0, - "minimum": 1.0, - "title": "Dialogrounds", - "description": "对话轮次(1~10)", - "default": 3 - }, - "permission": { - "$ref": "#/components/schemas/AppPermissionData", - "description": "权限配置" - }, - "workflows": { - "items": { "$ref": "#/components/schemas/AppFlowInfo" }, - "type": "array", - "title": "Workflows", - "description": "工作流信息列表", - "default": [] - }, - "mcpService": { - "items": { "$ref": "#/components/schemas/AppMcpServiceInfo" }, - "type": "array", - "title": "Mcpservice", - "description": "MCP服务id列表", - "default": [] - }, - "appId": { - "type": "string", - "title": "Appid", - "description": "应用ID" - }, - "published": { - "type": "boolean", - "title": "Published", - "description": "是否已发布" - } - }, - "type": "object", - "required": ["appType", "name", "description", "appId", "published"], - "title": "GetAppPropertyMsg", - "description": "GET /api/app/{appId} Result数据结构" - }, - "GetAppPropertyRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/GetAppPropertyMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetAppPropertyRsp", - "description": "GET /api/app/{appId} 返回数据结构" - }, - "GetAuthKeyRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/_GetAuthKeyMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetAuthKeyRsp", - "description": "GET /api/auth/key 返回数据结构" - }, - "GetBlacklistQuestionMsg": { - "properties": { - "question_list": { - "items": { "$ref": "#/components/schemas/Blacklist" }, - "type": "array", - "title": "Question List" - } - }, - "type": "object", - "required": ["question_list"], - "title": "GetBlacklistQuestionMsg", - "description": "GET /api/blacklist/question Result数据结构" - }, - "GetBlacklistQuestionRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/GetBlacklistQuestionMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetBlacklistQuestionRsp", - "description": "GET /api/blacklist/question 返回数据结构" - }, - "GetBlacklistUserMsg": { - "properties": { - "user_subs": { - "items": { "type": "string" }, - "type": "array", - "title": "User Subs" - } - }, - "type": "object", - "required": ["user_subs"], - "title": "GetBlacklistUserMsg", - "description": "GET /api/blacklist/user Result数据结构" - }, - "GetBlacklistUserRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/GetBlacklistUserMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetBlacklistUserRsp", - "description": "GET /api/blacklist/user 返回数据结构" - }, - "GetMCPServiceDetailMsg": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "MCP服务ID" - }, - "icon": { - "type": "string", - "title": "Icon", - "description": "图标", - "default": "" - }, - "name": { - "type": "string", - "title": "Name", - "description": "MCP服务名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "MCP服务描述" - }, - "overview": { - "type": "string", - "title": "Overview", - "description": "MCP服务概述" - }, - "tools": { - "items": { "$ref": "#/components/schemas/MCPTool" }, - "type": "array", - "title": "Tools", - "description": "MCP服务Tools列表", - "default": [] - } - }, - "type": "object", - "required": ["serviceId", "name", "description", "overview"], - "title": "GetMCPServiceDetailMsg", - "description": "GET /api/mcp_service/{serviceId} Result数据结构" - }, - "GetMCPServiceDetailRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "anyOf": [ - { "$ref": "#/components/schemas/GetMCPServiceDetailMsg" }, - { "$ref": "#/components/schemas/EditMCPServiceMsg" } - ], - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetMCPServiceDetailRsp", - "description": "GET /api/service/{serviceId} 返回数据结构" - }, - "GetMCPServiceListMsg": { - "properties": { - "currentPage": { - "type": "integer", - "title": "Currentpage", - "description": "当前页码" - }, - "services": { - "items": { "$ref": "#/components/schemas/MCPServiceCardItem" }, - "type": "array", - "title": "Services", - "description": "解析后的服务列表" - } - }, - "type": "object", - "required": ["currentPage", "services"], - "title": "GetMCPServiceListMsg", - "description": "GET /api/service Result数据结构" - }, - "GetMCPServiceListRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "$ref": "#/components/schemas/GetMCPServiceListMsg", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetMCPServiceListRsp", - "description": "GET /api/service 返回数据结构" - }, - "GetOperaRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "items": { "$ref": "#/components/schemas/OperateAndBindType" }, - "type": "array", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetOperaRsp", - "description": "GET /api/operate 返回数据结构" - }, - "GetParamsRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "items": { "$ref": "#/components/schemas/StepParams" }, - "type": "array", - "title": "Result", - "description": "参数列表", - "default": [] - } - }, - "type": "object", - "required": ["code", "message"], - "title": "GetParamsRsp", - "description": "GET /api/params 返回数据结构" - }, - "GetRecentAppListRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/RecentAppList" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetRecentAppListRsp", - "description": "GET /api/app/recent 返回数据结构" - }, - "GetServiceDetailMsg": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "服务ID" - }, - "name": { - "type": "string", - "title": "Name", - "description": "服务名称" - }, - "apis": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/ServiceApiData" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Apis", - "description": "解析后的接口列表" - }, - "data": { - "anyOf": [ - { "additionalProperties": true, "type": "object" }, - { "type": "null" } - ], - "title": "Data", - "description": "YAML 内容数据对象" - } - }, - "type": "object", - "required": ["serviceId", "name"], - "title": "GetServiceDetailMsg", - "description": "GET /api/service/{serviceId} Result数据结构" - }, - "GetServiceDetailRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "$ref": "#/components/schemas/GetServiceDetailMsg", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetServiceDetailRsp", - "description": "GET /api/service/{serviceId} 返回数据结构" - }, - "GetServiceListMsg": { - "properties": { - "currentPage": { - "type": "integer", - "title": "Currentpage", - "description": "当前页码" - }, - "totalCount": { - "type": "integer", - "title": "Totalcount", - "description": "总服务数" - }, - "services": { - "items": { "$ref": "#/components/schemas/ServiceCardItem" }, - "type": "array", - "title": "Services", - "description": "解析后的服务列表" - } - }, - "type": "object", - "required": ["currentPage", "totalCount", "services"], - "title": "GetServiceListMsg", - "description": "GET /api/service Result数据结构" - }, - "GetServiceListRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "$ref": "#/components/schemas/GetServiceListMsg", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "GetServiceListRsp", - "description": "GET /api/service 返回数据结构" - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { "$ref": "#/components/schemas/ValidationError" }, - "type": "array", - "title": "Detail" - } - }, - "type": "object", - "title": "HTTPValidationError" - }, - "HealthCheckRsp": { - "properties": { "status": { "type": "string", "title": "Status" } }, - "type": "object", - "required": ["status"], - "title": "HealthCheckRsp", - "description": "GET /health_check 返回数据结构" - }, - "KbIteam": { - "properties": { - "kbId": { "type": "string", "title": "Kbid" }, - "kbName": { "type": "string", "title": "Kbname" } - }, - "type": "object", - "required": ["kbId", "kbName"], - "title": "KbIteam", - "description": "GET /api/conversation Result数据结构" - }, - "KnowledgeBaseItem": { - "properties": { - "kbId": { - "type": "string", - "title": "Kbid", - "description": "知识库ID" - }, - "kbName": { - "type": "string", - "title": "Kbname", - "description": "知识库名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "知识库描述" - }, - "isUsed": { - "type": "boolean", - "title": "Isused", - "description": "是否使用" - } - }, - "type": "object", - "required": ["kbId", "kbName", "description", "isUsed"], - "title": "KnowledgeBaseItem", - "description": "知识库列表项数据结构" - }, - "LLMIteam": { - "properties": { - "icon": { - "type": "string", - "title": "Icon", - "default": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJjdXJyZW50Q29sb3IiIGZpbGwtcnVsZT0iZXZlbm9kZCIgaGVpZ2h0PSIxZW0iIHN0eWxlPSJmbGV4Om5vbmU7bGluZS1oZWlnaHQ6MSIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMWVtIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZT5PbGxhbWE8L3RpdGxlPjxwYXRoIGQ9Ik03LjkwNSAxLjA5Yy4yMTYuMDg1LjQxMS4yMjUuNTg4LjQxLjI5NS4zMDYuNTQ0Ljc0NC43MzQgMS4yNjMuMTkxLjUyMi4zMTUgMS4xLjM2MiAxLjY4YTUuMDU0IDUuMDU0IDAgMDEyLjA0OS0uNjM2bC4wNTEtLjAwNGMuODctLjA3IDEuNzMuMDg3IDIuNDguNDc0LjEwMS4wNTMuMi4xMS4yOTcuMTcuMDUtLjU2OS4xNzItMS4xMzQuMzYtMS42NDQuMTktLjUyLjQzOS0uOTU3LjczMy0xLjI2NGExLjY3IDEuNjcgMCAwMS41ODktLjQxYy4yNTctLjEuNTMtLjExOC43OTYtLjA0Mi40MDEuMTE0Ljc0NS4zNjggMS4wMTYuNzM3LjI0OC4zMzcuNDM0Ljc2OS41NjEgMS4yODcuMjMuOTM0LjI3IDIuMTYzLjExNSAzLjY0NWwuMDUzLjA0LjAyNi4wMTljLjc1Ny41NzYgMS4yODQgMS4zOTcgMS41NjMgMi4zNS40MzUgMS40ODcuMjE2IDMuMTU1LS41MzQgNC4wODhsLS4wMTguMDIxLjAwMi4wMDNjLjQxNy43NjIuNjcgMS41NjcuNzI0IDIuNGwuMDAyLjAzYy4wNjQgMS4wNjUtLjIgMi4xMzctLjgxNCAzLjE5bC0uMDA3LjAxLjAxLjAyNGMuNDcyIDEuMTU3LjYyIDIuMzIyLjQzOCAzLjQ4NmwtLjAwNi4wMzlhLjY1MS42NTEgMCAwMS0uNzQ3LjUzNi42NDguNjQ4IDAgMDEtLjU0LS43NDJjLjE2Ny0xLjAzMy4wMS0yLjA2OS0uNDgtMy4xMjNhLjY0My42NDMgMCAwMS4wNC0uNjE3bC4wMDQtLjAwNmMuNjA0LS45MjQuODU0LTEuODMuOC0yLjcyLS4wNDYtLjc3OS0uMzI1LTEuNTQ0LS44LTIuMjczYS42NDQuNjQ0IDAgMDEuMTgtLjg4NmwuMDA5LS4wMDZjLjI0My0uMTU5LjQ2Ny0uNTY1LjU4LTEuMTJhNC4yMjkgNC4yMjkgMCAwMC0uMDk1LTEuOTc0Yy0uMjA1LS43LS41OC0xLjI4NC0xLjEwNS0xLjY4My0uNTk1LS40NTQtMS4zODMtLjY3My0yLjM4LS42MWEuNjUzLjY1MyAwIDAxLS42MzItLjM3MWMtLjMxNC0uNjY1LS43NzItMS4xNDEtMS4zNDMtMS40MzZhMy4yODggMy4yODggMCAwMC0xLjc3Mi0uMzMyYy0xLjI0NS4wOTktMi4zNDMuODAxLTIuNjcgMS42ODZhLjY1Mi42NTIgMCAwMS0uNjEuNDI1Yy0xLjA2Ny4wMDItMS44OTMuMjUyLTIuNDk3LjcwMy0uNTIyLjM5LS44NzguOTM1LTEuMDY2IDEuNTg4YTQuMDcgNC4wNyAwIDAwLS4wNjggMS44ODZjLjExMi41NTguMzMxIDEuMDIuNTgyIDEuMjY5bC4wMDguMDA3Yy4yMTIuMjA3LjI1Ny41My4xMDkuNzg1LS4zNi42MjItLjYyOSAxLjU0OS0uNjczIDIuNDQtLjA1IDEuMDE4LjE4NiAxLjkwMi43MTkgMi41MzZsLjAxNi4wMTlhLjY0My42NDMgMCAwMS4wOTUuNjljLS41NzYgMS4yMzYtLjc1MyAyLjI1Mi0uNTYyIDMuMDUyYS42NTIuNjUyIDAgMDEtMS4yNjkuMjk4Yy0uMjQzLTEuMDE4LS4wNzgtMi4xODQuNDczLTMuNDk4bC4wMTQtLjAzNS0uMDA4LS4wMTJhNC4zMzkgNC4zMzkgMCAwMS0uNTk4LTEuMzA5bC0uMDA1LS4wMTlhNS43NjQgNS43NjQgMCAwMS0uMTc3LTEuNzg1Yy4wNDQtLjkxLjI3OC0xLjg0Mi42MjItMi41OWwuMDEyLS4wMjYtLjAwMi0uMDAyYy0uMjkzLS40MTgtLjUxLS45NTMtLjYzLTEuNTQ1bC0uMDA1LS4wMjRhNS4zNTIgNS4zNTIgMCAwMS4wOTMtMi40OWMuMjYyLS45MTUuNzc3LTEuNzAxIDEuNTM2LTIuMjY5LjA2LS4wNDUuMTIzLS4wOS4xODYtLjEzMi0uMTU5LTEuNDkzLS4xMTktMi43My4xMTItMy42Ny4xMjctLjUxOC4zMTQtLjk1LjU2Mi0xLjI4Ny4yNy0uMzY4LjYxNC0uNjIyIDEuMDE1LS43MzcuMjY2LS4wNzYuNTQtLjA1OS43OTcuMDQyem00LjExNiA5LjA5Yy45MzYgMCAxLjguMzEzIDIuNDQ2Ljg1NS42My41MjcgMS4wMDUgMS4yMzUgMS4wMDUgMS45NCAwIC44ODgtLjQwNiAxLjU4LTEuMTMzIDIuMDIyLS42Mi4zNzUtMS40NTEuNTU3LTIuNDAzLjU1Ny0xLjAwOSAwLTEuODcxLS4yNTktMi40OTMtLjczNC0uNjE3LS40Ny0uOTYzLTEuMTMtLjk2My0xLjg0NSAwLS43MDcuMzk4LTEuNDE3IDEuMDU2LTEuOTQ2LjY2OC0uNTM3IDEuNTUtLjg0OSAyLjQ4NS0uODQ5em0wIC44OTZhMy4wNyAzLjA3IDAgMDAtMS45MTYuNjVjLS40NjEuMzctLjcyMi44MzUtLjcyMiAxLjI1IDAgLjQyOC4yMS44MjkuNjEgMS4xMzQuNDU1LjM0NyAxLjEyNC41NDggMS45NDMuNTQ4Ljc5OSAwIDEuNDczLS4xNDcgMS45MzItLjQyNi40NjMtLjI4LjctLjY4Ni43LTEuMjU3IDAtLjQyMy0uMjQ2LS44OS0uNjgzLTEuMjU2LS40ODQtLjQwNS0xLjE0LS42NDMtMS44NjQtLjY0M3ptLjY2MiAxLjIxbC4wMDQuMDA0Yy4xMi4xNTEuMDk1LjM3LS4wNTYuNDlsLS4yOTIuMjN2LjQ0NmEuMzc1LjM3NSAwIDAxLS4zNzYuMzczLjM3NS4zNzUgMCAwMS0uMzc2LS4zNzN2LS40NmwtLjI3MS0uMjE4YS4zNDcuMzQ3IDAgMDEtLjA1Mi0uNDkuMzUzLjM1MyAwIDAxLjQ5NC0uMDUxbC4yMTUuMTcyLjIyLS4xNzRhLjM1My4zNTMgMCAwMS40OS4wNTF6bS01LjA0LTEuOTE5Yy40NzggMCAuODY3LjM5Ljg2Ny44NzFhLjg3Ljg3IDAgMDEtLjg2OC44NzEuODcuODcgMCAwMS0uODY3LS44Ny44Ny44NyAwIDAxLjg2Ny0uODcyem04LjcwNiAwYy40OCAwIC44NjguMzkuODY4Ljg3MWEuODcuODcgMCAwMS0uODY4Ljg3MS44Ny44NyAwIDAxLS44NjctLjg3Ljg3Ljg3IDAgMDEuODY3LS44NzJ6TTcuNDQgMi4zbC0uMDAzLjAwMmEuNjU5LjY1OSAwIDAwLS4yODUuMjM4bC0uMDA1LjAwNmMtLjEzOC4xODktLjI1OC40NjctLjM0OC44MzItLjE3LjY5Mi0uMjE2IDEuNjMxLS4xMjQgMi43ODIuNDMtLjEyOC44OTktLjIwOCAxLjQwNC0uMjM3bC4wMS0uMDAxLjAxOS0uMDM0Yy4wNDYtLjA4Mi4wOTUtLjE2MS4xNDgtLjIzOS4xMjMtLjc3MS4wMjItMS42OTItLjI1My0yLjQ0NC0uMTM0LS4zNjQtLjI5Ny0uNjUtLjQ1My0uODEzYS42MjguNjI4IDAgMDAtLjEwNy0uMDlMNy40NCAyLjN6bTkuMTc0LjA0bC0uMDAyLjAwMWEuNjI4LjYyOCAwIDAwLS4xMDcuMDljLS4xNTYuMTYzLS4zMi40NS0uNDUzLjgxNC0uMjkuNzk0LS4zODcgMS43NzYtLjIzIDIuNTcybC4wNTguMDk3LjAwOC4wMTRoLjAzYTUuMTg0IDUuMTg0IDAgMDExLjQ2Ni4yMTJjLjA4Ni0xLjEyNC4wMzgtMi4wNDMtLjEyOC0yLjcyMi0uMDktLjM2NS0uMjEtLjY0My0uMzQ5LS44MzJsLS4wMDQtLjAwNmEuNjU5LjY1OSAwIDAwLS4yODUtLjIzOWgtLjAwNHoiPjwvcGF0aD48L3N2Zz4=" - }, - "llmId": { "type": "string", "title": "Llmid", "default": "empty" }, - "modelName": { - "type": "string", - "title": "Modelname", - "default": "Ollama LLM" - } - }, - "type": "object", - "title": "LLMIteam", - "description": "GET /api/conversation Result数据结构" - }, - "LLMProvider": { - "properties": { - "provider": { - "type": "string", - "title": "Provider", - "description": "LLM提供商" - }, - "description": { - "type": "string", - "title": "Description", - "description": "LLM提供商描述" - }, - "url": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Url", - "description": "LLM提供商URL" - }, - "icon": { - "type": "string", - "title": "Icon", - "description": "LLM提供商图标" - } - }, - "type": "object", - "required": ["provider", "description", "icon"], - "title": "LLMProvider", - "description": "LLM提供商数据结构" - }, - "LLMProviderInfo": { - "properties": { - "llmId": { - "type": "string", - "title": "Llmid", - "description": "LLM ID" - }, - "icon": { - "type": "string", - "maxLength": 25536, - "title": "Icon", - "description": "LLM图标", - "default": "" - }, - "openaiBaseUrl": { - "type": "string", - "title": "Openaibaseurl", - "description": "OpenAI API Base URL", - "default": "https://api.openai.com/v1" - }, - "openaiApiKey": { - "type": "string", - "title": "Openaiapikey", - "description": "OpenAI API Key", - "default": "" - }, - "modelName": { - "type": "string", - "title": "Modelname", - "description": "模型名称" - }, - "maxTokens": { - "anyOf": [{ "type": "integer" }, { "type": "null" }], - "title": "Maxtokens", - "description": "最大token数" - }, - "isEditable": { - "type": "boolean", - "title": "Iseditable", - "description": "是否可编辑", - "default": true - } - }, - "type": "object", - "required": ["llmId", "modelName"], - "title": "LLMProviderInfo", - "description": "LLM数据结构" - }, - "ListLLMProviderRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "items": { "$ref": "#/components/schemas/LLMProvider" }, - "type": "array", - "title": "Result", - "default": [] - } - }, - "type": "object", - "required": ["code", "message"], - "title": "ListLLMProviderRsp", - "description": "GET /api/llm/provider 返回数据结构" - }, - "ListLLMRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "items": { "$ref": "#/components/schemas/LLMProviderInfo" }, - "type": "array", - "title": "Result", - "default": [] - } - }, - "type": "object", - "required": ["code", "message"], - "title": "ListLLMRsp", - "description": "GET /api/llm 返回数据结构" - }, - "ListOperate": { - "type": "string", - "enum": [ - "list_equal", - "list_not_equal", - "list_contains", - "list_not_contains", - "list_length_equal", - "list_length_greater_than", - "list_length_greater_than_or_equal", - "list_length_less_than", - "list_length_less_than_or_equal" - ], - "title": "ListOperate", - "description": "Choice 工具支持的列表运算符" - }, - "ListTeamKnowledgeMsg": { - "properties": { - "teamKbList": { - "items": { "$ref": "#/components/schemas/TeamKnowledgeBaseItem" }, - "type": "array", - "title": "Teamkblist", - "description": "团队知识库列表", - "default": [] - } - }, - "type": "object", - "title": "ListTeamKnowledgeMsg", - "description": "GET /api/knowledge Result数据结构" - }, - "ListTeamKnowledgeRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/ListTeamKnowledgeMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "ListTeamKnowledgeRsp", - "description": "GET /api/knowledge 返回数据结构" - }, - "MCPInstallStatus": { - "type": "string", - "enum": ["installing", "ready", "failed"], - "title": "MCPInstallStatus", - "description": "MCP 服务状态" - }, - "MCPServiceCardItem": { - "properties": { - "mcpserviceId": { - "type": "string", - "title": "Mcpserviceid", - "description": "mcp服务ID" - }, - "name": { - "type": "string", - "title": "Name", - "description": "mcp服务名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "mcp服务简介" - }, - "icon": { - "type": "string", - "title": "Icon", - "description": "mcp服务图标" - }, - "author": { - "type": "string", - "title": "Author", - "description": "mcp服务作者" - }, - "isActive": { - "type": "boolean", - "title": "Isactive", - "description": "mcp服务是否激活", - "default": false - }, - "status": { - "$ref": "#/components/schemas/MCPInstallStatus", - "description": "mcp服务状态", - "default": "installing" - } - }, - "type": "object", - "required": ["mcpserviceId", "name", "description", "icon", "author"], - "title": "MCPServiceCardItem", - "description": "插件中心:MCP服务卡片数据结构" - }, - "MCPTool": { - "properties": { - "id": { "type": "string", "title": "Id", "description": "MCP工具ID" }, - "name": { - "type": "string", - "title": "Name", - "description": "MCP工具名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "MCP工具描述" - }, - "mcp_id": { - "type": "string", - "title": "Mcp Id", - "description": "MCP ID" - }, - "input_schema": { - "additionalProperties": true, - "type": "object", - "title": "Input Schema", - "description": "MCP工具输入参数" - } - }, - "type": "object", - "required": ["id", "name", "description", "mcp_id", "input_schema"], - "title": "MCPTool", - "description": "MCP工具" - }, - "MCPType": { - "type": "string", - "enum": ["sse", "stdio", "stream"], - "title": "MCPType", - "description": "MCP 类型" - }, - "ModFavAppMsg": { - "properties": { - "appId": { - "type": "string", - "title": "Appid", - "description": "应用ID" - }, - "favorited": { - "type": "boolean", - "title": "Favorited", - "description": "是否已收藏" - } - }, - "type": "object", - "required": ["appId", "favorited"], - "title": "ModFavAppMsg", - "description": "PUT /api/app/{appId} Result数据结构" - }, - "ModFavAppRequest": { - "properties": { - "favorited": { - "type": "boolean", - "title": "Favorited", - "description": "是否收藏" - } - }, - "type": "object", - "required": ["favorited"], - "title": "ModFavAppRequest", - "description": "PUT /api/app/{appId} 请求数据结构" - }, - "ModFavAppRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/ModFavAppMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "ModFavAppRsp", - "description": "PUT /api/app/{appId} 返回数据结构" - }, - "ModFavServiceMsg": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "服务ID" - }, - "favorited": { - "type": "boolean", - "title": "Favorited", - "description": "是否已收藏" - } - }, - "type": "object", - "required": ["serviceId", "favorited"], - "title": "ModFavServiceMsg", - "description": "PUT /api/service/{serviceId} Result数据结构" - }, - "ModFavServiceRequest": { - "properties": { - "favorited": { - "type": "boolean", - "title": "Favorited", - "description": "是否收藏" - } - }, - "type": "object", - "required": ["favorited"], - "title": "ModFavServiceRequest", - "description": "PUT /api/service/{serviceId} 请求数据结构" - }, - "ModFavServiceRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "$ref": "#/components/schemas/ModFavServiceMsg", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "ModFavServiceRsp", - "description": "PUT /api/service/{serviceId} 返回数据结构" - }, - "ModifyConversationData": { - "properties": { - "title": { - "type": "string", - "maxLength": 2000, - "minLength": 1, - "title": "Title" - } - }, - "type": "object", - "required": ["title"], - "title": "ModifyConversationData", - "description": "修改会话信息" - }, - "NodeItem": { - "properties": { - "stepId": { "type": "string", "title": "Stepid", "default": "" }, - "serviceId": { - "type": "string", - "title": "Serviceid", - "default": "" - }, - "nodeId": { "type": "string", "title": "Nodeid", "default": "" }, - "name": { "type": "string", "title": "Name", "default": "" }, - "callId": { "type": "string", "title": "Callid", "default": "Empty" }, - "description": { - "type": "string", - "title": "Description", - "default": "" - }, - "enable": { "type": "boolean", "title": "Enable", "default": true }, - "parameters": { - "additionalProperties": true, - "type": "object", - "title": "Parameters", - "default": {} - }, - "depedency": { - "anyOf": [ - { "$ref": "#/components/schemas/DependencyItem" }, - { "type": "null" } - ] - }, - "position": { - "$ref": "#/components/schemas/PositionItem", - "default": { "x": 0.0, "y": 0.0 } - }, - "editable": { - "type": "boolean", - "title": "Editable", - "default": true - } - }, - "type": "object", - "title": "NodeItem", - "description": "请求/响应中的节点变量类" - }, - "NodeMetaDataItem": { - "properties": { - "nodeId": { "type": "string", "title": "Nodeid" }, - "callId": { "type": "string", "title": "Callid" }, - "name": { "type": "string", "title": "Name" }, - "description": { "type": "string", "title": "Description" }, - "parameters": { - "anyOf": [ - { "additionalProperties": true, "type": "object" }, - { "type": "null" } - ], - "title": "Parameters" - }, - "editable": { - "type": "boolean", - "title": "Editable", - "default": true - }, - "createdAt": { - "anyOf": [{ "type": "number" }, { "type": "null" }], - "title": "Createdat" - } - }, - "type": "object", - "required": [ - "nodeId", - "callId", - "name", - "description", - "parameters", - "createdAt" - ], - "title": "NodeMetaDataItem", - "description": "节点元数据类" - }, - "NodeServiceItem": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "服务ID" - }, - "name": { - "type": "string", - "title": "Name", - "description": "服务名称" - }, - "type": { - "type": "string", - "title": "Type", - "description": "服务类型" - }, - "nodeMetaDatas": { - "items": { "$ref": "#/components/schemas/NodeMetaDataItem" }, - "type": "array", - "title": "Nodemetadatas", - "default": [] - }, - "createdAt": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Createdat", - "description": "创建时间" - } - }, - "type": "object", - "required": ["serviceId", "name", "type"], - "title": "NodeServiceItem", - "description": "GET /api/flow/service 中单个service信息以及service下的节点元数据的信息" - }, - "NodeServiceListMsg": { - "properties": { - "services": { - "items": { "$ref": "#/components/schemas/NodeServiceItem" }, - "type": "array", - "title": "Services", - "description": "服务列表", - "default": [] - } - }, - "type": "object", - "title": "NodeServiceListMsg", - "description": "GET /api/flow/service result" - }, - "NodeServiceListRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/NodeServiceListMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "NodeServiceListRsp", - "description": "GET /api/flow/service 返回数据结构" - }, - "NumberOperate": { - "type": "string", - "enum": [ - "number_equal", - "number_not_equal", - "number_greater_than", - "number_less_than", - "number_greater_than_or_equal", - "number_less_than_or_equal" - ], - "title": "NumberOperate", - "description": "Choice 工具支持的数字运算符" - }, - "OidcRedirectMsg": { - "properties": { "url": { "type": "string", "title": "Url" } }, - "type": "object", - "required": ["url"], - "title": "OidcRedirectMsg", - "description": "GET /api/auth/redirect Result数据结构" - }, - "OidcRedirectRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/OidcRedirectMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "OidcRedirectRsp", - "description": "GET /api/auth/redirect 返回数据结构" - }, - "OperateAndBindType": { - "properties": { - "operate": { - "anyOf": [ - { "$ref": "#/components/schemas/NumberOperate" }, - { "$ref": "#/components/schemas/StringOperate" }, - { "$ref": "#/components/schemas/ListOperate" }, - { "$ref": "#/components/schemas/BoolOperate" }, - { "$ref": "#/components/schemas/DictOperate" } - ], - "title": "Operate", - "description": "操作类型" - }, - "bind_type": { - "$ref": "#/components/schemas/Type", - "description": "绑定类型" - } - }, - "type": "object", - "required": ["operate", "bind_type"], - "title": "OperateAndBindType", - "description": "操作和绑定类型数据结构" - }, - "ParamsNode": { - "properties": { - "paramName": { - "type": "string", - "title": "Paramname", - "description": "参数名称" - }, - "paramPath": { - "type": "string", - "title": "Parampath", - "description": "参数路径" - }, - "paramType": { - "$ref": "#/components/schemas/Type", - "description": "参数类型" - }, - "subParams": { - "anyOf": [ - { - "items": { "$ref": "#/components/schemas/ParamsNode" }, - "type": "array" - }, - { "type": "null" } - ], - "title": "Subparams", - "description": "子参数列表" - } - }, - "type": "object", - "required": ["paramName", "paramPath", "paramType"], - "title": "ParamsNode", - "description": "参数数据结构" - }, - "PermissionType": { - "type": "string", - "enum": ["protected", "public", "private"], - "title": "PermissionType", - "description": "权限类型" - }, - "PositionItem": { - "properties": { - "x": { "type": "number", "title": "X", "default": 0.0 }, - "y": { "type": "number", "title": "Y", "default": 0.0 } - }, - "type": "object", - "title": "PositionItem", - "description": "请求/响应中的前端相对位置变量类" - }, - "PostAuthKeyMsg": { - "properties": { "api_key": { "type": "string", "title": "Api Key" } }, - "type": "object", - "required": ["api_key"], - "title": "PostAuthKeyMsg", - "description": "POST /api/auth/key Result数据结构" - }, - "PostAuthKeyRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/PostAuthKeyMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "PostAuthKeyRsp", - "description": "POST /api/auth/key 返回数据结构" - }, - "PutFlowReq": { - "properties": { - "flow": { "$ref": "#/components/schemas/FlowItem-Input" } - }, - "type": "object", - "required": ["flow"], - "title": "PutFlowReq", - "description": "创建/修改流拓扑结构" - }, - "QuestionBlacklistRequest": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "question": { "type": "string", "title": "Question" }, - "answer": { "type": "string", "title": "Answer" }, - "is_deletion": { "type": "integer", "title": "Is Deletion" } - }, - "type": "object", - "required": ["id", "question", "answer", "is_deletion"], - "title": "QuestionBlacklistRequest", - "description": "POST /api/blacklist/question 请求数据结构" - }, - "RecentAppList": { - "properties": { - "applications": { - "items": { "$ref": "#/components/schemas/RecentAppListItem" }, - "type": "array", - "title": "Applications", - "description": "最近使用的应用列表" - } - }, - "type": "object", - "required": ["applications"], - "title": "RecentAppList", - "description": "GET /api/app/recent Result数据结构" - }, - "RecentAppListItem": { - "properties": { - "appId": { - "type": "string", - "title": "Appid", - "description": "应用ID" - }, - "name": { - "type": "string", - "title": "Name", - "description": "应用名称" - } - }, - "type": "object", - "required": ["appId", "name"], - "title": "RecentAppListItem", - "description": "GET /api/app/recent 列表项数据结构" - }, - "RecordContent": { - "properties": { - "question": { "type": "string", "title": "Question" }, - "answer": { "type": "string", "title": "Answer" }, - "data": { - "additionalProperties": true, - "type": "object", - "title": "Data", - "default": {} - }, - "facts": { - "items": { "type": "string" }, - "type": "array", - "title": "Facts", - "description": "[运行后修改]与Record关联的事实信息", - "default": [] - } - }, - "type": "object", - "required": ["question", "answer"], - "title": "RecordContent", - "description": "Record表子项:Record加密前的数据结构" - }, - "RecordData": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "groupId": { "type": "string", "title": "Groupid" }, - "conversationId": { "type": "string", "title": "Conversationid" }, - "taskId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Taskid" - }, - "document": { - "items": { "$ref": "#/components/schemas/RecordDocument" }, - "type": "array", - "title": "Document", - "default": [] - }, - "flow": { - "anyOf": [ - { "$ref": "#/components/schemas/RecordFlow" }, - { "type": "null" } - ] - }, - "content": { "$ref": "#/components/schemas/RecordContent" }, - "metadata": { "$ref": "#/components/schemas/RecordMetadata" }, - "comment": { - "$ref": "#/components/schemas/CommentType", - "default": "none" - }, - "createdAt": { "type": "number", "title": "Createdat" } - }, - "type": "object", - "required": [ - "id", - "groupId", - "conversationId", - "content", - "metadata", - "createdAt" - ], - "title": "RecordData", - "description": "GET /api/record/{conversation_id} Result内元素数据结构" - }, - "RecordDocument": { - "properties": { - "_id": { "type": "string", "title": "Id", "default": "" }, - "user_sub": { "type": "null", "title": "User Sub" }, - "name": { "type": "string", "title": "Name" }, - "type": { "type": "string", "title": "Type" }, - "size": { "type": "number", "title": "Size" }, - "created_at": { "type": "number", "title": "Created At" }, - "conversation_id": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Conversation Id" - }, - "order": { - "type": "integer", - "title": "Order", - "description": "文档顺序", - "default": 0 - }, - "abstract": { - "type": "string", - "title": "Abstract", - "description": "文档摘要", - "default": "" - }, - "author": { - "type": "string", - "title": "Author", - "description": "文档作者", - "default": "" - }, - "associated": { - "type": "string", - "enum": ["question", "answer"], - "title": "Associated" - } - }, - "type": "object", - "required": ["name", "type", "size", "associated"], - "title": "RecordDocument", - "description": "GET /api/record/{conversation_id} Result中的document数据结构" - }, - "RecordFlow": { - "properties": { - "id": { "type": "string", "title": "Id" }, - "recordId": { "type": "string", "title": "Recordid" }, - "flowId": { "type": "string", "title": "Flowid" }, - "flowName": { "type": "string", "title": "Flowname", "default": "" }, - "flowStatus": { - "$ref": "#/components/schemas/StepStatus", - "default": "success" - }, - "stepNum": { "type": "integer", "title": "Stepnum" }, - "steps": { - "items": { "$ref": "#/components/schemas/RecordFlowStep" }, - "type": "array", - "title": "Steps" - } - }, - "type": "object", - "required": ["id", "recordId", "flowId", "stepNum", "steps"], - "title": "RecordFlow", - "description": "Flow的执行信息" - }, - "RecordFlowStep": { - "properties": { - "stepId": { "type": "string", "title": "Stepid" }, - "stepStatus": { "$ref": "#/components/schemas/StepStatus" }, - "input": { - "additionalProperties": true, - "type": "object", - "title": "Input" - }, - "output": { - "additionalProperties": true, - "type": "object", - "title": "Output" - } - }, - "type": "object", - "required": ["stepId", "stepStatus", "input", "output"], - "title": "RecordFlowStep", - "description": "Record表子项:flow的单步数据结构" - }, - "RecordListMsg": { - "properties": { - "records": { - "items": { "$ref": "#/components/schemas/RecordData" }, - "type": "array", - "title": "Records" - } - }, - "type": "object", - "required": ["records"], - "title": "RecordListMsg", - "description": "GET /api/record/{conversation_id} Result数据结构" - }, - "RecordListRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/RecordListMsg" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "RecordListRsp", - "description": "GET /api/record/{conversation_id} 返回数据结构" - }, - "RecordMetadata": { - "properties": { - "inputTokens": { - "type": "integer", - "title": "Inputtokens", - "default": 0 - }, - "outputTokens": { - "type": "integer", - "title": "Outputtokens", - "default": 0 - }, - "timeCost": { "type": "number", "title": "Timecost", "default": 0 }, - "feature": { - "additionalProperties": true, - "type": "object", - "title": "Feature", - "default": {} - }, - "footNoteMetadataList": { - "items": { "$ref": "#/components/schemas/FootNoteMetaData" }, - "type": "array", - "title": "Footnotemetadatalist", - "description": "脚注元信息列表", - "default": [] - } - }, - "type": "object", - "title": "RecordMetadata", - "description": "Record表子项:Record的元信息" - }, - "RequestData": { - "properties": { - "question": { - "type": "string", - "maxLength": 2000, - "title": "Question", - "description": "用户输入" - }, - "conversationId": { - "type": "string", - "title": "Conversationid", - "description": "聊天ID", - "default": "" - }, - "groupId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Groupid", - "description": "问答组ID" - }, - "language": { - "type": "string", - "title": "Language", - "description": "语言", - "default": "zh" - }, - "files": { - "items": { "type": "string" }, - "type": "array", - "title": "Files", - "description": "文件列表", - "default": [] - }, - "app": { - "anyOf": [ - { "$ref": "#/components/schemas/RequestDataApp" }, - { "type": "null" } - ], - "description": "应用" - }, - "debug": { - "type": "boolean", - "title": "Debug", - "description": "是否调试", - "default": false - }, - "taskId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Taskid", - "description": "任务ID" - } - }, - "type": "object", - "required": ["question"], - "title": "RequestData", - "description": "POST /api/chat 请求的总的数据结构" - }, - "RequestDataApp": { - "properties": { - "appId": { - "type": "string", - "title": "Appid", - "description": "应用ID" - }, - "flowId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Flowid", - "description": "Flow ID" - }, - "params": { - "anyOf": [ - { "$ref": "#/components/schemas/param" }, - { "type": "null" } - ], - "description": "流执行过程中的参数补充" - } - }, - "type": "object", - "required": ["appId"], - "title": "RequestDataApp", - "description": "模型对话中包含的app信息" - }, - "ResponseData": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "title": "Result" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "ResponseData", - "description": "基础返回数据结构" - }, - "SearchType": { - "type": "string", - "enum": ["all", "name", "description", "author"], - "title": "SearchType", - "description": "搜索类型" - }, - "ServiceApiData": { - "properties": { - "name": { - "type": "string", - "title": "Name", - "description": "接口名称" - }, - "path": { - "type": "string", - "title": "Path", - "description": "接口路径" - }, - "description": { - "type": "string", - "title": "Description", - "description": "接口描述" - } - }, - "type": "object", - "required": ["name", "path", "description"], - "title": "ServiceApiData", - "description": "语义接口中心:服务 API 接口属性数据结构" - }, - "ServiceCardItem": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "服务ID" - }, - "name": { - "type": "string", - "title": "Name", - "description": "服务名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "服务简介" - }, - "icon": { - "type": "string", - "title": "Icon", - "description": "服务图标" - }, - "author": { - "type": "string", - "title": "Author", - "description": "服务作者" - }, - "favorited": { - "type": "boolean", - "title": "Favorited", - "description": "是否已收藏" - } - }, - "type": "object", - "required": [ - "serviceId", - "name", - "description", - "icon", - "author", - "favorited" - ], - "title": "ServiceCardItem", - "description": "语义接口中心:服务卡片数据结构" - }, - "StepParams": { - "properties": { - "stepId": { - "type": "string", - "title": "Stepid", - "description": "步骤ID" - }, - "name": { - "type": "string", - "title": "Name", - "description": "Step名称" - }, - "paramsNode": { - "anyOf": [ - { "$ref": "#/components/schemas/ParamsNode" }, - { "type": "null" } - ], - "description": "参数节点" - } - }, - "type": "object", - "required": ["stepId", "name"], - "title": "StepParams", - "description": "参数数据结构" - }, - "StepStatus": { - "type": "string", - "enum": [ - "unknown", - "init", - "waiting", - "running", - "success", - "error", - "param", - "cancelled" - ], - "title": "StepStatus", - "description": "步骤状态" - }, - "StringOperate": { - "type": "string", - "enum": [ - "string_equal", - "string_not_equal", - "string_contains", - "string_not_contains", - "string_starts_with", - "string_ends_with", - "string_length_equal", - "string_length_greater_than", - "string_length_greater_than_or_equal", - "string_length_less_than", - "string_length_less_than_or_equal", - "string_regex_match" - ], - "title": "StringOperate", - "description": "Choice 工具支持的字符串运算符" - }, - "TeamKnowledgeBaseItem": { - "properties": { - "teamId": { - "type": "string", - "title": "Teamid", - "description": "团队ID" - }, - "teamName": { - "type": "string", - "title": "Teamname", - "description": "团队名称" - }, - "kb_list": { - "items": { "$ref": "#/components/schemas/KnowledgeBaseItem" }, - "type": "array", - "title": "Kb List", - "description": "知识库列表", - "default": [] - } - }, - "type": "object", - "required": ["teamId", "teamName"], - "title": "TeamKnowledgeBaseItem", - "description": "团队知识库列表项数据结构" - }, - "Type": { - "type": "string", - "enum": ["string", "number", "list", "dict", "bool"], - "title": "Type", - "description": "Choice 工具支持的类型" - }, - "UpdateConversationRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { "$ref": "#/components/schemas/ConversationListItem" } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "UpdateConversationRsp", - "description": "POST /api/conversation 返回数据结构" - }, - "UpdateKbReq": { - "properties": { - "kbIds": { - "items": { "type": "string" }, - "type": "array", - "title": "Kbids", - "description": "知识库ID列表", - "default": [] - } - }, - "type": "object", - "title": "UpdateKbReq", - "description": "更新知识库请求体" - }, - "UpdateLLMReq": { - "properties": { - "icon": { - "type": "string", - "title": "Icon", - "description": "图标", - "default": "" - }, - "openaiBaseUrl": { - "type": "string", - "title": "Openaibaseurl", - "description": "OpenAI API Base URL", - "default": "" - }, - "openaiApiKey": { - "type": "string", - "title": "Openaiapikey", - "description": "OpenAI API Key", - "default": "" - }, - "modelName": { - "type": "string", - "title": "Modelname", - "description": "模型名称", - "default": "" - }, - "maxTokens": { - "type": "integer", - "title": "Maxtokens", - "description": "最大token数", - "default": 8192 - } - }, - "type": "object", - "title": "UpdateLLMReq", - "description": "更新大模型请求体" - }, - "UpdateMCPServiceMsg": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "MCP服务ID" - }, - "name": { - "type": "string", - "title": "Name", - "description": "MCP服务名称" - } - }, - "type": "object", - "required": ["serviceId", "name"], - "title": "UpdateMCPServiceMsg", - "description": "插件中心:MCP服务属性数据结构" - }, - "UpdateMCPServiceRequest": { - "properties": { - "serviceId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Serviceid", - "description": "服务ID(更新时传递)" - }, - "name": { - "type": "string", - "title": "Name", - "description": "MCP服务名称" - }, - "description": { - "type": "string", - "title": "Description", - "description": "MCP服务描述" - }, - "overview": { - "type": "string", - "title": "Overview", - "description": "MCP服务概述" - }, - "config": { - "type": "string", - "title": "Config", - "description": "MCP服务配置" - }, - "mcpType": { - "$ref": "#/components/schemas/MCPType", - "description": "MCP传输协议(Stdio/SSE/Streamable)", - "default": "stdio" - } - }, - "type": "object", - "required": ["name", "description", "overview", "config"], - "title": "UpdateMCPServiceRequest", - "description": "POST /api/mcpservice 请求数据结构" - }, - "UpdateMCPServiceRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "$ref": "#/components/schemas/UpdateMCPServiceMsg", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "UpdateMCPServiceRsp", - "description": "POST /api/mcp_service 返回数据结构" - }, - "UpdateServiceMsg": { - "properties": { - "serviceId": { - "type": "string", - "title": "Serviceid", - "description": "服务ID" - }, - "name": { - "type": "string", - "title": "Name", - "description": "服务名称" - }, - "apis": { - "items": { "$ref": "#/components/schemas/ServiceApiData" }, - "type": "array", - "title": "Apis", - "description": "解析后的接口列表" - } - }, - "type": "object", - "required": ["serviceId", "name", "apis"], - "title": "UpdateServiceMsg", - "description": "语义接口中心:服务属性数据结构" - }, - "UpdateServiceRequest": { - "properties": { - "serviceId": { - "anyOf": [{ "type": "string" }, { "type": "null" }], - "title": "Serviceid", - "description": "服务ID(更新时传递)" - }, - "data": { - "additionalProperties": true, - "type": "object", - "title": "Data", - "description": "对应 YAML 内容的数据对象" - } - }, - "type": "object", - "required": ["data"], - "title": "UpdateServiceRequest", - "description": "POST /api/service 请求数据结构" - }, - "UpdateServiceRsp": { - "properties": { - "code": { "type": "integer", "title": "Code" }, - "message": { "type": "string", "title": "Message" }, - "result": { - "$ref": "#/components/schemas/UpdateServiceMsg", - "title": "Result" - } - }, - "type": "object", - "required": ["code", "message", "result"], - "title": "UpdateServiceRsp", - "description": "POST /api/service 返回数据结构" - }, - "UserBlacklistRequest": { - "properties": { - "user_sub": { "type": "string", "title": "User Sub" }, - "is_ban": { "type": "integer", "title": "Is Ban" } - }, - "type": "object", - "required": ["user_sub", "is_ban"], - "title": "UserBlacklistRequest", - "description": "POST /api/blacklist/user 请求数据结构" - }, - "UserUpdateRequest": { - "properties": { - "autoExecute": { - "type": "boolean", - "title": "Autoexecute", - "description": "是否自动执行", - "default": false - } - }, - "type": "object", - "title": "UserUpdateRequest", - "description": "更新用户信息请求体" - }, - "ValidationError": { - "properties": { - "loc": { - "items": { "anyOf": [{ "type": "string" }, { "type": "integer" }] }, - "type": "array", - "title": "Location" - }, - "msg": { "type": "string", "title": "Message" }, - "type": { "type": "string", "title": "Error Type" } - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError" - }, - "_GetAuthKeyMsg": { - "properties": { - "api_key_exists": { "type": "boolean", "title": "Api Key Exists" } - }, - "type": "object", - "required": ["api_key_exists"], - "title": "_GetAuthKeyMsg", - "description": "GET /api/auth/key Result数据结构" - }, - "param": { - "properties": { - "content": { - "anyOf": [ - { "additionalProperties": true, "type": "object" }, - { "type": "boolean" } - ], - "title": "Content", - "description": "流执行过程中的参数补充内容", - "default": {} - }, - "description": { - "type": "string", - "title": "Description", - "description": "流执行过程中的参数补充描述", - "default": "" - } - }, - "type": "object", - "title": "param", - "description": "流执行过程中的参数补充" - } - } - } -} diff --git a/docs/resource/image-20250918092746531.png b/docs/resource/image-20250918092746531.png deleted file mode 100644 index c37f6abd03f5cfec02c2c581188acad47537e277..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918092746531.png and /dev/null differ diff --git a/docs/resource/image-20250918092914434.png b/docs/resource/image-20250918092914434.png deleted file mode 100644 index 15eb6f5fb465272b5f857acc3ec876b324794043..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918092914434.png and /dev/null differ diff --git a/docs/resource/image-20250918092956884.png b/docs/resource/image-20250918092956884.png deleted file mode 100644 index e9f39e26b97d32af46826e5c5f204b98e7da3490..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918092956884.png and /dev/null differ diff --git a/docs/resource/image-20250918093033417.png b/docs/resource/image-20250918093033417.png deleted file mode 100644 index 249318a9fb59ac71b27894bec0b9c29e7c28aa9a..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918093033417.png and /dev/null differ diff --git a/docs/resource/image-20250918093329139.png b/docs/resource/image-20250918093329139.png deleted file mode 100644 index c4c438c169f372c3ce0a6aa0f141f47ce795447b..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918093329139.png and /dev/null differ diff --git a/docs/resource/image-20250918093620039.png b/docs/resource/image-20250918093620039.png deleted file mode 100644 index 4cd69bb3cbe3883384457ba524d9ecbc40f35e21..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918093620039.png and /dev/null differ diff --git a/docs/resource/image-20250918095405109.png b/docs/resource/image-20250918095405109.png deleted file mode 100644 index 58e3196ec2ed2c9c958230dcb089bb237ba4d68f..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918095405109.png and /dev/null differ diff --git a/docs/resource/image-20250918095742768.png b/docs/resource/image-20250918095742768.png deleted file mode 100644 index a5ca512f02107f2fb8ac63971ac9c67dd2974161..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918095742768.png and /dev/null differ diff --git a/docs/resource/image-20250918100010161.png b/docs/resource/image-20250918100010161.png deleted file mode 100644 index 6c45efcbf9b55c7fb50d03e2be4a8689b9a3bfc6..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918100010161.png and /dev/null differ diff --git a/docs/resource/image-20250918100409132.png b/docs/resource/image-20250918100409132.png deleted file mode 100644 index c2fcc84a7a33099aae01999dccdc38fafddb408c..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918100409132.png and /dev/null differ diff --git a/docs/resource/image-20250918100502872.png b/docs/resource/image-20250918100502872.png deleted file mode 100644 index 73fb337b965272590628a6dbcb56646dd79e20c7..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918100502872.png and /dev/null differ diff --git a/docs/resource/image-20250918100635136.png b/docs/resource/image-20250918100635136.png deleted file mode 100644 index 8ebd4b3d6c85429b63c038c5e5a71e88a77d775e..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918100635136.png and /dev/null differ diff --git a/docs/resource/image-20250918100832293.png b/docs/resource/image-20250918100832293.png deleted file mode 100644 index 3d21d3b0fc11a75ff3a7c6875d47acf7a7fc3735..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918100832293.png and /dev/null differ diff --git a/docs/resource/image-20250918101004733.png b/docs/resource/image-20250918101004733.png deleted file mode 100644 index a3ac04e0b962df3f0acc741a83a0e9486341f253..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918101004733.png and /dev/null differ diff --git a/docs/resource/image-20250918101029066.png b/docs/resource/image-20250918101029066.png deleted file mode 100644 index 29f971788175a5b00bb2fec6b33eb8e001d4e640..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918101029066.png and /dev/null differ diff --git a/docs/resource/image-20250918101147653.png b/docs/resource/image-20250918101147653.png deleted file mode 100644 index 52546654a7d14dc7a2793edfed92e611a4a58db5..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918101147653.png and /dev/null differ diff --git a/docs/resource/image-20250918101425789.png b/docs/resource/image-20250918101425789.png deleted file mode 100644 index da12e2d14bbec2d6dd9192e8a7b45d59f10bcd20..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918101425789.png and /dev/null differ diff --git a/docs/resource/image-20250918101443421.png b/docs/resource/image-20250918101443421.png deleted file mode 100644 index ced1fb5baeaa90a20d299e84bef76ec3a692c011..0000000000000000000000000000000000000000 Binary files a/docs/resource/image-20250918101443421.png and /dev/null differ diff --git a/docs/resource/oi-deploy-01-welcome.png b/docs/resource/oi-deploy-01-welcome.png new file mode 100644 index 0000000000000000000000000000000000000000..e67117e0df1cf048d082794d497356c4c4f10b19 Binary files /dev/null and b/docs/resource/oi-deploy-01-welcome.png differ diff --git a/docs/resource/oi-deploy-02-env-check.png b/docs/resource/oi-deploy-02-env-check.png new file mode 100644 index 0000000000000000000000000000000000000000..0edd2ccebad72a4f08f3c93375595590b85cc07a Binary files /dev/null and b/docs/resource/oi-deploy-02-env-check.png differ diff --git a/docs/resource/oi-deploy-03-basic.png b/docs/resource/oi-deploy-03-basic.png new file mode 100644 index 0000000000000000000000000000000000000000..8ab373830d644ee035b2b8c595b9d54fc8accb18 Binary files /dev/null and b/docs/resource/oi-deploy-03-basic.png differ diff --git a/docs/resource/oi-deploy-04-llm-tab.png b/docs/resource/oi-deploy-04-llm-tab.png new file mode 100644 index 0000000000000000000000000000000000000000..a269726dd0f6c6ef464225b3e5a7a45718966318 Binary files /dev/null and b/docs/resource/oi-deploy-04-llm-tab.png differ diff --git a/docs/resource/oi-deploy-05-llm-fill-in.png b/docs/resource/oi-deploy-05-llm-fill-in.png new file mode 100644 index 0000000000000000000000000000000000000000..5a903b878d13552c40a1b3629b5ca1724953c9fa Binary files /dev/null and b/docs/resource/oi-deploy-05-llm-fill-in.png differ diff --git a/docs/resource/oi-deploy-06-start.png b/docs/resource/oi-deploy-06-start.png new file mode 100644 index 0000000000000000000000000000000000000000..917324bcf39fb46231ade44224aa6a141fc87f59 Binary files /dev/null and b/docs/resource/oi-deploy-06-start.png differ diff --git a/docs/resource/oi-deploy-07-ing.png b/docs/resource/oi-deploy-07-ing.png new file mode 100644 index 0000000000000000000000000000000000000000..1950f5915e4ffe1f6c6abd5349fe024d96b48918 Binary files /dev/null and b/docs/resource/oi-deploy-07-ing.png differ diff --git a/docs/resource/oi-deploy-08-finish.png b/docs/resource/oi-deploy-08-finish.png new file mode 100644 index 0000000000000000000000000000000000000000..a43c32c639f7461ebfd5d5a82ae4bb32ed64daa2 Binary files /dev/null and b/docs/resource/oi-deploy-08-finish.png differ diff --git "a/docs/\351\203\250\347\275\262 & \344\275\277\347\224\250\346\211\213\345\206\214.md" "b/docs/\351\203\250\347\275\262 & \344\275\277\347\224\250\346\211\213\345\206\214.md" deleted file mode 100644 index 5ddec1857ef391a5e170539ca833d6cb51772d01..0000000000000000000000000000000000000000 --- "a/docs/\351\203\250\347\275\262 & \344\275\277\347\224\250\346\211\213\345\206\214.md" +++ /dev/null @@ -1,1460 +0,0 @@ -# 部署 & 使用手册 - -## 一、更新源 - -**930 发布之后无需更新源**,安装只支持 `openEuler 24.03 LTS SP2`。 - -```bash -echo "[EPOL-Preview] -name=EPOL-Preview -baseurl=https://eulermaker.compass-ci.openeuler.openatom.cn/api/ems4/repositories/openEuler-24.03-LTS-SP2:epol/openEuler%3A24.03-LTS-SP2/$basearch -metadata_expire=1h -enabled=1 -gpgcheck=1 -gpgkey=https://eulermaker.compass-ci.openeuler.openatom.cn/api/ems4/repositories/openEuler-24.03-LTS-SP2:epol/openEuler%3A24.03-LTS-SP2/$basearch/RPM-GPG-KEY-openEuler" >> /etc/yum.repos.d/openEuler-Intelligence-Preview.repo -``` - -## 二、安装 openeuler-intelligence-cli 和 openeuler-intelligence-installer - -```bash -dnf install openeuler-intelligence-cli openeuler-intelligence-installer -y -``` - -![image-20250918092746531](./resource/image-20250918092746531.png) - -## 三、初始化 openEuler Intelligence - -```bash -oi --init -``` - -**注意**:*请使用 root 用户或管理员权限执行* - -![image-20250918092914434](./resource/image-20250918092914434.png) - -特别说明:命令行客户端的界面会随着终端的适配情况出现不同的样式,是正常现象,本文档以 Windows 10 的默认终端为例进行展示。 - -### 3.1 - -选择部署新服务 - -![image-20250918092956884](./resource/image-20250918092956884.png) - -### 3.2 - -点击继续配置 - -![image-20250918093033417](./resource/image-20250918093033417.png) - -### 3.3 - -服务器 IP 默认 127.0.0.1,部署模式选择默认的`轻量部署`,点击LLM配置 - -![image-20250918093329139](./resource/image-20250918093329139.png) - -依次填写 API 端点、API 秘钥、模型名称,轻量部署可不填写 Embedding 配置,全量部署需要填写 - -![image-20250918093620039](./resource/image-20250918093620039.png) - -### 3.4 - -点击开始部署,等待部署执行完成 - -![image-20250918095405109](./resource/image-20250918095405109.png) - -![image-20250918095742768](./resource/image-20250918095742768.png) - -![image-20250918100010161](./resource/image-20250918100010161.png) - -### 3.5 - -点击完成,结束初始化 - -## 四、使用 Shell 客户端 - -具体参考 README - -链接:[README](../README.md) - -### 4.1 - -打开 Shell 客户端 - -```bash -oi -``` - -![image-20250918100409132](./resource/image-20250918100409132.png) - -### 4.2 - -选择智能体,默认为hce运维助手 - -![image-20250918100502872](./resource/image-20250918100502872.png) - -### 4.3 - -进行智能体的使用,此处以hce运维助手举例,回车确认,进入对话界面 - -![image-20250918100635136](./resource/image-20250918100635136.png) - -### 4.4 - -在左下角输入栏输入命令或问题,如帮我分析当前机器性能情况,智能体会根据提问自动选择合适的 MCP 工具,并询问是否执行,此处点击确认 - -![image-20250918100832293](./resource/image-20250918100832293.png) - -### 4.5 - -根据具体情况依次执行 MCP 工具 - -![image-20250918101004733](./resource/image-20250918101004733.png) - -![image-20250918101029066](./resource/image-20250918101029066.png) - -![image-20250918101147653](./resource/image-20250918101147653.png) - -### 4.6 - -智能体根据工具调用结果输出分析报告 - -![image-20250918101425789](./resource/image-20250918101425789.png) - -![image-20250918101443421](./resource/image-20250918101443421.png) - -## 五、默认智能体的使用 - -### 5.1 运维助手 - -hce运维助手包含2个 MCP 服务,以下为工具列表 - -#### `shell_generator` 服务 - -| 工具名称 | 参数 | 功能描述 | -| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -| cmd_generator_tool | 必选参数: - goal:用户的需求(自然语言描述) 可选参数: - host:远程主机名称或 IP 地址(不提供则默认本地主机) | 根据用户的自然语言需求,结合目标主机(本地或远程)的系统信息(包括系统版本、运行时间、资源使用情况等),生成适配的 Shell 命令 | -| cmd_executor_tool | 必选参数: - command:需要执行的 Shell 命令 可选参数: - host:远程主机名称或 IP 地址(不提供则默认本地主机) | 在指定主机(本地或远程)上执行 Shell 命令,返回命令执行结果;若执行出错,返回错误信息 | - -#### `remote_info_mcp` 服务 - -| 工具名称 | 参数 | 功能描述 | -| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| top_collect_tool | 必选参数:无 可选参数: - host:远程主机名称 / IP(默认本地) - k:进程数量(默认 5) | 获取本地或远程主机内存占用最多的 k 个进程信息,包含进程 ID、名称、内存使用量(MB) | -| get_process_info_tool | 必选参数: - pid:进程 ID 可选参数: - host:远程主机名称 / IP(默认本地) | 获取本地或远程主机指定 PID 进程的详细信息,含状态、创建时间、CPU / 内存使用等 | -| change_name_to_pid_tool | 必选参数: - name:进程名称 可选参数: - host:远程主机名称 / IP(默认本地) | 根据进程名称获取本地或远程主机对应的 PID 列表,以空格分隔返回 | -| get_cpu_info_tool | 必选参数:无 可选参数: - host:远程主机名称 / IP(默认本地) | 获取本地或远程主机 CPU 信息,含物理 / 逻辑核心数、频率、各核心使用率 | -| memory_anlyze_tool | 必选参数:无 可选参数: - host:远程主机名称 / IP(默认本地) | 分析本地或远程主机内存使用情况,含总内存、可用 / 已用内存、使用率 | -| get_disk_info_tool | 必选参数:无 可选参数: - host:远程主机名称 / IP(默认本地) | 获取本地或远程主机磁盘信息,含设备名称、挂载点、容量及使用率 | -| get_os_info_tool | 必选参数:无 可选参数: - host:远程主机名称 / IP(默认本地) | 获取本地或远程主机操作系统类型及版本信息,返回字符串格式 | -| get_network_info_tool | 必选参数:无 可选参数: - host:远程主机名称 / IP(默认本地) | 获取本地或远程主机网络接口信息,含接口名称、IP / 子网掩码 / MAC 地址、启用状态 | -| write_report_tool | 必选参数: - report:报告内容字符串 可选参数:无 | 将分析结果写入本地报告文件,返回文件绝对路径 | -| telnet_test_tool | 必选参数: - host:远程主机名称 / IP - port:端口号(1-65535) 可选参数:无 | 测试本地到目标主机指定端口的 Telnet 连接,返回布尔值表示连接是否成功 | -| ping_test_tool | 必选参数: - host:远程主机名称 / IP 可选参数:无 | 测试本地到目标主机的 Ping 连接,返回布尔值表示连接是否成功 | -| get_dns_info_tool | 必选参数:无 可选参数: - host:远程主机名称 / IP(默认本地) | 获取本地或远程主机 DNS 配置信息,含 DNS 服务器列表、搜索域列表 | -| perf_data_tool | 必选参数:无 可选参数: - host:远程主机名称 / IP(默认本地) - pid:进程 ID(默认所有进程) | 收集本地或远程主机性能数据,含 CPU / 内存使用率、I/O 统计信息 | - -##### 使用方法参考 - -可根据工具能力咨询相关问题或者执行命令 - -如果需要查询远程服务器相关能力,需要配置 `/usr/lib/openEuler Intelligence/mcp_center/config/public/public_config.toml` - -```toml -# 公共配置文件 -# 语言设置,支持 zh (中文) 和 en (英文) -language = "zh" -# 大模型配置 -llm_remote = "https://api.openai.com/v1" -llm_model = "gpt-5" -llm_api_key = "sk-xxxxxxxxxxxxxxxx" -max_tokens = 8192 -temperature = 0.7 -# 远程主机列表配置 -[[remote_hosts]] -name = "remote_server_name" -os_type = "openEuler" -host = "xx.xx.xx.xx" -port = 22 -username = "root" -password = "xxxxxx" -``` - -### 5.2 sysTrace 运维助手 - -systrace 运维助手为 sysTrace 的 MCP 化服务,提供感知、定界和报告生成三个工具 - -| 工具名称 | 参数 | 功能描述 | -| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| slow_node_perception_tool | 必选参数: - task_id:任务 ID(格式如 IP 地址,例:192.168.2.122) 可选参数:无 | 检测指定 task_id 对应的机器性能是否发生劣化,返回包含异常标识及性能数据的感知结果;根据结果是否异常决定后续调用 `slow_node_detection_tool` 或直接调用 `generate_report_tool` | -| slow_node_detection_tool | 必选参数: - performance_data:感知工具返回的完整性能数据(PerceptionResult 类型) 可选参数:无 | 仅在 `slow_node_perception_tool` 返回 `is_anomaly=True` 时调用,对劣化节点进行慢卡定界分析,输出定界结果(含异常节点、指标等信息),需后续调用 `generate_report_tool` 生成报告 | -| generate_report_tool | 必选参数: - source_data:感知结果(PerceptionResult)或定界结果(AIJobDetectResult) - report_type:报告类型(ReportType 枚举,可选 normal/anomaly) 可选参数:无 | 接收感知或定界结果,按指定类型生成 Markdown 格式的《AI 训练任务性能诊断报告》,未劣化时输出正常结论,劣化时输出异常详情及建议 | - -systrace 运维助手的使用需要搭配 sysTrace 的数据采集部分,参考链接为 [docs/0.quickstart.md · openEuler/sysTrace](https://gitee.com/openeuler/sysTrace/blob/master/docs/0.quickstart.md) - -#### 5.2.1 在目标服务器上安装 sysTrace 数据采集模块,对 AI 训练任务进行数据采集 - -sysTrace 是一款运用于在 AI 训练任务中的软件。在 AI 训练中,常常出现训练任务故障导致训练成本浪费,业务痛点如下: - -- AI 训练性能故障缺乏常态化监控、检测能力 -- Host bound 引发的 AI 任务慢,卡故障缺乏全栈跟踪能力 - -sysTrace 工具支持如下功能: - -- 采集 torch_npu 层的 python 函数的调用栈 -- 采集 cann 层的内存持有情况,判断是否发生 HBM OOM 故障 -- 采集 mspti 的通信算子下发/执行,判断是否发生算子慢的情况,从而定位到慢卡 -- 采集 oncpu/offcpu 事件,判断 AI 训练中是否存在其他进程抢占 CPU 导致训练慢的问题 - -##### 环境 - -- **OS**: openEuler 22.03 (LTS-SP4) --5.10.0-60.18.0.50.oe2203.aarch64 -- **软件版本**:CANN 8.0RC3, torch 2.1.0, torch_npu 2.1.0.post10 - -##### 编译 - -- 下载源码: - -- 安装依赖包 - - *软件包版本:libbpf >= 0.8.1, clang >= 10.0.0 gcc >= 8.3.0, bpftool >= 6.8.0,如果版本均满足则跳过下面的手动安装步骤* - - ```bash - dnf install gcc g++ cmake make python3-devel protobuf-compiler protobuf-devel protobuf-c-devel libbpf clang libbpf-devel bpftool - ``` - -- 手动安装 `libbpf` - - ```bash - git clone https://github.com/libbpf/libbpf.git - git checkout v0.8.1 - cd libbpf/src - make && make install - ``` - -- 手动安装 `bpftool` - - ```bash - git clone --recurse-submodules https://github.com/libbpf/bpftool.git - git submodule update --init - cd src - make && make install - ``` - -- 编译 - - ```bash - cd sysTrace - bash build.sh - ``` - - 编译产物均在 `build` 目录下,会用到 `libsysTrace.so` 和 `sysTrace_cli` - - ```text - total 1776 - -rw-r--r--. 1 root root 17000 Jun 12 16:58 CMakeCache.txt - drwxr-xr-x. 7 root root 4096 Jun 12 17:10 CMakeFiles - -rw-r--r--. 1 root root 1798 Jun 12 16:58 cmake_install.cmake - -rw-r--r--. 1 root root 534270 Jun 12 17:10 libcommon.a - -rwxr-xr-x. 1 root root 1209296 Jun 12 17:10 libsysTrace.so - -rw-r--r--. 1 root root 20479 Jun 12 17:10 Makefile - drwxr-xr-x. 3 root root 4096 Jun 12 17:10 protos - -rwxr-xr-x. 1 root root 76736 Jun 12 17:10 sysTrace_cli - ``` - -##### 使用 - -###### 数据采集 - -- 修改 AI 训练任务脚本,使用 `LD_PRELAOD` 的方式将动态库加载到 AI 训练任务中 - - ```bash - LD_PRELOAD=/usr/local/lib/libunwind.so.8.2.0:/usr/local/lib/libunwind-aarch64.so.8.2.0:/home/ascend-toolkit-bak/ascend-toolkit/8.0.RC3.10/tools/mspti/lib64/libmspti.so:/systrace/build/libsysTrace.so python ... - ``` - -- **注意:以LD_PRELOAD的方式加载了/usr/local/lib/libunwind.so.8.2.0:/usr/local/lib/libunwind-aarch64.so.8.2.0的原因是因为低于1.7版本的libunwind有bug,需要手动下载最新版本的libunwind,如果环境中的libunwind版本大于等于1.7,则使用以下命令** - - ```bash - LD_PRELOAD=/home/ascend-toolkit-bak/ascend-toolkit/8.0.RC3.10/tools/mspti/lib64/libmspti.so:/systrace/build/libsysTrace.so python ... - ``` - -###### 动态开关 - -- sysTrace 支持动态开启采集数据,采集数据类型支持动态开启,sysTrace 提供二进制工具 sysTrace_cli,当前 L0 数据是常态开启,L1/2/3 类型数据可自行决定是否开启,使用命令如下: - - ```bash - [root@localhost ~]# sysTrace_cli help - Usage: sysTrace_client [args] - Commands: - set = - Enable/disable dump level - (levels: L0, L1, L2, L3) - interval = - Set dump interval in minutes - (levels: L1, L2, L3) - print [level|all] - Print current settings - (levels: L0, L1, L2, L3, all) - - Examples: - sysTrace_cli set L1=true - sysTrace_cli interval L1=10 - sysTrace_cli print all - ``` - -###### 数据落盘 - -- 所有采集的数据当前存放在 `/home/sysTrace` 目录下,每张卡上的数据以独立一个文件保存,集群多节点环境,建议将保存目录 `/home/sysTrace` 映射到共享目录,否则需要手动将每台节点上的数据手动拷贝,如下: - - ```text - drwxr-xr-x. 2 root root 4096 Jun 12 17:01 cann # 内存数据 - drwxr-xr-x. 2 root root 4096 Jun 12 17:01 mspti # 通信算子数据 - drwxr-xr-x. 2 root root 4096 Jun 12 17:01 timeline # torch_npu 层数据 - drwxr-xr-x. 2 root root 4096 Jun 12 17:01 osprobe # offcpu/cpu 事件 - ``` - -- sysTrace 支持动态开启采集数据,当前支持以下级别的数据: - - - L0:采集 torch_npu 层数据,采集数据类型包括如下数据(常态化采集) - - ```cpp - message Pytorch { - repeated PytorchStage pytorch_stages = 1; - uint32 rank = 2; // rank 号 - } - - message PytorchStage { - uint32 stage_id = 1; // AI 训练迭代轮次 - string stage_type = 2; // AI 训练迭代阶段 - uint64 start_us = 3; // 当前迭代阶段的开始时间 - uint64 end_us = 4; // 当前迭代阶段的结束时间 - repeated string stack_frames = 5; // 当前迭代阶段的 python 调用栈 - oneof debug_data { - GcDebugData gc_debug = 6; // 当前迭代阶段的 GC 数据 - } - } - ``` - - - L1:采集通信算子数据,采集数据包括如下数据 - - ```text - Flag,Id,Kind,Name,SourceKind,Timestamp,msptiObjectId_Ds_DeviceId,msptiObjectId_Ds_StreamId,msptiObjectId_Pt_ProcessId,msptiObjectId_Pt_ThreadId - ``` - -#### 5.2.2 修改 sysTrace 运维助手的配置文件配合数据采集模块对 AI 训练任务结果进行分析 - -在 `/etc/systrace/config` 目录下修改 `ftp_config.json` 文件 - -```json5 -{ - "servers": [ - { - "ip": "192.168.xxx.196", # 远程目标服务器的 IP - "port": 22, # 远程目标服务器的 SSH 端口 - "user": "root", # 用户名 - "password": "password", # 密码 - "perception_remote_dir": "/home/sysTrace/timeline", # 远程目标服务器 systrace 采集的 timeline 数据保存路径 - "detection_remote_dir": "/home/sysTrace/mspti", # 远程目标服务器 systrace 采集的 mspti 数据保存路径 - } - ], - "enable": "True" # True 为开启远程获取数据,False 为关闭只使用本地文件进行分析 -} - -``` - -修改 `model_config.json` 文件,主要是数据保存的路径,其他参数参考 [docs/0.quickstart.md · openEuler/sysTrace](https://gitee.com/openeuler/sysTrace/blob/master/docs/0.quickstart.md) - -```json -"training_log": "/home/sysTrace/timeline" # 感知数据的保存路径 -"root_path": "/home/sysTrace/mspti", # 定界数据的保存路径 -``` - -##### 慢卡定界算法配置 - -在文件 `model_config.json` 中,配置模型运行所需的参数。该配置项中,主要包含: - -- `with_fail_slow`: 配置启动慢节点检测性能劣化来源于性能劣化检测的时刻还是手动配置, 默认为false -- `slow_node_detection_range_times`: 慢节点检测输入的时间范围,默认为空列表 -- `slow_node_detection_time_span_hours`: 慢节点检测的时间长度,默认为0.5小时 -- `slow_node_detection_path`: 慢节点检测结果保存路径,默认为 `/etc/systrace/result/slow_node` -- `data_type`: 算子数据的格式,默认为 `json` -- `root_path`: 算子数据的输入路径,默认为 `/home/hbdir/systrace_failslow/data/baseline` -- `enable_detect_type`: 检测不同故障类型的开关,字典格式 - - `enable_cal`: 计算慢开关,默认为true - - `enable_op_launch`: 算子下发慢开关,默认为false - - `enable_comm`: 通信慢开关,默认为false - - `enable_dataloader`: 输入模型数据加载慢开关,默认为false - - `enable_ckpt`: 模型保存慢开关,默认为false -- `fail_slow_ops`: 检测不同故障类型对应的观测点,字典格式 - - `cal_slow`: 计算慢对应的观测点,默认为 `HcclAllGather` - - `op_launch_slow`: 算子下发慢对应的观测点,默认为 `HcclAllGather_launch` - - `comm_slow`: 通信慢对应的观测点,默认为 `HcclBatchSendRecv` - - `dataloader_slow`: 输入模型数据加载慢对应的观测点,默认为 `Dataloader` - - `ckpt_slow`: 模型保存满对应的观测点,默认为 `SaveCkpt` -- `save_image`: 时序数据保存的路径,用于debug算法效果,默认为 `image` -- `record_kpi`: 时序数据是否记录到检测结果中,默认为false -- `use_plot`: 时序数据保存开关,用于debug算法效果,默认为false -- `max_num_normal_results`: 检测结果最大记录正常节点数据数量,默认为16 -- `look_back`: 告警抑制,默认为20min -- `hccl_domain`: 通信域默认配置,格式为字典,默认为 `{}`,实际配置示例 `{"tp":[[0,1,2,3], [4,5,6,7]], "dp":[[0,4], [1,5],[2,6],[3,7]]}` -- `rank_table_json`: rank_table配置文件路径,用于 mindspore 通信域配置,默认路径 `./rank_table.json` -- `debug_data`: denug模式,会保存算子执行和算子下发的中间文件,默认为false - -在文件 `metric_config.json` 中,配置所有指标的检测算法参数,每个指标独立配置。该配置项中以 `HcclAllGather` 指标配置举例,主要包含: - -- `metric_type`: 指标类型,string类型,取值 `device` 和 `host` -- `aggregation`: 指标聚合配置,字典 - - `during_s`: 聚合窗口大小, int类型,默认5s - - `funcs`: 聚合方法配置,list类型,包含元素为 dict 类型 - - `func`: 聚合方法,string类型,有 `min`, `max`, `mean`, `percentile` 等 - - `func_params`: 聚合方法配置参数,字典类型,根据不同的聚合方法配置,默认为空 -- `priority`: 指标类型,string类型,取值 `device` 和 `host` -- `aggregation`: 检测优先级,int类型 -- `alarm_filter_window_size`: 告警过滤窗口大小,表示检测出的异常点连续个数,int类型,默认值为5 -- `space_detector`: 节点间对比检测器配置,不配置为 `null` - - `dist_metric`: 节点间距离函数类型, euclidean, string类型 - - `eps`: Dbscan聚类参数的阈值,点间距离大于该值则为另一类, float类型 - - `cv_threshold`: 判断值偏离均值的程度,偏移过大则认为是异常点,float类型 - - `min_samples`: dbscan最小成新簇的点数, int类型 - - `window_size`: 窗口大小,表示单次检测的窗口,不重叠,int类型 - - `scaling`: 表示时间序列是否归一化, bool类型 - - `type`: 空间检测器类型,string类型,取值 `SlidingWindowDBSCAN`, `OuterDataDetector` -- `time_detector`: 单节点时序异常检测配置, 不配置为 `null` - - `preprocess_eps`: Dbscann预处理的阈值, float类型 - - `preprocess_min_samples`: Dbscan预处理的最小点数,int类型 - - `type`: 时间检测器类型,string类型,取值为 `TSDBSCANDetector`, `SlidingWindowKSigmaDetector` - - `n_sigma_method`: 当为 `SlidingWindowKSigmaDetector` 类型时,配置字段,dict类型 - - `type`: `SlidingWindowKSigmaDetector` 采用的检测算法,可替换扩展,string类型,默认为 `SlidingWindowNSigma` - - `training_window_size`: 滑动窗口的最大值,超过该值,覆盖已有value,int类型 - - `min_update_window_size`: 滑动窗口的最小更新值,int类型 - - `min_std_val`: 最小标准差,当标准差为0时,设置为最小标准差,float类型 - - `bias`: 边界基础上的偏置系数,float类型 - - `abs_bias`: 边界基础上的偏置值,float类型 - - `nsigma_coefficient`: Ksigam的系数,int类型 - - `detect_type`: 检测边界类型,string类型,取值为 `lower_bound`, `upper_bound`, `bi_bound` - - `min_expert_lower_bound`: 下边界最小专家阈值,null表示不设置专家阈值,int或者null类型 - - `max_expert_lower_bound`: 下边界最大专家阈值,null表示不设置专家阈值,int或者null类型 - - `min_expert_upper_bound`: 上边界最小专家阈值,null表示不设置专家阈值,int或者null类型 - - `max_expert_upper_bound`: 上边界最大专家阈值,null表示不设置专家阈值,int或者null类型 - -#### 5.2.3 Shell 客户端使用 sysTrace 运维助手对训练采集数据进行分析 - -问题需要带上目标 IP - -如:帮我分析192.168.122.196机器的AI训练情况 - -### 5.3 调优助手 - -调优助手通过采集系统、微架构、应用等维度的指标数据,结合大模型和定制化的提示词工程,针对不同应用的可调参数给出可靠的参数推荐,同时根据推荐的参数运行 benchmark,与 baseline 做对比并计算出推荐参数对应用性能的提升值。 - -仓库地址:[README.md · openEuler/A-Tune](https://gitee.com/openeuler/A-Tune/blob/euler-copilot-tune/README.md) - -| 工具名称 | 参数 | 功能描述 | -| --------- | ------ | ------------------------------------------------------------ | -| Collector | 无参数 | 采集指定机器的性能指标,包括静态指标(系统配置等)、动态指标(实时性能数据)和可选的微依赖分析数据,并将结果缓存 | -| Analyzer | 无参数 | 对已采集的数据进行分析,识别性能瓶颈,生成分析报告(需先调用 Collector) | -| Optimizer | 无参数 | 基于分析结果提供参数优化建议和策略优化方案(需先调用 Analyzer) | -| StartTune | 无参数 | 执行实际调优操作,包括参数优化和策略优化,耗时小时级,结果需通过日志查看(需先完成 Collector、Analyzer、Optimizer 流程,且仅在用户明确要求时调用),指令:journalctl -xe -u tune-mcpserver --all -f | - -#### 工具调用流程说明 - -1. **数据采集阶段**:先调用 `Collector` 工具,获取机器的静态配置和动态性能指标,数据将自动缓存。 -2. **分析阶段**:调用 `Analyzer` 工具,基于采集的数据生成性能分析报告并识别瓶颈。 -3. **优化建议阶段**:调用 `Optimizer` 工具,根据分析结果提供参数调整和策略优化方案。 -4. **执行调优阶段**:仅当用户明确要求时,调用 `StartTune` 工具执行实际调优,完成后需通过日志(`journalctl -xe -u tune-mcpserver --all -f`)查看结果。 - -#### 注意事项 - -- 工具需按顺序调用,前序工具未执行会导致后续工具报错。 -- `StartTune` 工具执行耗时较长(约 1 小时),调用时需提醒用户耐心等待。 - -#### 5.3.1 使用前需修改配置文件指定要调优的机器和调优的系统 - -在 `/etc/euler-copilot-tune/config` 目录下修改 .env .yaml 文件 - -具体格式如下: - -```bash -LLM_KEY: "YOUR_LLM_KEY" -LLM_URL: "YOUR_LLM_URL" -LLM_MODEL_NAME: "YOUR_LLM_MODEL_NAME" -LLM_MAX_TOKENS: - -REMOTE_EMBEDDING_ENDPOINT: "YOUR_EMBEDDING_MODEL_URL" -REMOTE_EMBEDDING_MODEL_NAME: "YOUR_MODEL_NAME" -``` - -```yaml -servers: - - ip: "" # 应用所在 IP - host_user: "" # 登录机器的 usr id - password: "" # 登录机器的密码 - port: # 应用所在 IP 的具体 port - app: "mysql" # 当前支持 mysql、nginx、pgsql、spark - listening_address: "" # 应用监听的IP (当前仅 flink、nginx、spark 需要填写) - listening_port: "" # 应用监听的端口 (当前仅 flink、nginx、spark 需要填写) - target_process_name: "mysqld" # 调优应用的进程名称 - business_context: "高并发数据库服务,CPU负载主要集中在用户态处理" # 调优应用的描述(用于策略生成) - max_retries: 3 - delay: 1.0 - -feature: - - need_restart_application: False # 修改参数之后是否需要重启应用使参数生效 - need_recover_cluster: False # 调优过程中是否需要恢复集群 - microDep_collector: True # 是否开启微架构指标采集 - pressure_test_mode: True # 是否通过压测模拟负载环境 - tune_system_param: False # 是否调整系统参数 - tune_app_param: True # 是否调整应用参数 - strategy_optimization: False # 是否需要策略推荐 - benchmark_timeout: 3600 # benchmark 执行超时限制 - max_iterations: 10 # 最大迭代轮数 -``` - -在 `/etc/euler-copilot-tune/config` 目录下修改 `app_config.yaml`(重点是补充 set_param_template、get_param_template、benchmark 脚本),具体内容如下: - -```yaml -mysql: - user: "root" - password: "123456" - config_file: "/etc/my.cnf" - port: 3306 - set_param_template: 'grep -q "^$param_name\\s*=" "$config_file" && sed -i "s/^$param_name\\s*=.*/$param_name = $param_value/" "$config_file" || sed -i "/\\[mysqld\\]/a $param_name = $param_value" "$config_file"' - get_param_template: 'grep -E "^$param_name\s*=" $config_file | cut -d= -f2- | xargs' - stop_workload: "systemctl stop mysqld" - start_workload: "systemctl start mysqld" - benchmark: "$EXECUTE_MODE:local sh $SCRIPTS_DIR/mysql/parse_benchmark.sh $host_ip $port $user $password" - performance_metric: "QPS" - -flink: - set_param_template: 'sh /home/wsy/set_param.sh $param_name $param_value' - get_param_template: 'sh /home/wsy/get_param.sh $param_name' - benchmark: "sh /home/wsy/nexmark_test.sh" - stop_workload: 'docker exec -i flink_jm_8c32g bash -c "source /etc/profile && /usr/local/flink-1.16.3/bin/stop-cluster.sh && /usr/local/nexmark/bin/shutdown_cluster.sh"' - start_workload: 'docker exec -i flink_jm_8c32g bash -c "source /etc/profile && /usr/local/flink-1.16.3/bin/start-cluster.sh"' - performance_metric: "THROUGHPUT" - -pgsql: - user: "postgres" - password: "postgres" - config_file: "/data/data1/pgsql/postgresql.conf" - port: 5432 - set_param_template: 'grep -qE "^\s*$param_name\s*=" "$config_file" && sed -i "s/^[[:space:]]*$param_name[[:space:]]*=.*/$param_name = $param_value/" "$config_file" || echo "$param_name = $param_value" >> "$config_file"' - get_param_template: 'grep -oP "^\s*$param_name\s*=\s*\K.*" "$config_file"' - stop_workload: "su - postgres -c '/usr/local/pgsql/bin/pg_ctl stop -D /data/data1/pgsql/ -m fast'" - start_workload: "su - postgres -c '/usr/local/pgsql/bin/pg_ctl start -D /data/data1/pgsql/ -l /var/log/postgresql/postgresql.log'" - benchmark: "$EXECUTE_MODE:local sh $SCRIPTS_DIR/postgresql/parse_benchmark.sh $host_ip $port $user $password" - performance_metric: "QPS" - -spark: - set_param_template: 'sh /path/of/set_param.sh $param_name $param_value' - get_param_template: 'sh /path/of/get_param.sh $param_name' - benchmark: "sh /path/of/spark_benchmark.sh" - performance_metric: "DURATION" - -nginx: - port: 10000 - config_file: "/usr/local/nginx/conf/nginx.conf" - set_param_template: 'grep -q "^\\s*$param_name\\s\\+" "$config_file" && sed -i "s|^\\s*$param_name\\s\\+.*| $param_name $param_value;|" "$config_file" || sed -i "/http\\s*{/a\ $param_name $param_value;" "$config_file"' - get_param_template: 'grep -E "^\\s*$param_name\\s+" $config_file | head -1 | sed -E "s/^\\s*$param_name\\s+(.*);/\\1/"' - stop_workload: "/usr/local/nginx/sbin/nginx -s reload" - start_workload: "/usr/local/nginx/sbin/nginx -s reload" - benchmark: "$EXECUTE_MODE:local sh $SCRIPTS_DIR/nginx/parse_benchmark.sh $host_ip $port" - performance_metric: "QPS" - -ceph: - set_param_template: 'ceph config set osd "$param_name" "$param_value"' - get_param_template: 'sh /path/of/get_params.sh' - start_workload: "sh /path/of/restart_ceph.sh" - benchmark: "$EXECUTE_MODE:local sh $SCRIPTS_DIR/ceph/parse_benchmark.sh" - performance_metric: "BANDWIDTH" - -gaussdb: - user: "" - password: "" - config_file: "/path/of/config_file" - port: 5432 - set_param_template: 'gs_guc set -Z datanode -N all -I all -c "${param_name}=${param_value}"' - get_param_template: 'gs_guc check -Z datanode -N all -I all -c "${param_name}"' - stop_workload: "cm_ctl stop -m i" - start_workload: "cm_ctl start" - recover_workload: "$EXECUTE_MODE:local sh /path/of/gaussdb_cluster_recover.sh" - benchmark: "$EXECUTE_MODE:local sh/path/of/gaussdb_benchmark.sh" - performance_metric: "DURATION" - -system: - set_param_template: 'sysctl -w $param_name=$param_value' - get_param_template: 'sysctl $param_name' - -redis: - port: 6379 - config_file: "/etc/redis.conf" - set_param_template: "sed -i 's/^$param_name/$param_name $param_value/g' $config_file" - get_param_template: "grep -P '$param_name' $config_file | awk '{print $2}" - start_workload: "systemctl start redis" - stop_workload: "systemctl stop redis" - benchmark: "$EXECUTE_MODE:local sh $SCRIPTS_DIR/redis/parse_benchmark.sh $host_ip $port " - performance_metric: "QPS" -``` - -其中: - -- `set_param_template`: 根据调优结果修改应用参数,用于后续测试效果 -- `get_param_template`: 获取应用参数 -- `recover_workload`: 恢复集群 -- `benchmark`: benchmark 脚本,脚本可参考 `/etc/euler-copilot-tune/scripts` 结合业务需求自定义 - -#### 5.3.2 根据调优工具进行使用,采集数据,分析,推荐参数以及开始调优 - -### 5.4 OE-core 运维助手 - -OE-Core 是 openEuler Intelligence 智能体群的核心全能型助手,定位为 “系统基础运维一站式工具集”。其整合系统监控、文件管理、进程管控三大类 MCP 服务,无需切换多工具即可完成日常运维中 80% 的基础操作,覆盖从 “系统状态查看” 到 “文件操作” 再到 “进程管理” 的全流程需求。 - -其核心价值在于打破传统运维中“系统监控用 top/free、文件操作靠 rm/tar、进程管理需 ps/kill”的工具碎片化现状,通过标准化 MCP 工具封装与结构化数据输出,实现“一站式操作+自动化适配”——既支持本地主机快速巡检,也可通过 SSH 认证对接远程集群,适配 x86_64、arm64 多架构服务器,兼容 EulerOS、CentOS、Ubuntu 等主流 Linux 发行版,满足中小规模集群及单机运维的基础需求。 - -#### 5.4.1 MCP服务矩阵 - -智能体通过 14 个标准化 MCP 工具,构建“全场景覆盖、轻量化操作”的服务体系,涵盖系统监控、文件管理、进程管控三大运维维度,具体服务与工具映射如下: - -| 服务分类 | MCP 工具名称 | 核心功能定位 | 默认端口 | -|------------------|-----------------------------|----------------------------|-----------| -| 系统综合状态洞察 | [top_mcp](#top_mcp) | 实时监控系统负载与进程状态,支持自定义采集维度 | 12110 | -| | [free_mcp](#free_mcp) | 查看系统内存使用状态 | 13100 | -| | [vmstat_mcp](#vmstat_mcp) | 采集系统资源交互瓶颈数据 | 13101 | -| | [nvidia_mcp](#nvidia_mcp) | 查询 GPU 负载与状态 | 12114 | -| 文件与目录管理 | [ls_mcp](#ls_mcp) | 查看目录内容与文件属性 | 13112 | -| | [file_content_tool_mcp](#file_content_tool_mcp) | 文件内容增删改查 | 12125 | -| | [find_mcp](#find_mcp) | 按条件搜索文件/目录 | 13107 | -| | [grep_mcp](#grep_mcp) | 搜索文件内容关键词 | 13120 | -| | [rm_mcp](#rm_mcp) | 删除文件/目录 | 13110 | -| | [mv_mcp](#mv_mcp) | 文件/目录移动或重命名 | 13111 | -| | [mkdir_mcp](#mkdir_mcp) | 创建目录(支持多级) | 13109 | -| | [tar_mcp](#tar_mcp) | 文件/目录压缩解压(tar格式)| 13118 | -| | [zip_mcp](#zip_mcp) | 文件/目录压缩解压(zip格式)| 13119 | -| 进程基础管理 | [kill_mcp](#kill_mcp) | 暂停/恢复进程,查看信号量 | 12111 | - -#### 5.4.2 使用案例 - -以下按 “系统监控、文件管理、进程管控、综合运维” 四大高频场景分类,提供不同需求粒度的 Prompt 格式,直接复制即可使用,无需额外补充技术参数,只需替换ip或者一些目标信息即可 - -##### 系统监控类场景(核心需求:查状态、判异常) - -- **案例 1**:远程单点服务器基础状态巡检(最常用场景) - - ```text - 帮我查一下 192.168.3.5 这台服务器的运行状态,要包含 CPU 负载(1/5/15分钟)、内存实际使用率(排除缓存)、GPU 显存和核心占用率,最后告诉我有没有异常 - ``` - -- **案例 2**:本地节点资源告警排查 - - ```text - 我本地机器提示“内存不足”,帮我确认一下总内存、已用内存、缓存占用多少,再看看哪些进程内存占比最高,Top3 就行 - ``` - -- **案例 3**:多节点负载对比 - - ```text - 帮我批量查一下 192.168.3.5、192.168.3.6、192.168.3.7 这三台服务器的 CPU 空闲率和磁盘根目录使用率,整理成对比结果 - ``` - -- **案例 4**:GPU 专项检查 - - ```text - 192.168.3.10 这台 GPU 服务器的训练任务卡住了,帮我看看有几块 GPU、每块的显存使用率是不是超 90%,还有哪些进程在占用 GPU 资源 - ``` - -##### 文件管理类场景(核心需求:找文件、改内容、做归档) - -- **案例 1**:远程过期日志清理 - - ```text - 192.168.3.5 的 /var/log/nginx 目录下,帮我找所有 30 天前的 .log 文件,先压缩成 tar.gz 包存到 /var/log/archive,再把原文件删掉 - ``` - -- **案例 2**:本地多文件内容修改 - - ```text - 我本地 /data/app 目录下,所有子目录里的 config.yaml 文件,帮我把“timeout: 30s”改成“timeout: 60s”,改完后确认一下修改结果 - ``` - -- **案例 3**:按条件查找大文件 - - ```text - 帮我在 192.168.3.6 的 /data 目录下,找所有大于 10GB 的 .tar 或 .zip 压缩文件,显示文件路径、大小和最后修改时间 - ``` - -- **案例 4**:文件内容关键词搜索 - - ```text - 192.168.3.5 的 /var/log/messages 文件里,帮我搜索最近 7 天包含“error”或“fail”的日志,输出匹配的行号和内容 - ``` - -##### 进程管控类场景(核心需求:查进程、解阻塞) - -- **案例 1**:本地高负载进程临时暂停 - - ```text - 我本地有个叫“data_export”的进程,现在 CPU 占比快 90% 了,先帮我查它的 PID,然后暂停这个进程,暂停后再确认一下它的状态是不是“stopped” - ``` - -- **案例 2**:远程关键进程恢复运行 - - ```text - 192.168.3.5 上有个叫“redis-server”的进程之前暂停了(PID 1234),帮我恢复它的运行,恢复后验证进程状态是否正常,还要看它的内存占比有没有异常 - ``` - -- **案例 3**:批量暂停同类临时进程 - - ```text - 我本地运行了多个叫“test_task”的临时进程,帮我先列出所有这类进程的 PID 和状态,然后把它们全部暂停,最后输出暂停成功的 PID 列表 - ``` - -- **案例 4**:按 PID 精准暂停与恢复(应急场景) - - ```text - 帮我先查看 PID 5678 的进程名称和当前状态,确认是“app_worker”后暂停它;等 10 秒后,再恢复这个 PID 5678 进程的运行,全程记录状态变化 - ``` - -- **案例 5**:常用信号量含义查询(基础认知) - - ```text - 我想了解一下进程管理里常用的几个信号量含义,帮我解释下 SIGSTOP(19)、SIGCONT(18)、SIGTERM(15)这三个信号分别是做什么的,适合在什么场景用 - ``` - -- **案例 6**:应急信号量查询(操作前确认) - - ```text - 现在要恢复一个之前被暂停的“file_sync”进程,不确定该用哪个信号量,帮我查一下“恢复暂停进程”对应的信号量名称、编号和使用注意事项 - ``` - -##### 综合运维类场景(多需求组合) - -- **案例 1**:服务器初始化检查 - - ```text - 新部署的 192.168.3.20 服务器,帮我做个初始化检查:1. 查 CPU 核心数、内存总大小;2. 看 /data 目录是否存在,不存在就创建;3. 确认 sshd 进程是否在运行 - ``` - -- **案例 2**:应用部署前环境确认 - - ```text - 要在 192.168.3.8 部署 Java 应用,帮我确认:1. 内存空闲是否超 16GB;2. /opt/app 目录是否有写权限;3. 有没有占用 8080 端口的进程 - ``` - -- **案例 3**:故障应急排查 - - ```text - 192.168.3.9 的 nginx 服务没响应,帮我排查:1. nginx 进程是否存活;2. CPU 和内存有没有满;3. /var/log/nginx/error.log 里最近 10 条错误日志是什么 - ``` - -### 5.5 OE-PerfDoctor 性能诊断医师 - -OE-PerfDoctor 是 openEuler Intelligence 体系下的**系统级性能智能诊断智能体**,专注于服务器集群、关键业务应用的性能瓶颈定位与优化建议生成。其核心能力在于整合底层硬件分析、内存访问监控、CPU 性能剖析及高级诊断工具链,通过“自动化工具调用+多维度数据联动分析+可视化结果呈现”的流程,替代传统人工性能调优中的重复性操作,大幅提升性能问题排查效率。 - -OE-PerfDoctor 已集成四大类核心性能分析服务,覆盖从硬件基线到应用层瓶颈的全链路诊断,支持 x86_64、arm64 多架构服务器,适配 EulerOS、CentOS、Ubuntu 等主流 Linux 发行版。 - -#### 5.5.1 MCP服务矩阵 - -OE-PerfDoctor是 openEuler Intelligence 中的性能深度诊断专家,专注于系统级性能问题的定位和优化。该智能体通过 10 个标准化 MCP 工具,构建 “硬件 - 系统 - 应用” 全链路的性能诊断体系,涵盖 NUMA 架构分析、CPU 性能剖析、系统调用与中断诊断三大维度,具体服务与工具映射如下: - -| 服务分类 | MCP 工具名称 | 核心功能定位 | 默认端口 | -|------------------------|-----------------------------|------------------------------------------------|-----------| -| NUMA 架构分析与优化 | [lscpu_mcp](#lscpu_mcp) | 采集 CPU 静态架构信息,为 NUMA 分析提供硬件基线 | 12202 | -| | [numa_topo_mcp](#numa_topo_mcp) | 解析 NUMA 节点分布、CPU 与内存绑定关系 | 12203 | -| | [numastat_mcp](#numastat_mcp) | 监控 NUMA 节点内存访问状态,识别分配不均衡问题 | 12210 | -| | [numa_cross_node_mcp](#numa_cross_node_mcp) | 定位跨 NUMA 节点访问的进程,量化性能损耗 | 12211 | -| CPU 性能剖析 | [hotspot_trace_mcp](#hotspot_trace_mcp) | 跟踪 CPU 热点进程/函数,识别高负载代码段 | 12216 | -| | [cache_miss_audit_mcp](#cache_miss_audit_mcp) | 审计 CPU 缓存(L1/L2/L3)未命中率,定位缓存损耗 | 12217 | -| | [flame_graph_mcp](#flame_graph_mcp) | 生成 CPU 耗时火焰图,可视化函数调用栈性能瓶颈 | 12222 | -| | [func_timing_trace_mcp](#func_timing_trace_mcp) | 分析函数级执行耗时,定位慢函数 | 12218 | -| 系统调用与中断诊断 | [strace_syscall_mcp](#strace_syscall_mcp) | 统计进程系统调用频率与耗时,定位 I/O 瓶颈 | 12219 | -| | [perf_interrupt_mcp](#perf_interrupt_mcp) | 定位高频中断源,识别中断导致的 CPU 资源争抢 | 12220 | - -#### 5.5.2 使用案例 - -以下按 “NUMA 内存问题诊断、CPU 热点定位、系统调用瓶颈排查” 三大高频性能场景分类,提供自然语言交互 Prompt 格式,直接替换 IP、进程名等关键信息即可使用,无需额外补充技术参数。 - -- 场景 1:NUMA 内存分配不均衡诊断(数据库性能下降) - - ```text - 192.168.4.10 这台数据库服务器查询延迟突增,帮我分析是不是 NUMA 内存问题:1. 查一下 NUMA 节点分布和各节点内存使用率;2. 看看有没有进程跨节点访问内存,占比多少;3. 最后给优化建议 - ``` - -- 场景 2:CPU 高负载根因定位(应用响应慢) - - ```text - 我本地运行的“order-service”应用 CPU 一直占 85% 以上,帮我定位瓶颈:1. 找出 CPU 热点函数;2. 查一下 L3 缓存未命中率是不是超标;3. 生成火焰图看看函数调用栈哪里耗时最多 - ``` - -- 场景 3:系统调用瓶颈排查(I/O 密集型应用卡顿) - - ```text - 192.168.4.15 的“file-transfer”应用传输文件时卡顿,帮我查:1. 这个进程的系统调用里,哪些调用频率高、耗时久;2. 有没有高频中断占用 CPU;3. 总结卡顿的核心原因 - ``` - -- 场景 4:综合性能诊断(集群节点性能差异) - - ```text - 帮我对比 192.168.4.20 和 192.168.4.21 两台节点的性能差异:1. 查 CPU 架构和 NUMA 拓扑是否一致;2. 对比相同“data-process”进程的 CPU 热点和缓存命中率;3. 指出导致性能差异的关键因素 - ``` - -### 5.6 OE-高级运维工程师 - -OE-AdvOps 是 openEuler Intelligence 智能体群中的专精型高级运维工具,定位为“复杂运维场景解决方案提供者”。其聚焦企业级服务器的进阶运维需求,整合进程高级控制、系统资源深度监控、内存与交换空间管理三大核心能力,通过标准化 MCP 工具封装,替代传统“命令拼接 + 人工判断” 的低效运维模式,实现复杂操作的自动化与可追溯,适配中大规模集群的高级运维场景。 - -其核心价值在于解决传统运维中 “进程后台启动靠 nohup 手动记录、资源瓶颈诊断需 sar/vmstat 多工具拼接、swap 管理风险高” 的痛点,支持本地与远程(SSH 认证)双模式操作,适配 x86_64、arm64 多架构服务器,兼容 EulerOS、CentOS、Ubuntu 等主流 Linux 发行版,可满足 “进程精细化管控、系统资源深度诊断、内存交换空间安全管理” 的高阶需求。 - -#### 5.6.1 MCP服务矩阵 - -OE-AdvOps 智能体通过 9 个标准化 MCP 工具,构建 “进程 - 资源 - 内存” 全维度的高级运维体系,涵盖进程高级控制、系统资源监控与诊断、内存与交换空间管理三大维度,具体服务与工具映射如下: - -| 服务分类 | MCP 工具名称 | 核心功能定位 | 默认端口 | -|------------------------|-----------------------------|------------------------------------------------|-----------| -| 进程高级控制 | [nohup_mcp](#nohup_mcp) | 后台启动进程并记录输出日志,避免会话断开中断 | 12301 | -| | [strace_mcp](#strace_mcp) | 跟踪进程系统调用与信号,分析进程异常原因 | 12302 | -| | [kill_mcp](#kill_mcp) | 精细化控制进程(暂停/恢复/发送指定信号),查看进程状态 | 12111 | -| | [top_mcp](#top_mcp) | 实时监控进程资源占用(CPU/内存),识别高负载进程 | 12110 | -| 系统资源监控与诊断 | [sar_mcp](#sar_mcp) | 采集系统历史/实时资源数据(CPU/内存/I/O),生成诊断报告 | 12303 | -| | [vmstat_mcp](#vmstat_mcp) | 监控系统内存交换、I/O 等待、CPU 上下文切换,定位资源瓶颈 | 13101 | -| 内存与交换空间管理 | [sync_mcp](#sync_mcp) | 同步系统缓冲区数据到磁盘,避免数据丢失 | 12304 | -| | [swapon_mcp](#swapon_mcp) | 启用 swap 分区/文件,查看当前 swap 使用状态 | 13104 | -| | [swapoff_mcp](#swapoff_mcp) | 安全停用 swap 分区/文件,释放交换空间 | 13105 | -| | [fallocate_mcp](#fallocate_mcp) | 预分配文件空间(常用于创建 swap 文件),指定大小与路径 | 12305 | - -#### 5.6.2 使用案例 - -以下按 “进程高级管控、系统资源深度诊断、内存与交换空间管理” 三大高频高级运维场景分类,提供自然语言交互 Prompt 格式,直接替换 IP、进程名、路径等关键信息即可使用: - -- 场景 1:进程后台启动与异常跟踪 - - ```text - 帮我在 192.168.5.10 上后台启动“data_sync.sh”脚本,日志输出到 /var/log/data_sync.log;启动后用 top_mcp 监控它的内存占用,要是超过 50% 就用 strace_mcp 跟踪它的系统调用,查异常原因 - ``` - -- 场景 2:系统资源历史瓶颈诊断 - - ```text - 192.168.5.15 昨天 14:00-16:00 出现 CPU 负载突增,帮我用 sar_mcp 查这段时间的 CPU 使用率(用户态/系统态)、内存交换情况,再用 vmstat_mcp 看当时的 I/O 等待时间,总结负载突增的原因 - ``` - -- 场景 3:swap 文件创建与安全管理 - - ```text - 我本地服务器内存不足,帮我创建一个 20G 的 swap 文件:1. 用 fallocate_mcp 在 /data/swapfile 预分配 20G 空间;2. 启用这个 swap 文件(用 swapon_mcp);3. 最后查看当前 swap 总容量和使用率 - ``` - -- 场景 4:进程异常恢复与数据保护 - - ```text - 192.168.5.20 上的“db_backup”进程(PID 6789)无响应,帮我:1. 用 kill_mcp 发送 SIGSTOP 暂停进程;2. 用 sync_mcp 强制同步缓冲区数据;3. 尝试发送 SIGCONT 恢复进程,恢复失败就输出重启建议 - ``` - -### 5.7 OE-文件系统专家 - -OE-FileMaster 是 openEuler Intelligence 中的专精型文件系统管理工具,定位为 “全场景文件操作一站式解决方案”。其聚焦文件系统的全生命周期管理,整合高级查找与内容处理、权限与所有权管控、文件创建与查看三大核心能力,覆盖从 “文件创建” 到 “内容编辑” 再到 “权限配置” 的全流程操作,替代传统运维中 “多命令切换 + 手动校验” 的低效模式,实现文件操作的标准化与自动化。 - -其核心价值在于解决传统文件管理中 “高级查找靠 find 复杂参数拼接、内容修改需 grep+sed 组合、权限配置易因参数错误导致安全风险” 的痛点,支持本地与远程(SSH 认证)双模式操作,适配 x86_64、arm64 多架构服务器,兼容 EulerOS、CentOS、Ubuntu 等主流 Linux 发行版,可满足 “日常文件运维、批量内容处理、权限安全管控” 的精细化需求。 - -#### 5.7.1 MCP服务矩阵 - -OE-FileMaster 智能体通过 14 个标准化 MCP 工具,构建 “查找 - 处理 - 管控 - 查看” 全维度的文件系统管理体系,涵盖高级查找与内容处理、权限与所有权管理、文件创建与查看三大维度,具体服务与工具映射如下: - -| 服务分类 | MCP 工具名称 | 核心功能定位 | 默认端口 | -|------------------------|-----------------------------|------------------------------------------------|-----------| -| 高级查找与内容处理 | [find_mcp](#find_mcp) | 按路径/名称/大小/修改时间等多条件查找文件/目录 | 13107 | -| | [grep_mcp](#grep_mcp) | 按关键词搜索文件内容,支持多文件批量匹配 | 13120 | -| | [sed_mcp](#sed_mcp) | 批量替换文件内容(支持正则),处理结构化文本 | 13201 | -| | [file_content_tool_mcp](#file_content_tool_mcp) | 全功能文件内容管理(读/写/改/删),支持大文件分段操作 | 12125 | -| 权限与所有权管理 | [chmod_mcp](#chmod_mcp) | 配置文件/目录权限(数字/符号格式),保障访问安全 | 13202 | -| | [chown_mcp](#chown_mcp) | 修改文件/目录所有者与所属组,管控资源归属 | 13203 | -| 文件创建与查看 | [touch_mcp](#touch_mcp) | 创建空文件/更新文件修改时间,初始化文件资源 | 13204 | -| | [cat_mcp](#cat_mcp) | 读取完整文件内容,支持文本/配置文件快速查看 | 13205 | -| | [head_mcp](#head_mcp) | 查看文件前 N 行内容,快速定位文件头部信息 | 13206 | -| | [tail_mcp](#tail_mcp) | 查看文件后 N 行/实时跟踪文件更新(tail -f) | 13207 | -| | [echo_mcp](#echo_mcp) | 向文件写入文本内容(覆盖/追加模式),快速生成简单文件 | 13208 | -| | [ls_mcp](#ls_mcp) | 查看目录内容与文件属性(权限/大小/修改时间) | 13112 | -| | [rm_mcp](#rm_mcp) | 安全删除文件/目录(支持递归),清理无效资源 | 13110 | -| | [mv_mcp](#mv_mcp) | 移动文件/目录或重命名,调整资源存储路径 | 13111 | - -#### 5.7.2 使用案例 - -以下按 “高级内容处理、权限安全管控、日常文件运维” 三大高频文件管理场景分类,提供自然语言交互 Prompt 格式,直接替换 IP、文件路径、关键词等关键信息即可使用: - -- 场景 1:批量文件内容替换(配置更新) - - ```text - 192.168.6.10 的 /data/apps 目录下,所有子目录的 config.ini 文件里,帮我把“server_ip = 10.0.0.1”改成“server_ip = 10.0.0.2”,改完后用 grep_mcp 验证替换结果 - ``` - -- 场景 2:文件权限与所有权修复(安全管控) - - ```text - 本地 /var/www/html 目录下的网站文件,现在普通用户也能修改,帮我处理:1. 用 chmod_mcp 把所有 .php 文件权限设为 644,目录设为 755;2. 用 chown_mcp 把所有者改成 www 用户和 www 组,递归处理所有子文件 - ``` - -- 场景 3:日志文件高级处理(运维分析) - - ```text - 帮我处理 192.168.6.15 的 /var/log/nginx/access.log:1. 用 find_mcp 找出去年 12 月的日志文件;2. 用 grep_mcp 统计这些日志里包含“/api/login”的请求行数;3. 用 tail_mcp 实时跟踪当前日志的最新 20 行 - ``` - -- 场景 4:文件创建与内容初始化(资源准备) - - ```text - 要在 192.168.6.20 部署脚本,帮我:1. 用 touch_mcp 在 /opt/scripts 下创建 start.sh 和 stop.sh;2. 用 echo_mcp 向 start.sh 写入“#!/bin/bash\n/opt/app/start”(追加模式);3. 用 chmod_mcp 给两个脚本设为 700 权限 - ``` - -### 5.8 NUMA专精专家 - -OE-NUMAExpert 是 openEuler Intelligence 中的专精型硬件优化工具,定位为 “NUMA 架构全场景优化与诊断解决方案”。其聚焦多 CPU 服务器的 NUMA(非均匀内存访问)架构特性,整合进程 NUMA 绑定、Docker 容器 NUMA 控制、NUMA 性能测试与硬件诊断四大核心能力,解决传统 NUMA 优化中 “手动绑定效率低、容器管控难、性能差异无量化” 的痛点,最大化发挥多架构服务器的硬件性能。 - -其核心价值在于通过标准化 MCP 工具,实现 “NUMA 拓扑可视化→进程 / 容器精准绑定→性能对比测试→硬件问题诊断” 的全流程自动化,支持本地与远程(SSH 认证)双模式操作,适配 x86_64、arm64 多架构服务器,兼容 EulerOS、CentOS、Ubuntu 等主流 Linux 发行版,可满足 “高性能计算、数据库集群、AI 训练” 等对硬件资源敏感场景的 NUMA 优化需求。 - -#### 5.8.1 MCP服务矩阵 - -OE-NUMAExpert 智能体通过 8 个标准化 MCP 工具,构建 “拓扑分析 - 绑定控制 - 性能测试 - 诊断优化” 全维度的 NUMA 硬件管理体系,涵盖 NUMA 拓扑与状态监控、进程 / 容器 NUMA 绑定、NUMA 性能对比与诊断三大维度,具体服务与工具映射如下: - -| 服务分类 | MCP 工具名称 | 核心功能定位 | 默认端口 | -|------------------------|-----------------------------|------------------------------------------------|-----------| -| NUMA 拓扑与状态监控 | [numa_topo_mcp](#numa_topo_mcp) | 解析 NUMA 节点分布、CPU 核心归属与内存关联关系,生成拓扑图 | 12203 | -| | [numastat_mcp](#numastat_mcp) | 实时监控各 NUMA 节点内存访问量、本地/跨节点访问占比,识别不均衡问题 | 12210 | -| 进程/容器 NUMA 绑定 | [numa_bind_proc_mcp](#numa_bind_proc_mcp) | 进程启动时绑定到指定 NUMA 节点,避免跨节点内存访问 | 12401 | -| | [numa_rebind_proc_mcp](#numa_rebind_proc_mcp) | 对运行中进程重新绑定 NUMA 节点,动态调整资源分配 | 12402 | -| | [numa_bind_docker_mcp](#numa_bind_docker_mcp) | 启动 Docker 容器时绑定 NUMA 节点,管控容器硬件资源 | 12403 | -| | [numa_container_mcp](#numa_container_mcp) | 查看/修改已运行 Docker 容器的 NUMA 绑定配置,动态优化 | 12404 | -| NUMA 性能对比与诊断 | [numa_perf_compare_mcp](#numa_perf_compare_mcp) | 对比不同 NUMA 绑定策略下的性能数据(延迟/吞吐量),生成优化建议 | 12405 | -| | [numa_diagnose_mcp](#numa_diagnose_mcp) | 诊断 NUMA 相关硬件问题(节点故障、内存访问异常),输出修复方案 | 12406 | - -#### 5.8.2 使用案例 - -以下按 “进程 NUMA 优化、Docker 容器 NUMA 管控、NUMA 性能测试与诊断” 三大高频硬件优化场景分类,提供自然语言交互 Prompt 格式,直接替换 IP、进程 ID、节点 ID 等关键信息即可使用: - -- 场景 1:数据库进程 NUMA 绑定(性能优化) - - ```text - 192.168.7.10 上的 MySQL 进程(PID 3456)跨 NUMA 节点访问导致延迟高,帮我:1. 用 numa_topo_mcp 查该服务器的 NUMA 节点分布;2. 把 MySQL 进程重新绑定到 NUMA 节点 0;3. 用 numastat_mcp 验证跨节点访问占比是否下降 - ``` - -- 场景 2:Docker 容器 NUMA 管控(资源隔离) - - ```text - 要在 192.168.7.15 启动一个 AI 训练容器(镜像 tensorflow:latest),帮我:1. 启动时用 numa_bind_docker_mcp 绑定到 NUMA 节点 1;2. 启动后用 numa_container_mcp 确认绑定是否生效;3. 限制容器只使用节点 1 的 CPU 和内存 - ``` - -- 场景 3:NUMA 绑定策略性能对比(方案选型) - - ```text - 帮我在本地服务器测试不同 NUMA 绑定对“data-process”程序的影响:1. 分别绑定到 NUMA 节点 0、节点 1、不绑定;2. 用 numa_perf_compare_mcp 对比三种场景下的处理延迟和吞吐量;3. 推荐最优绑定策略 - ``` - -- 场景 4:NUMA 硬件问题诊断(故障排查) - - ```text - 192.168.7.20 服务器近期内存访问延迟突然升高,怀疑是 NUMA 节点问题,帮我:1. 用 numa_diagnose_mcp 检查所有 NUMA 节点和内存状态;2. 用 numastat_mcp 看各节点跨访问占比;3. 输出问题根因和修复建议 - ``` - -### 5.9 MCP 总览 - -当前已集成 **30+ 核心功能模块**,能力覆盖运维全场景,具体包含七大方向: - -1. 硬件信息采集:支持 CPU 架构解析、NUMA 拓扑查询、GPU 负载监控,为底层资源分析提供数据基线; -2. 系统资源监控:实时采集内存使用状态、CPU 负载变化、网络流量数据,动态捕捉资源瓶颈; -3. 进程与服务管控:实现进程启停控制、信号量含义查询、后台进程稳定执行,保障服务运行可控; -4. 文件操作管理:覆盖文件增删改查、压缩解压(tar/zip 格式)、权限与所有权配置,满足文件全生命周期需求; -5. 性能诊断优化:**内置火焰图生成能力**(基于系统原生 `perf` 工具封装)、系统调用排查、CPU 缓存失效定位,无需额外部署独立性能分析工具,即可助力深度性能调优; -6. 虚拟化与容器辅助:支持 Docker 容器 NUMA 绑定、QEMU 虚拟机管理,适配虚拟化运维场景; -7. 网络扫描探测:可执行 IP/网段探测、端口识别,快速完成网络基础巡检。 - -上述能力仅依赖系统原生基础工具(如 `perf`),无需额外部署第三方独立运维套件,即可满足从基础运维到深度性能优化的全流程需求。 - -在部署与迭代层面,具备两大核心优势: - -- **双模式适配**:支持本地直接调用与远程 SSH 管控,兼顾单机运维与多节点集群管理场景; -- **高稳定性与可扩展性**:单个模块的升级、维护不影响整体运行;开源特性允许社区开发者参与功能迭代与 Bug 修复,持续丰富模块能力,适配更多新兴运维场景。 - -#### 5.9.1 MCP_Server列表 - -| 端口号 | 服务名称 | 目录路径 | 简介 | -|--------|------------------------------|-----------------------------------------------|-------------------------------------------------| -| 12100 | [remote_info_mcp](#remote_info_mcp) | mcp_center/servers/remote_info_mcp | 获取端点信息 | -| 12101 | [shell_generator_mcp](#shell_generator_mcp) | mcp_center/servers/shell_generator_mcp | 生成&执行shell命令 | -| 12110 | [top_mcp](#top_mcp) | mcp_center/servers/top_mcp | 获取系统负载信息 | -| 12111 | [kill_mcp](#kill_mcp) | mcp_center/servers/kill_mcp | 控制进程&查看进程信号量含义 | -| 12112 | [nohup_mcp](#nohup_mcp) | mcp_center/servers/nohup_mcp | 后台执行进程 | -| 12113 | [strace_mcp](#strace_mcp) | mcp_center/servers/strace_mcp | 跟踪进程信息,可以用于异常情况分析 | -| 12114 | [nvidia_mcp](#nvidia_mcp) | mcp_center/servers/nvidia_mcp | GPU负载信息查询 | -| 12125 | [file_content_tool_mcp](#file_content_tool_mcp) | mcp_center/servers/file_content_tool_mcp | 文件内容增删改查 | -| 12145 | [systrace_mcpserver_mcp](#systrace_mcpserver_mcp) | mcp_center/servers/systrace/systrace_mcpserver_mcp | 开启MCP Server服务 | -| 12146 | [ssystrace_openapi_mcp](#ssystrace_openapi_mcp) | mcp_center/servers/systrace/ssystrace_openapi_mcp | 开启OpenAPI Server服务 | -| 12147 | [euler_copilot_tune_mcp](#euler_copilot_tune_mcp) | mcp_center/servers/euler_copilot_tune_mcp | 调优MCP服务 | -| 12202 | [lscpu_mcp](#lscpu_mcp) | mcp_center/servers/lscpu_mcp | CPU架构等静态信息收集 | -| 12203 | [numa_topo_mcp](#numa_topo_mcp) | mcp_center/servers/numa_topo_mcp | 查询 NUMA 硬件拓扑与系统配置 | -| 12204 | [numa_bind_proc_mcp](#numa_bind_proc_mcp) | mcp_center/servers/numa_bind_proc_mcp | 启动时绑定进程到指定 NUMA 节点 | -| 12205 | [numa_rebind_proc_mcp](#numa_rebind_proc_mcp) | mcp_center/servers/numa_rebind_proc_mcp | 修改已启动进程的 NUMA 绑定 | -| 12206 | [numa_bind_docker_mcp](#numa_bind_docker_mcp) | mcp_center/servers/numa_bind_docker_mcp | 为 Docker 容器配置 NUMA 绑定 | -| 12208 | [numa_perf_compare_mcp](#numa_perf_compare_mcp) | mcp_center/servers/numa_perf_compare_mcp | 用 NUMA 绑定控制测试变量 | -| 12209 | [numa_diagnose_mcp](#numa_diagnose_mcp) | mcp_center/servers/numa_diagnose_mcp | 用 NUMA 绑定定位硬件问题 | -| 12210 | [numastat_mcp](#numastat_mcp) | mcp_center/servers/numastat_mcp | 查看系统整体 NUMA 内存访问状态 | -| 12211 | [numa_cross_node_mcp](#numa_cross_node_mcp) | mcp_center/servers/numa_cross_node_mcp | 定位跨节点内存访问过高的进程 | -| 12214 | [numa_container_mcp](#numa_container_mcp) | mcp_center/servers/numa_container_mcp | 监控 Docker 容器的 NUMA 内存访问 | -| 12216 | [hotspot_trace_mcp](#hotspot_trace_mcp) | mcp_center/servers/hotspot_trace_mcp | 快速定位系统 / 进程的 CPU 性能瓶颈 | -| 12217 | [cache_miss_audit_mcp](#cache_miss_audit_mcp) | mcp_center/servers/cache_miss_audit_mcp | 定位 CPU 缓存失效导致的性能损耗 | -| 12218 | [func_timing_trace_mcp](#func_timing_trace_mcp) | mcp_center/servers/func_timing_trace_mcp | 精准测量函数执行时间(含调用栈) | -| 12219 | [strace_syscall_mcp](#strace_syscall_mcp) | mcp_center/servers/strace_syscall_mcp | 排查不合理的系统调用(高频 / 耗时) | -| 12220 | [perf_interrupt_mcp](#perf_interrupt_mcp) | mcp_center/servers/perf_interrupt_mcp | 定位高频中断导致的 CPU 占用 | -| 12222 | [flame_graph_mcp](#flame_graph_mcp) | mcp_center/servers/flame_graph_mcp | 火焰图生成:可视化展示性能瓶颈 | -| 13100 | [free_mcp](#free_mcp) | mcp_center/servers/free_mcp | 获取系统内存整体状态 | -| 13101 | [vmstat_mcp](#vmstat_mcp) | mcp_center/servers/vmstat_mcp | 系统资源交互瓶颈信息采集 | -| 13102 | [sar_mcp](#sar_mcp) | mcp_center/servers/sar_mcp | 系统资源监控与故障诊断 | -| 13103 | [sync_mcp](#sync_mcp) | mcp_center/servers/sync_mcp | 内存缓冲区数据写入磁盘 | -| 13104 | [swapon_mcp](#swapon_mcp) | mcp_center/servers/swapon_mcp | 查看swap设备状态 | -| 13105 | [swapoff_mcp](#swapoff_mcp) | mcp_center/servers/swapoff_mcp | swap设备停用 | -| 13106 | [fallocate_mcp](#fallocate_mcp) | mcp_center/servers/fallocate_mcp | 临时创建并启用swap文件 | -| 13107 | [find_mcp](#find_mcp) | mcp_center/servers/find_mcp | 文件查找 | -| 13108 | [touch_mcp](#touch_mcp) | mcp_center/servers/touch_mcp | 文件创建与时间校准 | -| 13109 | [mkdir_mcp](#mkdir_mcp) | mcp_center/servers/mkdir_mcp | 文件夹创建 | -| 13110 | [rm_mcp](#rm_mcp) | mcp_center/servers/rm_mcp | 文件删除 | -| 13111 | [mv_mcp](#mv_mcp) | mcp_center/servers/mv_mcp | 文件移动或重命名 | -| 13112 | [ls_mcp](#ls_mcp) | mcp_center/servers/ls_mcp | 查看目录内容 | -| 13113 | [head_mcp](#head_mcp) | mcp_center/servers/head_mcp | 文件开头内容查看工具 | -| 13114 | [tail_mcp](#tail_mcp) | mcp_center/servers/tail_mcp | 文件末尾内容查看工具 | -| 13115 | [cat_mcp](#cat_mcp) | mcp_center/servers/cat_mcp | 文件内容查看工具 | -| 13116 | [chown_mcp](#chown_mcp) | mcp_center/servers/chown_mcp | 文件所有者修改工具 | -| 13117 | [chmod_mcp](#chmod_mcp) | mcp_center/servers/chmod_mcp | 文件权限修改工具 | -| 13118 | [tar_mcp](#tar_mcp) | mcp_center/servers/tar_mcp | 文件压缩解压工具 | -| 13119 | [zip_mcp](#zip_mcp) | mcp_center/servers/zip_mcp | 文件压缩解压工具 | -| 13120 | [grep_mcp](#grep_mcp) | mcp_center/servers/grep_mcp | 文件内容搜索工具 | -| 13121 | [sed_mcp](#sed_mcp) | mcp_center/servers/sed_mcp | 文本处理工具 | -| 13125 | [echo_mcp](#echo_mcp) | mcp_center/servers/echo_mcp | 文本写入工具 | - -#### 5.9.2 MCP_Server 详情 - -本部分将针对核心MCP服务模块展开详细说明,通过“服务-工具-功能-参数-返回值”的结构化表格,清晰呈现每个MCP_Server的具体能力:包括其包含的工具列表、各工具的核心作用、调用时需传入的关键参数,以及执行后返回的结构化数据格式。旨在为运维人员提供“即查即用”的操作指南,确保能快速理解服务功能、正确配置参数、高效解析返回结果,满足日常运维、性能分析与故障排查的实际需求。 - -##### remote_info_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|-----------------|-----------------------|--------------------------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| | top_collect_tool | 获取目标设备(本地/远程)中**内存占用排名前k个**的进程信息,k支持自定义配置 | - `host`:远程主机名/IP(本地采集可不填)
- `k`:需获取的进程数量(默认5) | 进程列表(含`pid`进程ID、`name`进程名称、`memory`内存使用量(MB)) | -| | get_process_info_tool | 查询指定PID进程的**详细运行信息**,支持本地与远程进程信息获取 | - `host`:远程主机名/IP(本地查询可不填)
- `pid`:需查询的进程ID(必传,且为正整数) | 进程详细字典(含`status`状态、`create_time`创建时间、`cpu_times`CPU时间、`memory_info`内存信息、`open_files`打开文件列表、`connections`网络连接等) | -| | change_name_to_pid_tool | 根据进程名称反向查询对应的**PID列表**,解决“已知进程名查ID”的场景需求 | - `host`:远程主机名/IP(本地查询可不填)
- `name`:需查询的进程名称(必传,不能为空) | 以空格分隔的PID字符串(如“1234 5678”) | -| **remote_info_mcp** | get_cpu_info_tool | 采集目标设备的CPU硬件与使用状态信息,包括核心数、频率、核心使用率 | - `host`:远程主机名/IP(本地采集可不填) | CPU信息字典(含`physical_cores`物理核心数、`total_cores`逻辑核心数、`max_frequency`最大频率(MHz)、`cpu_usage`各核心使用率(%)等) | -| | memory_anlyze_tool | 分析目标设备的内存使用情况,计算总内存、可用内存及使用率 | - `host`:远程主机名/IP(本地采集可不填) | 内存信息字典(含`total`总内存(MB)、`available`可用内存(MB)、`used`已用内存(MB)、`percent`内存使用率(%)等) | -| | get_disk_info_tool | 采集目标设备的磁盘分区信息与容量使用状态,过滤临时文件系统(tmpfs/devtmpfs) | - `host`:远程主机名/IP(本地采集可不填) | 磁盘列表(含`device`设备名、`mountpoint`挂载点、`fstype`文件系统类型、`total`总容量(GB)、`percent`磁盘使用率(%)等) | -| | get_os_info_tool | 获取目标设备的操作系统类型与版本信息,适配OpenEuler、Ubuntu、CentOS等多系统 | - `host`:远程主机名/IP(本地采集可不填) | 操作系统信息字符串(如“OpenEuler 22.03 LTS”或“Ubuntu 20.04.5 LTS”) | -| | get_network_info_tool | 采集目标设备的网络接口信息,包括IP地址、MAC地址、接口启用状态 | - `host`:远程主机名/IP(本地采集可不填) | 网络接口列表(含`interface`接口名、`ip_address`IP地址、`mac_address`MAC地址、`is_up`接口是否启用(布尔值)等) | -| | write_report_tool | 将系统信息分析结果写入本地报告文件,自动生成带时间戳的文件路径 | - `report`:报告内容字符串(必传,不能为空) | 报告文件路径字符串(如“/reports/system_report_20240520_153000.txt”) | -| | telnet_test_tool | 测试目标主机指定端口的Telnet连通性,验证端口开放状态 | - `host`:远程主机名/IP(必传)
- `port`:端口号(1-65535,必传) | 连通性结果(布尔值:`True`成功,`False`失败) | -| | ping_test_tool | 测试目标主机的ICMP Ping连通性,验证主机网络可达性 | - `host`:远程主机名/IP(必传) | 连通性结果(布尔值:`True`成功,`False`失败) | -| | get_dns_info_tool | 采集目标设备的DNS配置信息,包括DNS服务器列表与搜索域 | - `host`:远程主机名/IP(本地采集可不填) | DNS信息字典(含`nameservers`DNS服务器列表、`search`搜索域列表) | -| | perf_data_tool | 采集目标设备的实时性能数据,支持“指定进程”或“全系统”性能监控 | - `host`:远程主机名/IP(本地采集可不填)
- `pid`:进程ID(全系统监控可不填) | 性能数据字典(含`cpu_usage`CPU使用率(%)、`memory_usage`内存使用率(%)、`io_counters`I/O统计信息) | - ---- - -##### shell_generator_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|------------------|-----------------------|--------------------------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| **shell_generator** | cmd_generator_tool | 1. 系统信息采集:指定`host`则通过SSH获取远程主机信息(系统发行版、运行时间、根分区/内存使用、Top5内存进程),不指定则采集本机信息;2. LLM命令生成:将系统信息与用户需求传入大语言模型,生成符合场景的Linux shell命令;3. 格式校验:提取LLM返回的YAML格式命令块,输出有效命令字符串 | - `host`(可选):远程主机名/IP,需提前在配置文件配置主机IP、端口、用户名、密码,不提供则操作本机
- `goal`(必填):用户运维需求描述(如“查询根分区使用率”“查看内存占用最高的3个进程”) | 符合场景的Linux shell命令字符串(经格式校验后的有效命令) | -| | cmd_executor_tool | 1. 多场景命令执行:支持本地或远程主机执行shell命令;2. 远程执行:通过SSH连接远程主机(基于配置文件信息),执行命令并捕获标准输出/错误输出,执行后关闭连接;3. 本地执行:通过`subprocess`模块执行命令,返回结果;4. 错误处理:命令执行出错(权限不足、命令不存在等)时,返回具体错误信息 | - `host`(可选):远程主机名/IP,需与配置文件信息匹配,不提供则操作本机
- `command`(必填):需执行的Linux shell命令字符串(建议由`cmd_generator_tool`生成) | 1. 命令执行成功:返回命令标准输出内容;2. 命令执行失败:返回具体错误信息(如“权限不足:Permission denied”“命令不存在:command not found”) | - ---- - -##### top_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| **top_mcp** | top_collect_tool | 获取目标设备(本地/远程)中**内存占用排名前k个**的进程信息,k支持自定义配置 | - `host`:远程主机名/IP(本地采集可不填)
- `k`:需获取的进程数量(默认5) | 进程列表(含`pid`进程ID、`name`进程名称、`memory`内存使用量(MB)) | -| | top_servers_tool | 通过`top`命令获取指定目标(本地/远程服务器)的负载信息,涵盖CPU、内存、磁盘、网络及进程状态,为运维、性能分析和故障排查提供数据支持 | - `host`:远程主机名/IP(本地采集可不填)
- `dimensions`:需采集的维度(可选值:cpu、memory、disk、network)
- `include_processes`:是否包含进程信息(布尔值)
- `top_n`:需返回的进程数量(整数) | - `server_info`:服务器基本信息
- `metrics`:请求维度的统计结果(如CPU使用率、内存占用率)
- `processes`:进程列表(仅`include_processes`=True时返回)
- `error`:错误信息(如连接失败,无错误则为null) | - ---- - -##### kill_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| | pause_process | 通过`kill`指令发送`SIGSTOP`信号暂停进程(支持本地/远程) | - `pid`:需暂停的进程PID(正整数,必填)
- `host`:远程主机名/IP(默认`localhost`,本地操作可不填)
- `port`:SSH端口(默认22,远程操作时使用)
- `username`:SSH用户名(默认`root`,远程操作时需指定)
- `password`:SSH密码(远程操作时必填) | - `success`:操作是否成功(布尔值)
- `message`:操作结果描述(字符串)
- `data`:包含操作详情的字典
  - `host`:操作的主机名/IP
  - `pid`:被暂停的进程PID | -| **kill_mcp** | resume_process | 通过`kill`指令发送`SIGCONT`信号恢复进程(支持本地/远程) | - `pid`:需恢复的进程PID(正整数,必填)
- `host`:远程主机名/IP(默认`localhost`,本地操作可不填)
- `port`:SSH端口(默认22,远程操作时使用)
- `username`:SSH用户名(默认`root`,远程操作时需指定)
- `password`:SSH密码(远程操作时必填) | - `success`:操作是否成功(布尔值)
- `message`:操作结果描述(字符串)
- `data`:包含操作详情的字典
  - `host`:操作的主机名/IP
  - `pid`:被恢复的进程PID | -| | get_kill_signals | 查看本地或远程服务器的`kill`信号量含义及功能说明 | - `host`:远程主机名/IP(本地查询可不填)
- `port`:SSH端口(默认22,远程查询时使用)
- `username`:SSH用户名(远程查询时必填)
- `password`:SSH密码(远程查询时必填) | - `success`:查询是否成功(布尔值)
- `message`:查询结果描述(字符串)
- `data`:包含信号量信息的字典
  - `host`:查询的主机名/IP(本地为`localhost`)
  - `signals`:信号量列表,每个元素包含:
    - `number`:信号编号(整数)
    - `name`:信号名称(如`SIGTERM`)
    - `description`:信号功能说明 | - ---- - -##### ls_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|------------------|------------------------------------------------------------------------------|--------------------| -| **ls_mcp** | ls_collect_tool | 列出目录内容 | - `host`:远程主机名/IP(本地采集可不填)
- `file`:目标文件/目录 | 目标目录内容的列表 | - ---- - -##### lscpu_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------| ------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | -| **lscpu_mcp** | lscpu_info_tool | 使用 `lscpu` 命令获取本地或远程主机的 CPU 架构及核心静态信息 | - `host`:远程主机名/IP(若不提供则获取本机信息) | `architecture`:CPU 架构(如 x86\_64)、`cpus_total`:CPU 总数量、`model_name`:CPU 型号名称、`cpu_max_mhz`:CPU 最大频率 (MHz)、`vulnerabilities`:常见安全漏洞的缓解状态字典 | - ---- - -##### mkdir_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|----------------------------------------------------------|------------------------------------------------------------------------------|----------------------------| -| **mkdir_mcp** | mkdir_collect_tool | 进行目录创建、支持批量创建、设置权限、递归创建多级目录 | - `host`:远程主机名/IP(本地采集可不填)
- `dir`:创建目录名 | 布尔值,表示mkdir操作是否成功 | - ---- - -##### mv_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **mv_mcp** | mv_collect_tool | 移动或重命名文件/目录 | - `host`:远程主机名/IP(本地采集可不填)
- `source`:源文件或目录
- `target`:目标文件或目录 | 布尔值,表示mv操作是否成功 | - ---- - -##### nohup_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|----------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| **nohup_mcp** | run_with_nohup | 使用`nohup`在本地或远程服务器运行命令,支持后台执行 | - `command`:需执行的命令(字符串,必填)
- `host`:远程主机IP或hostname(本地执行可不填)
- `port`:SSH端口(默认22,远程执行时使用)
- `username`:SSH用户名(远程执行时必填)
- `password`:SSH密码(远程执行时必填)
- `output_file`:输出日志文件路径(可选,默认自动生成)
- `working_dir`:命令执行的工作目录(可选) | - `success`:操作是否成功(布尔值)
- `message`:执行结果描述(字符串)
- `pid`:进程ID(成功执行时返回)
- `output_file`:输出日志文件路径
- `command`:实际执行的命令
- `host`:执行命令的主机(本地为`localhost`) | - ---- - -##### perf_microarch_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------------|---------------------------|---------------------------------------------------------------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| **perf_microarch_mcp** | cache_miss_audit_tool | 通过 `perf stat -a -e cache-misses,cycles,instructions sleep 10` 采集整机的微架构指标,支持本地和远程执行 | - `host`:可选,远程主机名/IP,留空则采集本机 | `cache_misses`:缓存未命中次数
`cycles`:CPU 周期数
`instructions`:指令数
`ipc`:每周期指令数 (Instructions per Cycle)
`seconds`:采集时长(秒) | - ---- - -##### cache_miss_audit_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------------|---------------------------|---------------------------------------------------------------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| **cache_miss_audit_mcp** | cache_miss_audit_tool | 通过 `perf stat -a -e cache-misses,cycles,instructions sleep 10` 采集整机的微架构指标,支持本地和远程执行 | - `host`:可选,远程主机名/IP,留空则采集本机 | `cache_miss`:缓存未命中次数
`cycles`:CPU 周期数
`instructions`:指令数
`ipc`:每周期指令数 (Instructions per Cycle)
`seconds`:采集时长(秒) | - ---- - -##### cat_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|------------------|------------------------------------------------------------------------------|--------------------| -| **cat_mcp** | cat_file_view_tool | 快速查看文件内容 | - `host`:远程主机名/IP(本地采集可不填)
- `file`:查看的文件路径 | 文件内容字符串 | - ---- - -##### chmod_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-------------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **chmod_mcp** | chmod_change_mode_tool | 修改文件或目录的权限 | - `host`:远程主机名/IP(本地操作可不填)
- `mode`:权限模式(如755、644等)
- `file`:目标文件或目录路径 | 布尔值,表示操作是否成功 | - ---- - -##### chown_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|---------------------------|------------------------------|------------------------------------------------------------------------------|----------------------------| -| **chown_mcp** | chown_change_owner_tool | 修改文件或目录的所有者和所属组 | - `host`:远程主机名/IP(本地操作可不填)
- `owner_group`:文件所有者和文件关联组
- `file`:要修改的目标文件 | 布尔值,表示操作是否成功 | - ---- - -##### disk_manager_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|--------------------|-----------------------|--------------------------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| | top_collect_tool | 获取目标设备(本地/远程)中**内存占用排名前k个**的进程信息,k支持自定义配置 | - `host`:远程主机名/IP(本地采集可不填)
- `k`:需获取的进程数量(默认5) | 进程列表(含`pid`进程ID、`name`进程名称、`memory`内存使用量(MB)) | -| | get_process_info_tool | 查询指定PID进程的**详细运行信息**,支持本地与远程进程信息获取 | - `host`:远程主机名/IP(本地查询可不填)
- `pid`:需查询的进程ID(必传,且为正整数) | 进程详细字典(含`status`状态、`create_time`创建时间、`cpu_times`CPU时间、`memory_info`内存信息、`open_files`打开文件列表、`connections`网络连接等) | -| | change_name_to_pid_tool | 根据进程名称反向查询对应的**PID列表**,解决“已知进程名查ID”的场景需求 | - `host`:远程主机名/IP(本地查询可不填)
- `name`:需查询的进程名称(必传,不能为空) | 以空格分隔的PID字符串(如“1234 5678”) | -| | get_cpu_info_tool | 采集目标设备的CPU硬件与使用状态信息,包括核心数、频率、核心使用率 | - `host`:远程主机名/IP(本地采集可不填) | CPU信息字典(含`physical_cores`物理核心数、`total_cores`逻辑核心数、`max_frequency`最大频率(MHz)、`cpu_usage`各核心使用率(%)等) | -| | memory_anlyze_tool | 分析目标设备的内存使用情况,计算总内存、可用内存及使用率 | - `host`:远程主机名/IP(本地采集可不填) | 内存信息字典(含`total`总内存(MB)、`available`可用内存(MB)、`used`已用内存(MB)、`percent`内存使用率(%)等) | -| **disk_manager_mcp** | get_disk_info_tool | 采集目标设备的磁盘分区信息与容量使用状态,过滤临时文件系统(tmpfs/devtmpfs) | - `host`:远程主机名/IP(本地采集可不填) | 磁盘列表(含`device`设备名、`mountpoint`挂载点、`fstype`文件系统类型、`total`总容量(GB)、`percent`磁盘使用率(%)等) | -| | get_os_info_tool | 获取目标设备的操作系统类型与版本信息,适配OpenEuler、Ubuntu、CentOS等多系统 | - `host`:远程主机名/IP(本地采集可不填) | 操作系统信息字符串(如“OpenEuler 22.03 LTS”或“Ubuntu 20.04.5 LTS”) | -| | get_network_info_tool | 采集目标设备的网络接口信息,包括IP地址、MAC地址、接口启用状态 | - `host`:远程主机名/IP(本地采集可不填) | 网络接口列表(含`interface`接口名、`ip_address`IP地址、`mac_address`MAC地址、`is_up`接口是否启用(布尔值)等) | -| | write_report_tool | 将系统信息分析结果写入本地报告文件,自动生成带时间戳的文件路径 | - `report`:报告内容字符串(必传,不能为空) | 报告文件路径字符串(如“/reports/system_report_20240520_153000.txt”) | -| | telnet_test_tool | 测试目标主机指定端口的Telnet连通性,验证端口开放状态 | - `host`:远程主机名/IP(必传)
- `port`:端口号(1-65535,必传) | 连通性结果(布尔值:`True`成功,`False`失败) | -| | ping_test_tool | 测试目标主机的ICMP Ping连通性,验证主机网络可达性 | - `host`:远程主机名/IP(必传) | 连通性结果(布尔值:`True`成功,`False`失败) | -| | get_dns_info_tool | 采集目标设备的DNS配置信息,包括DNS服务器列表与搜索域 | - `host`:远程主机名/IP(本地采集可不填) | DNS信息字典(含`nameservers`DNS服务器列表、`search`搜索域列表) | -| | perf_data_tool | 采集目标设备的实时性能数据,支持“指定进程”或“全系统”性能监控 | - `host`:远程主机名/IP(本地采集可不填)
- `pid`:进程ID(全系统监控可不填) | 性能数据字典(含`cpu_usage`CPU使用率(%)、`memory_usage`内存使用率(%)、`io_counters`I/O统计信息) | - ---- - -##### echo_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-------------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **echo_mcp** | echo_write_to_file_tool | 使用echo命令将文本写入文件 | - `host`:远程主机名/IP(本地操作可不填)
- `text`:要写入的文本内容
- `file`:要写入的文件路径
- `options`:echo选项(可选),如"-n"不输出换行符等
- `mode`:写入模式,"w"表示覆盖写入,"a"表示追加写入,默认为"w" | 布尔值,表示写入操作是否成功 | - ---- - -##### fallocate_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|------------------|---------------------------|--------------------------|------------------------------------------------------------------------------|--------------------------------| -| **fallocate_mcp** | fallocate_create_file_tool | 创建并启用swap文件(修正工具功能描述,与参数匹配) | - `host`:远程主机名/IP(本地采集可不填)
- `name`:swap空间对应的设备或文件路径
- `size`:创建的磁盘空间大小 | 布尔值,表示创建启用swap文件是否成功 | - ---- - -##### file_content_tool_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|------------------------|-------------------------|--------------------------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| | file_grep_tool | 通过`grep`命令搜索文件中匹配指定模式的内容(支持正则、大小写忽略等) | - `file_path`:目标文件路径(绝对路径,必填)
- `pattern`:搜索模式(支持正则,如"error",必填)
- `options`:`grep`可选参数(如"-n"显示行号、"-i"忽略大小写,可选)
- `host`:远程主机名/IP(默认`localhost`,本地操作可不填)
- `port`:SSH端口(默认22,远程操作时使用)
- `username`:SSH用户名(默认`root`,远程操作时需指定)
- `password`:SSH密码(远程操作时必填) | - `success`:操作是否成功(布尔值)
- `message`:操作结果描述(如"本地文件搜索完成")
- `data`:包含操作详情的字典
  - `host`:操作的主机名/IP
  - `file_path`:目标文件路径
  - `result`:匹配结果列表(每行一个匹配项) | -| | file_sed_tool | 通过`sed`命令替换文件中匹配的内容(支持全局替换、原文件修改) | - `file_path`:目标文件路径(绝对路径,必填)
- `pattern`:替换模式(如"s/old/new/g",`g`表示全局替换,必填)
- `in_place`:是否直接修改原文件(布尔值,默认`False`,仅输出结果)
- `options`:`sed`可选参数(如"-i.bak"备份原文件,可选)
- `host`/`port`/`username`/`password`:同`file_grep_tool` | - `success`:操作是否成功(布尔值)
- `message`:操作结果描述(如"远程sed执行成功")
- `data`:包含操作详情的字典
  - `host`:操作的主机名/IP
  - `file_path`:目标文件路径
  - `result`:替换后内容(`in_place=False`时返回) | -| **file_content_tool_mcp** | file_awk_tool | 通过`awk`命令对文本文件进行高级处理(支持列提取、条件过滤) | - `file_path`:目标文件路径(绝对路径,必填)
- `script`:`awk`处理脚本(如"'{print $1,$3}'"提取1、3列,必填)
- `options`:`awk`可选参数(如"-F:"指定分隔符为冒号,可选)
- `host`/`port`/`username`/`password`:同`file_grep_tool` | - `success`:操作是否成功(布尔值)
- `message`:操作结果描述(如"本地awk处理成功")
- `data`:包含操作详情的字典
  - `host`:操作的主机名/IP
  - `file_path`:目标文件路径
  - `result`:处理结果列表(每行一个结果项) | -| | file_sort_tool | 通过`sort`命令对文本文件进行排序(支持按列、升序/降序) | - `file_path`:目标文件路径(绝对路径,必填)
- `options`:`sort`可选参数(如"-n"按数字排序、"-k2"按第2列排序、"-r"降序,可选)
- `output_file`:排序结果输出路径(可选,默认不保存到文件)
- `host`/`port`/`username`/`password`:同`file_grep_tool` | - `success`:操作是否成功(布尔值)
- `message`:操作结果描述(如"远程排序完成")
- `data`:包含操作详情的字典
  - `host`:操作的主机名/IP
  - `file_path`/`output_file`:目标文件/输出文件路径
  - `result`:排序结果列表(`output_file`为空时返回) | -| | file_unique_tool | 通过`unique`命令对文本文件进行去重(支持统计重复次数) | - `file_path`:目标文件路径(绝对路径,必填)
- `options`:`unique`可选参数(如"-u"仅显示唯一行、"-c"统计重复次数,可选)
- `output_file`:去重结果输出路径(可选,默认不保存到文件)
- `host`/`port`/`username`/`password`:同`file_grep_tool` | - `success`:操作是否成功(布尔值)
- `message`:操作结果描述(如"本地去重完成")
- `data`:包含操作详情的字典
  - `host`:操作的主机名/IP
  - `file_path`/`output_file`:目标文件/输出文件路径
  - `result`:去重结果列表(`output_file`为空时返回) | -| | file_echo_tool | 通过`echo`命令向文件写入内容(支持覆盖/追加模式) | - `content`:要写入的内容(如"Hello World",必填)
- `file_path`:目标文件路径(绝对路径,必填)
- `append`:是否追加内容(布尔值,默认`False`,覆盖原文件)
- `host`/`port`/`username`/`password`:同`file_grep_tool` | - `success`:操作是否成功(布尔值)
- `message`:操作结果描述(如"本地写入成功")
- `data`:包含操作详情的字典
  - `host`:操作的主机名/IP
  - `file_path`:目标文件路径
  - `action`:操作类型("overwrite"覆盖/"append"追加) | - ---- - -##### find_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-------------------------|------------------------------|------------------------------------------------------------------------------|------------------------------------------------| -| | find_with_name_tool | 基于名称在指定目录下查找文件 | - `host`:远程主机名/IP(本地采集可不填)
- `path`:指定查找的目录
- `name`:要找的文件名 | 查找到的文件列表(含`file`符合查找要求的具体文件路径) | -| **find_mcp** | find_with_date_tool | 基于修改时间在指定目录下查找文件 | - `host`:远程主机名/IP(本地采集可不填)
- `path`:指定查找的目录
- `date_condition`:修改时间条件(如"-mtime -1"表示1天内修改,补充参数使功能匹配) | 查找到的文件列表(含`file`符合查找要求的具体文件路径) | -| | find_with_size_tool | 基于文件大小在指定目录下查找文件 | - `host`:远程主机名/IP(本地采集可不填)
- `path`:指定查找的目录
- `size_condition`:文件大小条件(如"+10M"表示大于10MB,补充参数使功能匹配) | 查找到的文件列表(含`file`符合查找要求的具体文件路径) | - ---- - -##### flame_graph_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|------------------|-----------------------| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -| **flame_graph_mcp** | flame_graph | 基于 `perf.data` 生成 CPU 火焰图,用于性能分析(支持本地/远程) | - `host`:远程主机地址(可选)
- `perf_data_path`:perf.data 输入路径(必选)
- `output_path`:SVG 输出路径(默认:\~/cpu\_flamegraph.svg)
- `flamegraph_path`:FlameGraph 脚本路径(必选) | - `svg_path`:生成的火焰图文件路径
- `status`:生成状态(success / failure)
- `message`:状态信息 | - ---- - -##### free_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------| -| **free_mcp** | free_collect_tool | 获取目标设备(本地/远程)中内存整体状态信息 | - `host`:远程主机名/IP(本地采集可不填) | 内存信息列表(含`total`系统内存总量(MB)、`used`系统已使用内存量(MB)、`free`空闲物理内存(MB)、`available`系统可分配内存(MB)) | - ---- - -##### func_timing_trace_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|------------------------|---------------------------| ------------------------------------------ | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| **func_timing_trace_mcp** | func_timing_trace_tool | 使用 `perf record -g` 采集目标进程的函数调用栈耗时,并解析热点函数 | - `pid`:目标进程 PID
- `host`:可选,远程主机 IP/域名;留空则采集本机 | `top_functions`:函数耗时分析结果,包含列表,每项包括:
• `function`:函数名
• `self_percent`:函数自身耗时占比
• `total_percent`:函数总耗时占比
• `call_stack`:函数调用栈 | - ---- - -##### grep_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|------------------------------------------------| -| **grep_mcp** | grep_search_tool | 在文件中搜索指定模式的内容 | - `host`:远程主机名/IP(本地搜索可不填)
- `options`:grep选项(可选),如"-i"忽略大小写,"-n"显示行号等
- `pattern`:要搜索的模式(支持正则表达式)
- `file`:要搜索的文件路径 | 包含匹配行的字符串,如果没有找到匹配项则返回相应的提示信息 | - ---- - -##### head_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|--------------------| -| **head_mcp** | head_file_view_tool | 快速查看文件开头部分内容 | - `host`:远程主机名/IP(本地采集可不填)
- `num`:查看文件开头行数,默认为10行
- `file`:查看的文件路径 | 文件内容字符串 | - ---- - -##### hotspot_trace_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|--------------------|-------------------------| ---------------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| **hotspot_trace_mcp** | hotspot_trace_tool | 使用 `perf record` 和 `perf report` 分析系统或指定进程的 CPU 性能瓶颈 | - `host`:远程主机名/IP(可选,不填则分析本机)
- `pid`:目标进程 ID(可选,不填则分析整机) | - `total_samples`:总样本数
- `event_count`:事件计数(如 cycles)
- `hot_functions`:热点函数列表(按 Children 百分比排序,包含函数名、库、符号类型和占比) | - ---- - -##### numa_bind_docker_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|------------------------|---------------------------| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| **numa_bind_docker_mcp** | numa_bind_docker_tool | 使用 `numactl` 将指定 NUMA 绑定参数插入到镜像原有的 ENTRYPOINT / CMD 前,运行 Docker 容器(本地/远程) | - `image`:镜像名称
- `cpuset_cpus`:允许使用的 CPU 核心范围
- `cpuset_mems`:允许使用的内存节点
- `detach`:是否后台运行容器(默认 False)
- `host`:远程主机名/IP(可选) | - `status`:操作状态(success / error)
- `message`:操作结果信息
- `output`:命令的原始输出(如有) | - ---- - -##### numa_bind_proc_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|------------------------|---------------------------| ------------------------------------------------ | --------------------------------------------------------------------------------------------------- | ------------------------------------------------- | -| **numa_bind_proc_mcp** | numa_bind_proc_tool | 使用 `numactl` 命令在指定的 NUMA 节点和内存节点上运行程序(支持本地/远程执行) | - `host`:远程主机名/IP(本地可不填)
- `numa_node`:NUMA 节点编号(整数)
- `memory_node`:内存节点编号(整数)
- `program_path`:程序路径(必填) | `stdout`:程序标准输出、`stderr`:程序标准错误、`exit_code`:程序退出码 | - ---- - -##### numa_container_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|--------------------|-------------------------| -------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| **numa_container_mcp** | numa_container | 监控指定 Docker 容器的 NUMA 内存访问情况(支持本地/远程执行) | - `container_id`:要监控的容器 ID 或名称
- `host`:远程主机地址(可选,若为空则在本地执行) | - `status`:操作状态(success / error)
- `message`:操作结果信息
- `output`:NUMA 内存访问统计信息(包含每个 NUMA 节点的内存使用情况) | - ---- - -##### numa_cross_node_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------------|---------------------------| ------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| **numa_cross_node_mcp** | numa_cross_node | 自动检测 NUMA 跨节点访问异常的进程(支持本地与远程主机) | - `host`:远程主机 IP/域名(可选,留空则检测本机)
- `threshold`:跨节点内存比例阈值(默认 30%) | `overall_conclusion`:整体结论(是否存在问题、严重程度、摘要),`anomaly_processes`:异常进程列表(包含 `pid`、`local_memory`、`remote_memory`、`cross_ratio`、`name`、`command`) | - ---- - -##### numa_diagnose_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|--------------------|-------------------------| --------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| **numa_diagnose_mcp** | numa_diagnose | 获取 NUMA 架构硬件监控信息,包括 CPU 实时频率、规格参数以及 NUMA 拓扑结构 | - `host`:远程主机地址(可选,不填则在本地执行) | - `real_time_frequencies`:各 CPU 核心实时频率 (MHz)
- `specifications`:CPU 规格信息(型号 / 频率范围 / NUMA 节点)
- `numa_topology`:NUMA 拓扑结构 | - ---- - -##### numa_perf_compare_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|------------------------|---------------------------| ------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| **numa_perf_compare_mcp** | numa_perf_compare | 执行NUMA基准测试,支持本地绑定、跨节点绑定和不绑定三种策略 | - `benchmark`:基准测试可执行文件路径(如 `/root/mcp_center/stream`)
- `host`:远程主机名称或IP地址(可选) | `numa_nodes`:系统NUMA节点数量
`test_results`:包含三种绑定策略的测试结果
`timestamp`:执行时间
`error`:错误信息(如有) | - ---- - -##### numa_rebind_proc_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|------------------------|---------------------------| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| **numa_rebind_proc_mcp** | numa_rebind_proc_tool | 修改已运行进程的 NUMA 内存绑定,使用 migratepages 工具将进程的内存从一个 NUMA 节点迁移到另一个节点 | - `pid`:进程 ID
- `from_node`:当前内存所在的 NUMA 节点编号
- `to_node`:目标 NUMA 节点编号
- `host`:远程主机 IP 或名称(可选) | `status`:操作状态(success / error)
`message`:操作结果信息
`output`:命令的原始输出(如有) | - ---- - -##### numa_topo_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|--------------------|-------------------------| ------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| **numa_topo_mcp** | numa_topo_tool | 使用 numactl 获取本地或远程主机的 NUMA 拓扑信息 | - `host`:远程主机名称或 IP(可选,不填表示获取本机信息) | - `nodes_total`:总节点数
- `nodes`:节点信息列表,每个节点包含:`node_id`(节点 ID)、`cpus`(CPU 列表)、`size_mb`(内存大小 MB)、`free_mb`(空闲内存 MB) | - ---- - -##### numastat_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|--------------------|-------------------------| ------------------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| **numastat_mcp** | numastat_info_tool | 使用 `numastat` 命令获取本地或远程主机的 NUMA 统计信息 | - `host`:远程主机名称或 IP,若不提供则获取本机信息 | `numa_hit`: NUMA 命中次数、`numa_miss`: NUMA 未命中次数、`numa_foreign`: 外部访问次数、`interleave_hit`: 交错命中次数、`local_node`: 本地节点访问次数、`other_node`: 其他节点访问次数 | - ---- - -##### nvidia_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-------------------------|-------------------------------------------|------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------| -| | nvidia_smi_status | 输出结构化 GPU 状态数据(JSON 友好) | - `host`:远程主机 IP/hostname(本地可不填)
- `port`:SSH 端口(默认 22)
- `username`/`password`:远程查询必填
- `gpu_index`:指定 GPU 索引(可选)
- `include_processes`:是否包含进程信息(默认 False) | - `success`:查询成功与否
- `message`:结果描述
- `data`:结构化数据,包含:
  - `host`:主机地址
  - `gpus`:GPU 列表(含索引、型号、利用率、显存等) | -| **nvidia_mcp** | nvidia_smi_raw_table | 输出 `nvidia-smi` 原生表格(保留原始格式) | - `host`:远程主机 IP/hostname(本地可不填)
- `port`:SSH 端口(默认 22)
- `username`/`password`:远程查询必填 | - `success`:查询成功与否
- `message`:结果描述
- `data`:原始表格数据,包含:
  - `host`:主机地址
  - `raw_table`:`nvidia-smi` 原生表格字符串(含换行和格式) | - ---- - -##### perf_interrupt_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|--------------------|-------------------------| ---------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| **perf_interrupt_mcp** | perf_interrupt_health_check | 检查系统中断统计信息,以定位高频中断导致的 CPU 占用 | - `host`:远程主机名称或 IP 地址,若不提供则获取本机信息 | 返回一个包含中断信息的列表,每个元素包含:`irq_number` 中断编号、`total_count` 总触发次数、`device` 设备名称、`cpu_distribution` 各 CPU 核心的中断分布、`interrupt_type` 中断类型 | - ---- - -##### rm_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **rm_mcp** | rm_collect_tool | 对文件或文件夹进行删除 | - `host`:远程主机名/IP(本地采集可不填)
- `path`:要进行删除的文件或文件夹路径 | 布尔值,表示rm操作是否成功 | - ---- - -##### sar_mcp - -|MCP_Server名称|MCP_Tool列表|工具功能|核心输入参数|关键返回内容| -|---|---|---|---|---| -||sar_cpu_collect_tool|分析CPU使用的周期性规律|- `host`:远程主机名/IP(本地采集可不填)
- `interval`:监控的时间间隔
- `count`:监控次数|采集指标列表:含`timestamp`采集时间点、`user`用户空间程序占用CPU的百分比、`nice`低优先级用户进程占用的CPU百分比、`system`内核空间程序占用CPU的百分比、`iowait`CPU等待磁盘I/O操作的时间百分比、`steal`虚拟化环境中其他虚拟机占用的CPU时间百分比、`idle`CPU空闲时间百分比| -||sar_memory_collect_tool|分析内存资源使用的周期性规律|- `host`:远程主机名/IP(本地采集可不填)
- `interval`:监控的时间间隔
- `count`:监控次数|采集指标列表:含`timestamp`采集时间点、`kbmemfree`物理空闲内存量、`kbavail`实际可用内存、`kbmemused`已使用的物理内存、`memused`已用内存占总物理内存的百分比、`kbbuffers`内核缓冲区(Buffer)占用的内存、`kbcached`内核缓存(Cache)占用的内存、`kbcommit`当前工作负载所需的总内存量、`commit`kbcommit占系统总可用内存百分比、`kbactive`活跃内存、`kbinact`非活跃内存、`kbdirty`等待写入磁盘的脏数据量| -||sar_disk_collect_tool|分析磁盘IO使用的周期性规律|- `host`:远程主机名/IP(本地采集可不填)
- `interval`:监控的时间间隔
- `count`:监控次数|采集指标列表:含`timestamp`采集时间点、`name`磁盘设备名称、`tps`每秒传输次数、`rkB_s`每秒读取的数据量、`wkB_s`每秒写入的数据量、`dkB_s`每秒丢弃的数据量、`areq-sz`平均每次I/O请求的数据大小、`aqu-sz`平均I/O请求队列长度、`await`平均每次I/O请求的等待时间、`util`设备带宽利用率| -|**sar_mcp**|sar_network_collect_tool|分析网络流量的周期性规律|- `host`:远程主机名/IP(本地采集可不填)
- `interval`:监控的时间间隔
- `count`:监控次数|采集指标列表:含`timestamp`采集时间点、`iface`网络接口名称、`rxpck_s`每秒接收的数据包数量、`txpck_s`每秒发送的数据包数量、`rxkB_s`每秒接收的数据量、`txkB_s`每秒发送的数据量、`rxcmp_s`每秒接收的压缩数据包数、`txcmp_s`每秒发送的压缩数据包数、`rxmcst_s`每秒接收的多播数据包数、`ifutil`网络接口带宽利用率| -||sar_cpu_historicalinfo_collect_tool|进行历史状态分析,排查过去某时段cpu的性能问题|- `host`:远程主机名/IP(本地查询可不填)
- `file`:sar要分析的log文件
- `starttime`:分析开始的时间点
- `endtime`:分析结束的时间点|采集指标列表:含`timestamp`采集时间点、`user`用户空间程序占用CPU的百分比、`nice`低优先级用户进程占用的CPU百分比、`system`内核空间程序占用CPU的百分比、`iowait`CPU等待磁盘I/O操作的时间百分比、`steal`虚拟化环境中其他虚拟机占用的CPU时间百分比、`idle`CPU空闲时间百分比| -||sar_memory_historicalinfo_collect_tool|进行历史状态分析,排查过去某时段内存的性能问题|- `host`:远程主机名/IP(本地查询可不填)
- `file`:sar要分析的log文件
- `starttime`:分析开始的时间点
- `endtime`:分析结束的时间点|采集指标列表:含`timestamp`采集时间点、`kbmemfree`物理空闲内存量、`kbavail`实际可用内存、`kbmemused`已使用的物理内存、`memused`已用内存占总物理内存的百分比、`kbbuffers`内核缓冲区(Buffer)占用的内存、`kbcached`内核缓存(Cache)占用的内存、`kbcommit`当前工作负载所需的总内存量、`commit`kbcommit占系统总可用内存百分比、`kbactive`活跃内存、`kbinact`非活跃内存、`kbdirty`等待写入磁盘的脏数据量| -||sar_disk_historicalinfo_collect_tool|进行历史状态分析,排查过去某时段磁盘IO的性能问题|- `host`:远程主机名/IP(本地查询可不填)
- `file`:sar要分析的log文件
- `starttime`:分析开始的时间点
- `endtime`:分析结束的时间点|采集指标列表:含`timestamp`采集时间点、`name`磁盘设备名称、`tps`每秒传输次数、`rkB_s`每秒读取的数据量、`wkB_s`每秒写入的数据量、`dkB_s`每秒丢弃的数据量、`areq-sz`平均每次I/O请求的数据大小、`aqu-sz`平均I/O请求队列长度、`await`平均每次I/O请求的等待时间、`util`设备带宽利用率| -||sar_network_historicalinfo_collect_tool|进行历史状态分析,排查过去某时段网络的性能问题|- `host`:远程主机名/IP(本地查询可不填)
- `file`:sar要分析的log文件
- `starttime`:分析开始的时间点
- `endtime`:分析结束的时间点|采集指标列表:含`timestamp`采集时间点、`iface`网络接口名称、`rxpck_s`每秒接收的数据包数量、`txpck_s`每秒发送的数据包数量、`rxkB_s`每秒接收的数据量、`txkB_s`每秒发送的数据量、`rxcmp_s`每秒接收的压缩数据包数、`txcmp_s`每秒发送的压缩数据包数、`rxmcst_s`每秒接收的多播数据包数、`ifutil`网络接口带宽利用率| - ---- - -##### sed_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **sed_mcp** | sed_text_replace_tool | 在文件中替换指定模式的文本 | - `host`:远程主机名/IP(本地操作可不填)
- `options`:sed选项(可选),如"-i"直接修改文件
- `pattern`:要替换的模式(支持正则表达式)
- `replacement`:替换后的文本
- `file`:要操作的文件路径 | 布尔值,表示操作是否成功 | -| | sed_text_delete_tool | 删除文件中匹配模式的行 | - `host`:远程主机名/IP(本地操作可不填)
- `options`:sed选项(可选),如"-i"直接修改文件
- `pattern`:要删除的行的模式(支持正则表达式)
- `file`:要操作的文件路径 | 布尔值,表示操作是否成功 | - ---- - -##### strace_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------------|--------------------------------------------|------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------| -| | strace_track_file_process | 跟踪进程的文件操作和运行状态(如打开、读取、写入文件等) | - `pid`:目标进程PID(必填)
- `host`:远程主机IP/hostname(本地跟踪可不填)
- `port`:SSH端口(默认22)
- `username`/`password`:远程跟踪时必填
- `output_file`:日志路径(可选)
- `follow_children`:是否跟踪子进程(默认False)
- `duration`:跟踪时长(秒,可选) | - `success`:跟踪启动状态
- `message`:结果描述
- `strace_pid`:跟踪进程ID
- `output_file`:日志路径
- `target_pid`/`host`:目标进程及主机信息 | -| **strace_mcp** | strace_check_permission_file| 排查进程的"权限不足"和"文件找不到"错误 | - `pid`:目标进程PID(必填)
- 远程参数(`host`/`port`/`username`/`password`)
- `output_file`:日志路径(可选)
- `duration`:跟踪时长(默认30秒) | - 基础状态信息(`success`/`message`等)
- `errors`:错误统计字典,包含:
  - 权限不足错误详情
  - 文件找不到错误详情 | -| | strace_check_network | 诊断进程网络问题(连接失败、超时、DNS解析等) | - `pid`:目标进程PID(必填)
- 远程参数(同上)
- `output_file`:日志路径(可选)
- `duration`:跟踪时长(默认30秒)
- `trace_dns`:是否跟踪DNS调用(默认True) | - 基础状态信息
- `errors`:网络错误统计,包含:
  - 连接被拒绝、超时等错误
  - DNS解析失败详情(若启用) | -| | strace_locate_freeze | 定位进程卡顿原因(IO阻塞、锁等待等慢操作) | - `pid`:目标进程PID(必填)
- 远程参数(同上)
- `output_file`:日志路径(可选)
- `duration`:跟踪时长(默认30秒)
- `slow_threshold`:慢操作阈值(默认0.5秒) | - 基础状态信息
- `analysis`:卡顿分析字典,包含:
  - 慢操作调用详情
  - 阻塞类型分类统计
  - 耗时最长的系统调用 | - ---- - -##### strace_syscall_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------| -| **strace_syscall_mcp** | strace_syscall | 采集指定进程的系统调用统计信息 | - `host`:可选,远程主机地址
- `pid`:目标进程ID(必填)
- `timeout`:采集超时时间,默认10秒 | List\[Dict],每个字典包含:
- `syscall`:系统调用名称
- `total_time`:总耗时(秒)
- `call_count`:调用次数
- `avg_time`:平均耗时(微秒)
- `error_count`:错误次数 | - ---- - -##### swapoff_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------------|--------------------------------------------|------------------------------------------------------------------------------|----------------------------| -| **swapoff_mcp** | swapoff_disabling_swap_tool | 停用交换空间(Swap),释放已启用的交换分区或交换文件,将其从系统内存管理中移除 | - `host`:远程主机名/IP(本地采集可不填)
- `name`:停用的swap空间路径 | 布尔值,表示停用指定swap空间是否成功 | - ---- - -##### swapon_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **swapon_mcp** | swapon_collect_tool | 获取目标设备(本地/远程)中当前swap设备状态 | - `host`:远程主机名/IP(本地采集可不填) | swap设备列表(含`name`swap空间对应的设备或文件路径、`type`swap空间的类型、`size`swap空间的总大小、`used`当前已使用的swap空间量、`prio`swap空间的优先级) | - ---- - -##### tail_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **tail_mcp** | tail_file_view_tool | 快速查看文件末尾部分内容 | - `host`:远程主机名/IP(本地采集可不填)
- `num`:查看文件末尾行数,默认为10行
- `file`:查看的文件路径 | 文件内容字符串 | - ---- - -##### tar_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **tar_mcp** | tar_extract_file_tool | 使用tar命令解压文件或目录 | - `host`:远程主机名称或IP地址,若不提供则表示对本机文件进行修改
- `options`:tar命令选项(如`-xzvf`等)
- `file`:压缩包文件路径
- `extract_path`:指定解压目录 | 布尔值,表示解压操作是否成功 | -| | tar_compress_file_tool | 使用tar命令压缩文件或目录 | - `host`:远程主机名称或IP地址,若不提供则表示对本机文件进行压缩
- `options`:tar命令选项(如`-czvf`、`-xzvf`等)
- `source_path`:需要压缩的文件或目录路径
- `archive_path`:压缩包输出路径 | 布尔值,表示压缩操作是否成功 | - ---- - -##### touch_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|---------------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **touch_mcp** | touch_create_files_tool | 进行文件快速初始化、批量创建 | - `host`:远程主机名/IP(本地采集可不填)
- `file`:创建的文件名 | 布尔值,表示touch操作是否成功 | -| | touch_timestamp_files_tool | 进行文件时间戳校准与模拟 | - `host`:远程主机名/IP(本地查询可不填)
- `options`:更新访问时间\更新修改时间(`-a`表示仅更新访问时间、`-m`表示仅更新修改时间)
- `file`:文件名 | 布尔值,表示touch操作是否成功 | - ---- - -##### vmstat_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **vmstat_mcp** | vmstat_collect_tool | 获取目标设备资源整体状态 | - `host`:远程主机名/IP(本地采集可不填) | 系统资源状态字典(含`r`运行队列进程数、`b`等待 I/O 的进程数、`si`每秒从磁盘加载到内存的数据量(KB/s)、`so`每秒从内存换出到磁盘的数据量(KB/s)、`bi`从磁盘读取的块数、`bo`写入磁盘的块数、`in`每秒发生的中断次数(含时钟中断)、`cs`每秒上下文切换次数、`us`用户进程消耗 CPU 时间、`sy`内核进程消耗 CPU 时间、`id`CPU 空闲时间、`wa`CPU 等待 I/O 完成的时间百分比、`st`被虚拟机偷走的 CPU 时间百分比) | -| | vmstat_slabinfo_collect_tool | 获取内核 slab 内存缓存(slabinfo)的统计信息 | - `host`:远程主机名/IP(本地查询可不填) | slab内存缓存信息详细字典(含`cache`内核中slab缓存名称、`num`当前活跃的缓存对象数量、`total`该缓存的总对象数量、`size`每个缓存对象的大小、`pages`每个slab中包含的缓存对象数量) | - ---- - -##### zip_mcp - -| MCP_Server名称 | MCP_Tool列表 | 工具功能 | 核心输入参数 | 关键返回内容 | -|----------------|-----------------------|--------------------------|------------------------------------------------------------------------------|----------------------------| -| **zip_mcp** | zip_extract_file_tool | 使用unzip命令解压zip文件 | - `host`:远程主机名称或IP地址,若不提供则表示对本机文件进行修改
- `file`:压缩包文件路径
- `extract_path`:指定解压目录 | 布尔值,表示解压操作是否成功 | -| | tar_compress_file_tool | 使用zip命令压缩文件或目录 | - `host`:远程主机名称或IP地址,若不提供则表示对本机文件进行压缩
- `source_path`:需要压缩的文件或目录路径
- `archive_path`:压缩包输出路径 | 布尔值,表示压缩操作是否成功 | diff --git "a/docs/\351\203\250\347\275\262\346\211\213\345\206\214.md" "b/docs/\351\203\250\347\275\262\346\211\213\345\206\214.md" new file mode 100644 index 0000000000000000000000000000000000000000..fd4d3d9b66109e26b355d1a6e2dbcfc1b3310c50 --- /dev/null +++ "b/docs/\351\203\250\347\275\262\346\211\213\345\206\214.md" @@ -0,0 +1,224 @@ +# OE-CLI 部署手册 + +## 1. 环境要求 + +- **操作系统**:openEuler 24.03 LTS SP2 或更高版本 +- **内存**:至少 8GB RAM +- **存储**:至少 20GB 可用磁盘空间 +- **网络**:稳定的互联网连接(如果无法访问外部网络,请提前下载所需资源,详见 Q&A) +- **大模型服务**: + - 线上:需要能支持工具调用的线上大模型 API 访问权限,如百炼、DeepSeek Chat、GLM-4.5 等 + - 本地:支持部署在本地的 LLM 服务,如 llama-server、ollama、vLLM、LM Studio 等,需要部署支持工具调用能力的大模型,如 Qwen3、GLM-4.5、Kimi K2、DeepSeek 3.2 等 +- **系统权限**:需要具备 sudo 权限以安装必要的软件包和依赖项 + +## 2. 安装步骤 + +### 2.1 安装软件包 + +```bash +sudo dnf update -y +sudo dnf install -y openeuler-intelligence-cli openeuler-intelligence-installer +``` + +如果在 openEuler 24.03 LTS SP2 上找不到 `openeuler-intelligence-cli` 软件包,请参考 Q&A 中的解决方案。 + +### 2.2 初始化 openEuler Intelligence + +```bash +sudo oi --init +``` + +![选择连接现有服务或部署新服务](./resource/oi-deploy-01-welcome.png) + +部署全新服务的过程中涉及安装 RPM 包,请使用具有管理员权限的用户运行该命令。 + +**特别说明**:命令行客户端的界面样式会随着终端的适配情况出现差异,建议使用支持 256 色及以上的终端模拟器以获得最佳体验。本文档以 openEuler DevStation 内置的 GNOME 终端为例。 + +### 2.3 选择部署新服务 + +在欢迎界面选择“部署新服务”,然后按回车键继续。 + +![选择部署新服务](./resource/oi-deploy-02-env-check.png) + +选择“继续配置”,进入参数配置界面。 + +### 2.4 配置参数 + +在参数配置界面,根据实际情况设置以下参数: + +- **服务地址**:设置 openEuler Intelligence 服务的 IP,默认值为 `127.0.0.1`; +- **部署模式**:选择“轻量部署”或“全量部署”,轻量部署模式仅支持命令行界面,默认初始化全部内置的智能体,并自动选择“智能运维助手”为默认智能体;全量部署模式支持图形界面和命令行界面,但需要通过 Web 图形界面手动按需初始化智能体。 + +![基础配置](./resource/oi-deploy-03-basic.png) + +完成基础配置后,点击上方“LLM 配置”标签页,进入大模型配置界面。 + +![LLM 配置标签页](./resource/oi-deploy-04-llm-tab.png) + +依次配置大模型服务参数: + +- API 端点:填写线上或本地大模型服务的 API 地址; +- API 密钥:根据所选大模型服务的要求,填写 API Key 或 Token; +- 模型名称:选择所需使用的大模型。 + +另外,请确保所选大模型支持工具调用能力,否则智能体将无法正常工作。 + +![填写大模型信息](./resource/oi-deploy-05-llm-fill-in.png) + +**Embedding 模型配置:** + +- “轻量部署”模式下,若不配置 Embedding 模型,可能会导致部分智能体功能受限; +- “全量部署”模式下必须填写 Embedding 模型,否则无法继续部署; +- 支持的 Embedding 端口格式有 OpenAI 兼容格式和 MindIE 格式,请根据实际情况填写。 + +### 2.5 启动部署 + +完成大模型配置后,点击下方“开始部署”按钮,启动部署程序。 + +![开始部署](./resource/oi-deploy-06-start.png) + +![部署进行中](./resource/oi-deploy-07-ing.png) + +部署过程可能需要较长时间,请耐心等待。部署完成后,系统会提示成功信息。 + +![部署完成](./resource/oi-deploy-08-finish.png) + +轻量部署模式完成后,会显示已经初始化的智能体以及自动配置的默认智能体。 + +## 3. 内网离线环境部署 + +### 3.1 预下载资源 + +在内网环境中部署时,请提前下载以下资源: + +- 下载 MongoDB Server 安装包,放置在 `/opt/mongodb` 目录下: + - x86_64 版本:[mongodb-org-server-7.0.21-1.el9.x86_64.rpm](https://repo.mongodb.org/yum/redhat/9/mongodb-org/7.0/x86_64/RPMS/mongodb-org-server-7.0.21-1.el9.x86_64.rpm) + - aarch64 版本:[mongodb-org-server-7.0.21-1.el9.aarch64.rpm](https://repo.mongodb.org/yum/redhat/9/mongodb-org/7.0/aarch64/RPMS/mongodb-org-server-7.0.21-1.el9.aarch64.rpm) +- 下载 MongoDB Shell 安装包,放置在 `/opt/mongodb` 目录下: + - x86_64 版本:[mongodb-mongosh-2.5.2.x86_64.rpm](https://downloads.mongodb.com/compass/mongodb-mongosh-2.5.2.x86_64.rpm) + - aarch64 版本:[mongodb-mongosh-2.5.2.aarch64.rpm](https://downloads.mongodb.com/compass/mongodb-mongosh-2.5.2.aarch64.rpm) +- **全量部署**模式下,还需下载 MinIO RPM 安装包,放置在 `/opt/minio` 目录下: + - x86_64 版本:[x86_64 版本下载页面](https://dl.min.io/server/minio/release/linux-amd64/) + *请选择最新版本进行下载,例如:`minio-20250907161309.0.0-1.x86_64.rpm`* + - aarch64 版本:[aarch64 版本下载页面](https://dl.min.io/server/minio/release/linux-arm64/) + *请选择最新版本进行下载,例如:`minio-20250907161309.0.0-1.arm64.rpm`* + +### 3.2 使用大模型服务 + +#### 3.2.1 内网线上大模型服务 + +若内网环境的大模型服务须通过 HTTPS 访问,请确保证书已正确配置在系统中,避免部署过程中出现证书验证失败的问题。若无法配置有效的证书,请在环境变量中添加以下内容以跳过证书验证: + +```bash +export OI_SKIP_SSL_VERIFY=true +``` + +将上述命令添加到 `~/.bashrc` 或 `~/.bash_profile` 文件中,以确保每次登录时该环境变量均被设置。 + +#### 3.2.2 本地部署大模型服务 + +若没有可用的大模型服务,可以在本地部署一个支持工具调用能力的大模型服务。openEuler Intelligence 支持标准的 OpenAI API (v1/chat/completions)。 + +若本地设备没有 GPU,建议使用激活参数更少的 MoE 模型,如 Qwen3-30B-A3B,以获得更好的性能表现。本地部署大模型需要较大的计算资源,建议使用至少 32GB 内存和多核 CPU 的服务器或 PC 进行部署。 + +### 3.3 为 openEuler Intelligence 配置代理 + +若内网环境需要通过代理服务器访问大模型服务,请在 `oi-runtime` 的 service 文件 (`/etc/systemd/system/oi-runtime.service`) 中设置以下内容: + +```bash +[Service] +Environment="HTTP_PROXY=http://:" +Environment="HTTPS_PROXY=http://:" +Environment="NO_PROXY=localhost,127.0.0.1" +``` + +将 `` 和 `` 替换为实际的代理服务器地址和端口号。保存文件后,执行以下命令以重新加载服务配置并重启 `oi-runtime` 服务: + +```bash +sudo systemctl daemon-reload +sudo systemctl restart oi-runtime +``` + +## 4. 常见问题解答 (Q&A) + +### Q1: openEuler 24.03 LTS SP2 搜不到软件包怎么办? + +**A1**: 官方源默认没有配置 EPOL 仓的 update 源,可以手动添加 EPOL 仓的源配置文件 `/etc/yum.repos.d/openEuler.repo`,内容如下: + +```ini +[update-EPOL] +name=update-EPOL +baseurl=https://repo.openeuler.org/openEuler-24.03-LTS-SP2/EPOL/update/main/$basearch/ +metadata_expire=1h +enabled=1 +gpgcheck=1 +gpgkey=http://repo.openeuler.org/openEuler-24.03-LTS-SP2/OS/$basearch/RPM-GPG-KEY-openEuler +``` + +### Q2: 安装界面的语言如何设置? + +**A2**: 安装界面的默认语言会根据终端的语言环境变量自动选择。可以通过设置 `LANG` 环境变量来指定语言,例如: + +```bash +export LANG=zh_CN.UTF-8 # 设置为中文 +export LANG=en_US.UTF-8 # 设置为英文 +``` + +部署完成后,如果需要更改界面语言,可以在命令行界面使用以下命令: + +```bash +oi --locale zh_CN # 切换到中文 +oi --locale en_US # 切换到英文 +``` + +### Q3: 部署过程中遇到 pip 包下载很慢怎么办? + +**A3**: 可以使用国内的 pip 镜像源,例如清华大学的镜像源。可以通过修改 pip 配置文件来使用清华镜像源: + +```bash +mkdir -p ~/.pip +echo "[global]" > ~/.pip/pip.conf +echo "index-url = https://pypi.tuna.tsinghua.edu.cn/simple" >> ~/.pip/pip.conf +``` + +### Q4: 如果无法访问外网,如何获取所需的依赖包? + +**A4**: 可以在有外网的环境中下载所需的依赖包,然后通过 U 盘等方式将其拷贝到目标环境中进行安装。具体步骤如下: + +1. 在有外网的环境中,使用以下命令下载所需的依赖包: + + ```bash + pip download -d /path/to/download/dir + ``` + + 将 `` 替换为实际需要下载的包名,`/path/to/download/dir` 替换为实际的下载目录。 + +2. 将下载的依赖包拷贝到目标环境中。 + +3. 在目标环境中,使用以下命令安装依赖包: + + ```bash + pip install --no-index --find-links=/path/to/download/dir + ``` + + 将 `/path/to/download/dir` 替换为实际的下载目录,`` 替换为实际需要安装的包名。 + +### Q5: 服务器系统版本受制约,无法升级到 openEuler 24.03 LTS SP2,怎么办? + +**A5**: 可以尝试在当前系统版本上手动安装所需的软件包和依赖项。由于 openEuler 24.03 LTS 以上的版本都使用 6.6 版本的内核和 Python 3.11,因此只需要将 `/etc/os-release` 和 `/etc/openEuler-release` 文件中的版本信息修改为 24.03 LTS SP2 即可。 + +请注意,使用这种方法安装需要自行准备所需的软件包和依赖项,可能会遇到兼容性问题,建议在测试环境中进行验证后再应用到生产环境中。 + +### Q6: 全量部署后,如何访问图形界面? + +**A6**: 全量部署完成后,可以通过浏览器访问图形界面。默认情况下,图形界面的访问地址为 `http://localhost:8080`。 + +### Q7: 全量部署后,Shell 端无法连接后端服务怎么办? + +**A7**: 如果运行环境支持完整桌面端,例如 DevStation 或 VNC 连接,请运行 `oi --login` 命令打开浏览器登录。注意,首次登录前请先在 Web 端确认已注册有效的账号,并确保浏览器不会拦截弹出窗口。 + +如果无法使用完整桌面端,例如 SSH 连接或纯命令行环境,请先通过浏览器登录。浏览器正常登录后,需要打开“开发者工具”,从“应用”选项卡中找到“本地存储”,查看 `ECSESSION` 键对应的值。然后在 Shell 端按下 `Ctrl + S` 打开设置页面,找到“API 密钥”输入框,将 `ECSESSION` 的值粘贴进去并保存。保存后,Shell 端即可正常连接后端服务。 + +### Q8: 全量部署后,如何初始化智能体? + +**A8**: 全量部署后,需先在网页端注册名为 `openEuler` 的管理员用户,然后登录图形界面。先在“插件中心”中注册所需的 MCP Server 服务,然后在“应用中心”中创建并配置智能体。具体操作请参考 Web 图形界面的使用手册。 diff --git a/pyproject.toml b/pyproject.toml index 98c8633a7794d0b3aae8a238d0094688ef36a0a5..1c650e6fce88ce7ba099f63a36eabc3793c95c16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,18 @@ [project] name = "oi-cli" -version = "0.10.2" +version = "2.0.0" description = "智能 Shell 命令行工具" readme = "README.md" requires-python = ">=3.11" license = { text = "MulanPSL-2.0" } authors = [{ name = "openEuler", email = "contact@openeuler.org" }] dependencies = [ - "httpx==0.28.1", - "openai==2.3.0", - "rich==14.2.0", - "textual==6.3.0", - "toml==0.10.2", + "httpx>=0.28.1", + "openai>=2.7.1", + "pyyaml>=6.0.3", + "rich>=14.2.0", + "textual>=6.5.0", + "toml>=0.10.2", ] classifiers = [ "Development Status :: 3 - Alpha", diff --git a/scripts/deploy/1-check-env/check_env.sh b/scripts/deploy/1-check-env/check_env.sh index e848cfcdd308be2f2406e75393d044d81a13b5b3..d54c49410e19f3319d619b432b5a1a82760d5834 100644 --- a/scripts/deploy/1-check-env/check_env.sh +++ b/scripts/deploy/1-check-env/check_env.sh @@ -14,22 +14,25 @@ get_el_version() { # 首先检查是否为 openEuler 系统 if [ -f "/etc/openEuler-release" ]; then local openeuler_version - openeuler_version=$(grep -oP 'openEuler release \K[0-9]+\.[0-9]+' /etc/openEuler-release) + openeuler_version=$(grep -Eo 'openEuler release [0-9]+\.[0-9]+' /etc/openEuler-release | awk '{print $3}' | tail -n 1) if [ -n "$openeuler_version" ]; then # 将版本号转换为可比较的数字格式(如 22.03 -> 2203) local major minor major=$(echo "$openeuler_version" | cut -d'.' -f1) minor=$(echo "$openeuler_version" | cut -d'.' -f2) - local version_num=$((major * 100 + minor)) - # openEuler 22.03 及之前使用 el8,24.03 及之后使用 el9 - if [ $version_num -le 2203 ]; then - echo "8" - return 0 - else - echo "9" - return 0 + if [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]]; then + local version_num=$((10#$major * 100 + 10#$minor)) + + # openEuler 22.03 及之前使用 el8,24.03 及之后使用 el9 + if [ $version_num -le 2203 ]; then + echo "8" + return 0 + else + echo "9" + return 0 + fi fi fi fi @@ -43,14 +46,22 @@ get_el_version() { local major minor major=$(echo "$kernel_version" | cut -d'.' -f1) minor=$(echo "$kernel_version" | cut -d'.' -f2) - local version_num=$((major * 100 + minor)) - # 内核版本 < 5.14 使用 el8,>= 5.14 使用 el9 - if [ $version_num -lt 514 ]; then - echo "8" - else - echo "9" + if [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]]; then + local version_num=$((10#$major * 100 + 10#$minor)) + + # 内核版本 < 5.14 使用 el8,>= 5.14 使用 el9 + if [ $version_num -lt 514 ]; then + echo "8" + return 0 + else + echo "9" + return 0 + fi fi + + echo -e "${COLOR_ERROR}[Error] 无法确定兼容的 el 版本,请检查系统信息${COLOR_RESET}" >&2 + return 1 } # 安装wget工具 @@ -336,46 +347,40 @@ function set_hostname { } # 检查单个软件包是否可用 +# 参数: 包名 [备用包名1] [备用包名2] ... check_package() { - local pkg=$1 - if dnf list "$pkg" &>/dev/null; then - echo -e "${COLOR_INFO}[Info] $(printf '%-30s' "$pkg") \t(可用)${COLOR_RESET}" + local primary_pkg=$1 + shift + local alternate_pkgs=("$@") + + # 先检查主包名 + if dnf list "$primary_pkg" &>/dev/null; then + echo -e "${COLOR_INFO}[Info] $(printf '%-30s' "$primary_pkg") \t(可用)${COLOR_RESET}" return 0 + fi + + # 如果主包名不可用,尝试备用包名 + for alt_pkg in "${alternate_pkgs[@]}"; do + if dnf list "$alt_pkg" &>/dev/null; then + echo -e "${COLOR_INFO}[Info] $(printf '%-30s' "$primary_pkg") \t(可用,使用 $alt_pkg)${COLOR_RESET}" + return 0 + fi + done + + # 所有包名都不可用 + if [ ${#alternate_pkgs[@]} -gt 0 ]; then + echo -e "${COLOR_ERROR}[Error] $(printf '%-30s' "$primary_pkg") \t(不可用,已尝试: ${alternate_pkgs[*]})${COLOR_RESET}" else - echo -e "${COLOR_ERROR}[Error] $(printf '%-30s' "$pkg") \t(不可用)${COLOR_RESET}" - return 1 + echo -e "${COLOR_ERROR}[Error] $(printf '%-30s' "$primary_pkg") \t(不可用)${COLOR_RESET}" fi + return 1 } -# 需要检查的软件包列表 -PACKAGES=( - "euler-copilot-web" - "euler-copilot-witchaind-web" - "authHub" - "authhub-web" - "euler-copilot-rag" - "euler-copilot-framework" - "nginx" - "redis" - "mysql" - "mysql-server" - "java-17-openjdk" - "postgresql-server" - "postgresql-server-devel" - "postgresql" - "libpq-devel" - "git" - "make" - "gcc" - "gcc-c++" - "clang" - "llvm" - "tar" - "python3-pip" -) + all_available=true # 检查所有软件包 -check_all_packages() { - local PACKAGES=("$@") +# 包名格式: "package_name" 或 "package_name:alternate1:alternate2" +check_packages() { + local packages=("$@") local timeout_seconds=30 local start_time @@ -383,7 +388,7 @@ check_all_packages() { echo -e "${COLOR_INFO}--------------------------------${COLOR_RESET}" - for pkg in "${PACKAGES[@]}"; do + for pkg_spec in "${packages[@]}"; do # 检查是否超时 local current_time current_time=$(date +%s) @@ -394,17 +399,19 @@ check_all_packages() { echo -e "${COLOR_INFO}--------------------------------${COLOR_RESET}" return 2 fi - if ! check_package "$pkg"; then + + # 分割包名和备用名(使用冒号分隔) + IFS=':' read -ra pkg_names <<<"$pkg_spec" + if ! check_package "${pkg_names[@]}"; then all_available=false fi sleep 0.1 # 避免请求过快 done - } check_web_pkg() { local pkgs=( "nginx" - "redis" + "redis:redis6" # 支持 redis 或 redis6 包名 "mysql" "mysql-server" "authHub" @@ -412,7 +419,7 @@ check_web_pkg() { "euler-copilot-web" "euler-copilot-witchaind-web" ) - if ! check_all_packages "${pkgs[@]}"; then + if ! check_packages "${pkgs[@]}"; then return 1 fi } @@ -426,7 +433,7 @@ check_framework_pkg() { "tar" "python3-pip" ) - if ! check_all_packages "${pkgs[@]}"; then + if ! check_packages "${pkgs[@]}"; then return 1 fi } @@ -441,7 +448,7 @@ check_rag_pkg() { "postgresql-server-devel" "libpq-devel" ) - if ! check_all_packages "${pkgs[@]}"; then + if ! check_packages "${pkgs[@]}"; then return 1 fi } diff --git a/scripts/deploy/2-install-dependency/install_openEulerIntelligence.sh b/scripts/deploy/2-install-dependency/install_openEulerIntelligence.sh index e2eaf1e0ce546eef98eb01c8a08399197ef28f9a..01eb57ad1d62668ac6e0d68651d63a2eb4677db7 100644 --- a/scripts/deploy/2-install-dependency/install_openEulerIntelligence.sh +++ b/scripts/deploy/2-install-dependency/install_openEulerIntelligence.sh @@ -18,22 +18,25 @@ get_el_version() { # 首先检查是否为 openEuler 系统 if [ -f "/etc/openEuler-release" ]; then local openeuler_version - openeuler_version=$(grep -oP 'openEuler release \K[0-9]+\.[0-9]+' /etc/openEuler-release) + openeuler_version=$(grep -Eo 'openEuler release [0-9]+\.[0-9]+' /etc/openEuler-release | awk '{print $3}' | tail -n 1) if [ -n "$openeuler_version" ]; then # 将版本号转换为可比较的数字格式(如 22.03 -> 2203) local major minor major=$(echo "$openeuler_version" | cut -d'.' -f1) minor=$(echo "$openeuler_version" | cut -d'.' -f2) - local version_num=$((major * 100 + minor)) - # openEuler 22.03 及之前使用 el8,24.03 及之后使用 el9 - if [ $version_num -le 2203 ]; then - echo "8" - return 0 - else - echo "9" - return 0 + if [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]]; then + local version_num=$((10#$major * 100 + 10#$minor)) + + # openEuler 22.03 及之前使用 el8,24.03 及之后使用 el9 + if [ $version_num -le 2203 ]; then + echo "8" + return 0 + else + echo "9" + return 0 + fi fi fi fi @@ -47,14 +50,22 @@ get_el_version() { local major minor major=$(echo "$kernel_version" | cut -d'.' -f1) minor=$(echo "$kernel_version" | cut -d'.' -f2) - local version_num=$((major * 100 + minor)) - # 内核版本 < 5.14 使用 el8,>= 5.14 使用 el9 - if [ $version_num -lt 514 ]; then - echo "8" - else - echo "9" + if [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]]; then + local version_num=$((10#$major * 100 + 10#$minor)) + + # 内核版本 < 5.14 使用 el8,>= 5.14 使用 el9 + if [ $version_num -lt 514 ]; then + echo "8" + return 0 + else + echo "9" + return 0 + fi fi + + echo -e "${COLOR_ERROR}[Error] 无法确定兼容的 el 版本,请检查系统信息${COLOR_RESET}" >&2 + return 1 } # 初始化本地仓库 @@ -207,46 +218,57 @@ install_minio() { esac } # 智能安装函数 +# 参数: 包名 或 "包名:备用包名1:备用包名2" smart_install() { - local pkg=$1 + local pkg_spec=$1 local retry=3 local LOCAL_REPO_DIR="/var/cache/rpms" local use_local=false + # 解析包名和备用包名 + IFS=':' read -ra pkg_names <<<"$pkg_spec" + local primary_pkg="${pkg_names[0]}" + # 检查本地仓库目录是否存在 if [ -d "$LOCAL_REPO_DIR" ]; then use_local=true fi - echo -e "${COLOR_INFO}[Info] 正在安装 $pkg ...${COLOR_RESET}" + echo -e "${COLOR_INFO}[Info] 正在安装 $primary_pkg ...${COLOR_RESET}" - while [ $retry -gt 0 ]; do - # 本地安装模式(仅在本地仓库可用时尝试) - if [[ "$use_local" == true ]]; then - # 检查本地是否存在包(支持模糊匹配) - local local_pkg - local_pkg=$(find "$LOCAL_REPO_DIR" -name "${pkg}-*.rpm" | head -1) + # 尝试安装主包名和备用包名 + for pkg in "${pkg_names[@]}"; do + local current_retry=$retry - if [[ -n "$local_pkg" ]]; then - if dnf --disablerepo='*' --enablerepo=local-rpms install -y "$pkg"; then - installed_pkgs+=("$pkg") - return 0 + while [ $current_retry -gt 0 ]; do + # 本地安装模式(仅在本地仓库可用时尝试) + if [[ "$use_local" == true ]]; then + # 检查本地是否存在包(支持模糊匹配) + local local_pkg + local_pkg=$(find "$LOCAL_REPO_DIR" -name "${pkg}-*.rpm" | head -1) + + if [[ -n "$local_pkg" ]]; then + if dnf --disablerepo='*' --enablerepo=local-rpms install -y "$pkg"; then + installed_pkgs+=("$primary_pkg") + return 0 + fi fi fi - fi - # 在线安装模式(本地仓库不可用或本地安装失败) - if dnf install -y "$pkg"; then - installed_pkgs+=("$pkg") - return 0 - fi + # 在线安装模式(本地仓库不可用或本地安装失败) + if dnf install -y "$pkg"; then + installed_pkgs+=("$primary_pkg") + return 0 + fi - ((retry--)) - sleep 1 + ((current_retry--)) + sleep 1 + done done - echo "${COLOR_ERROR}[Error] 错误: $pkg 安装失败!${COLOR_RESET}" - missing_pkgs+=("$pkg") + # 所有包名都尝试失败 + echo "${COLOR_ERROR}[Error] 错误: $primary_pkg 安装失败!${COLOR_RESET}" + missing_pkgs+=("$primary_pkg") install_success=false return 1 @@ -756,7 +778,7 @@ install_rag() { install_web() { local pkgs=( "nginx" - "redis" + "redis:redis6" # 支持 redis 或 redis6 包名 "mysql" "mysql-server" "authHub" diff --git a/scripts/deploy/2-install-dependency/uninstall_dependency.sh b/scripts/deploy/2-install-dependency/uninstall_dependency.sh index c2187f3c9ee39b4f2b6283c61c2048174493510f..31c09d8ef071cd7bad3b4eaea3c4eb21c1f607ab 100644 --- a/scripts/deploy/2-install-dependency/uninstall_dependency.sh +++ b/scripts/deploy/2-install-dependency/uninstall_dependency.sh @@ -8,9 +8,10 @@ COLOR_RESET='\033[0m' # 重置颜色 declare -a uninstalled_pkgs=() uninstall_success=true missing_pkgs=() +# 包名格式: "package_name" 或 "package_name:alternate1:alternate2" pkgs=( "nginx" - "redis" + "redis:redis6" # 支持 redis 或 redis6 包名 "mysql" "java-17-openjdk" "postgresql" @@ -76,33 +77,45 @@ uninstall_dependency() { trap cleanup INT TERM ERR # 检查并卸载每个包 - for pkg in "${pkgs[@]}"; do + for pkg_spec in "${pkgs[@]}"; do + # 解析包名和备用包名(使用冒号分隔) + IFS=':' read -ra pkg_names <<<"$pkg_spec" + local primary_pkg="${pkg_names[0]}" + # MinIO 使用专门的卸载函数处理 - if [ "$pkg" = "minio" ]; then + if [ "$primary_pkg" = "minio" ]; then uninstall_minio || { uninstall_success=false - missing_pkgs+=("$pkg") + missing_pkgs+=("$primary_pkg") } continue fi - if rpm -q "$pkg" >/dev/null 2>&1; then - echo -e "${COLOR_INFO}[Info] 正在卸载 $pkg...${COLOR_RESET}" - if [ "$pkg" = "nginx" ]; then - dnf remove -y nginx >/dev/null 2>&1 - elif dnf remove -y "$pkg" >/dev/null 2>&1; then - uninstalled_pkgs+=("$pkg") - else - echo -e "${COLOR_ERROR}[Error] 卸载 $pkg 失败!${COLOR_RESET}" - uninstall_success=false - missing_pkgs+=("$pkg") - cleanup + # 尝试检查并卸载主包名或备用包名 + local pkg_found=false + for pkg in "${pkg_names[@]}"; do + if rpm -q "$pkg" >/dev/null 2>&1; then + pkg_found=true + echo -e "${COLOR_INFO}[Info] 正在卸载 $pkg...${COLOR_RESET}" + if [ "$pkg" = "nginx" ]; then + dnf remove -y nginx >/dev/null 2>&1 + elif dnf remove -y "$pkg" >/dev/null 2>&1; then + uninstalled_pkgs+=("$pkg") + break # 成功卸载后跳出循环 + else + echo -e "${COLOR_ERROR}[Error] 卸载 $pkg 失败!${COLOR_RESET}" + uninstall_success=false + missing_pkgs+=("$pkg") + cleanup + fi fi - else - echo -e "${COLOR_INFO}[Info] $pkg 未安装,跳过...${COLOR_RESET}" - fi + done + if [ "$pkg_found" = false ]; then + echo -e "${COLOR_INFO}[Info] $primary_pkg 未安装,跳过...${COLOR_RESET}" + fi done + # 取消捕获 trap - INT TERM ERR # 检查安装结果 @@ -160,11 +173,11 @@ delete_dir() { local target="$BASE_PWD/$dir" # 检查目录是否存在 - if ls -d $target &>/dev/null; then + if ls -d "$target" &>/dev/null; then echo -e "${COLOR_INFO}[Info] 正在删除: $target${COLOR_RESET}" | tee -a "$LOG_FILE" # 实际删除操作 - if rm -rf $target; then + if rm -rf "$target"; then deleted_dirs+=("$target") echo -e "${COLOR_INFO}[Info] 成功删除: $target${COLOR_RESET}" | tee -a "$LOG_FILE" else diff --git a/scripts/deploy/4-other-script/agent_manager.py b/scripts/deploy/4-other-script/agent_manager.py index dbd7fc5b0b791e60f24be190b822df8fc7e87eac..53d9dbdbc07f7a028fcc43c137f2df7d86c3b7b4 100644 --- a/scripts/deploy/4-other-script/agent_manager.py +++ b/scripts/deploy/4-other-script/agent_manager.py @@ -361,17 +361,16 @@ async def process_mcp_config(api_client: ApiClient, config_path: str) -> str: async def query_mcp_server(api_client: ApiClient, mcp_id: str) -> dict[str, Any] | None: """查询MCP服务状态""" logger.debug("查询MCP服务状态: %s", mcp_id) - response = await api_client.request("GET", "/api/mcp") + response = await api_client.request("GET", "/api/mcp/"+mcp_id) if response.get("code") != HTTP_OK: msg = f"查询MCP服务失败: {response.get('message', '未知错误')}" raise RuntimeError(msg) - services = response.get("result", {}).get("services", []) - for service in services: - if service.get("mcpserviceId") == mcp_id: - logger.debug("MCP服务 %s 状态: %s", mcp_id, service.get("status")) - return service + service = response.get("result", {}) + if service.get("serviceId") == mcp_id: + logger.debug("MCP服务 %s 状态: %s", mcp_id, service.get("status")) + return service return None @@ -483,9 +482,7 @@ async def create_agent(api_client: ApiClient, config_path: str) -> None: await install_mcp_server(api_client, service_id) mcp_server = await wait_for_mcp_service(api_client, service_id) - # 激活服务(如果未激活) - if not mcp_server.get("isActive"): - await activate_mcp_server(api_client, service_id) + await activate_mcp_server(api_client, service_id) # 创建应用数据 app_name = mcp_server.get("name", f"agent_{service_id[:6]}")[:20] diff --git a/scripts/tools/i18n-manager.sh b/scripts/tools/i18n-manager.sh index ef38b42c68b1330c5fb3031d66c81fab32e4af38..012767f35ec0c07ac8c204690f6d84ee93898a78 100755 --- a/scripts/tools/i18n-manager.sh +++ b/scripts/tools/i18n-manager.sh @@ -52,6 +52,10 @@ show_help() { echo " 更新所有语言的翻译文件" print_green " compile" echo " 编译翻译文件为二进制格式" + print_green " uniq" + echo " 去除翻译文件中的重复条目" + print_green " stats" + echo " 显示翻译文件的统计信息" print_green " all" echo " 执行完整流程 (extract -> update -> compile)" print_green " help" @@ -60,6 +64,8 @@ show_help() { echo "示例:" echo " $0 extract # 提取可翻译字符串" echo " $0 compile # 编译翻译文件" + echo " $0 uniq # 去除重复条目" + echo " $0 stats # 查看翻译统计" echo " $0 all # 完整翻译工作流" echo "" echo "更多信息请参考: docs/development/国际化开发指南.md" @@ -93,7 +99,7 @@ extract() { --output="$POT_FILE" \ --from-code=UTF-8 \ --package-name=oi-cli \ - --package-version=0.10.2 \ + --package-version=2.0.0 \ --msgid-bugs-address=contact@openeuler.org \ --copyright-holder="openEuler Intelligence Project" \ --add-comments=Translators \ @@ -212,6 +218,102 @@ compile() { fi } +# 去除重复的翻译条目 +uniq() { + print_blue "🔧 去除重复的翻译条目..." + + check_gettext + + if ! command -v msguniq &>/dev/null; then + print_red "❌ msguniq command not found. Please install gettext tools." + exit 1 + fi + + processed=0 + failed=0 + + # 遍历所有语言目录 + for locale_path in "$LOCALE_DIR"/*; do + if [ ! -d "$locale_path" ]; then + continue + fi + + locale_name=$(basename "$locale_path") + po_file="$locale_path/LC_MESSAGES/messages.po" + + if [ ! -f "$po_file" ]; then + print_yellow "⚠️ Skipping $locale_name: PO file not found" + continue + fi + + echo " Processing $locale_name..." + # 创建临时文件 + temp_file="${po_file}.tmp" + + set +e + set +o pipefail + if msguniq --use-first "$po_file" -o "$temp_file" 2>/dev/null; then + mv "$temp_file" "$po_file" + echo " ✅ Processed $locale_name" + processed=$((processed + 1)) + else + print_yellow " ⚠️ Failed to process $locale_name" + rm -f "$temp_file" + failed=$((failed + 1)) + fi + set -e + set -o pipefail + done + + echo "" + if [ "$processed" -gt 0 ]; then + print_green "✅ Successfully processed $processed translation file(s)" + fi + + if [ "$failed" -gt 0 ]; then + print_yellow "⚠️ Failed to process $failed translation file(s)" + fi + + if [ "$processed" -eq 0 ] && [ "$failed" -eq 0 ]; then + print_yellow "⚠️ No translation files found to process" + fi +} + +# 显示翻译统计信息 +stats() { + print_blue "📊 翻译统计信息..." + + check_gettext + + found=0 + + # 遍历所有语言目录 + for locale_path in "$LOCALE_DIR"/*; do + if [ ! -d "$locale_path" ]; then + continue + fi + + locale_name=$(basename "$locale_path") + po_file="$locale_path/LC_MESSAGES/messages.po" + + if [ ! -f "$po_file" ]; then + print_yellow "⚠️ Skipping $locale_name: PO file not found" + continue + fi + + echo "" + print_green "=== $locale_name ===" + msgfmt --statistics "$po_file" 2>&1 || true + found=$((found + 1)) + done + + if [ "$found" -eq 0 ]; then + echo "" + print_yellow "⚠️ No translation files found" + fi + echo "" +} + # 执行完整流程 all() { extract @@ -241,6 +343,12 @@ main() { compile) compile ;; + uniq) + uniq + ;; + stats) + stats + ;; all) all ;; diff --git a/src/__version__.py b/src/__version__.py index a0a887adf831ab4a0d88086a71999e95138949aa..d6fc4d3fc2afe5cba3ac07bb8db539bc47a6d541 100644 --- a/src/__version__.py +++ b/src/__version__.py @@ -1,3 +1,3 @@ """版本信息模块""" -__version__ = "0.10.2" +__version__ = "2.0.0" diff --git a/src/app/css/styles.tcss b/src/app/css/styles.tcss index c89bbfa4e3e06f2b3f815f5bc3b63f1eb917a08d..a5919eb7fc8b08a50d0a8176d5e7246631949397 100644 --- a/src/app/css/styles.tcss +++ b/src/app/css/styles.tcss @@ -60,8 +60,10 @@ SettingsScreen { #settings-screen { align: center middle; - width: 80%; + width: 85%; height: 95%; + max-width: 120; + max-height: 50; color: #ffffff; } @@ -95,12 +97,50 @@ SettingsScreen { margin-top: 1; } -/* 设置值样式 */ +/* 设置值样式 (可编辑类型) */ .settings-input { width: 80%; content-align: left middle; } +/* 设置值容器(用于包含值和徽章) */ +.settings-value-container { + margin-left: 1; + background: $surface; + width: 80%; + height: 3; + align: left middle; +} + +/* 设置值样式 (只读类型) */ +.settings-value { + margin-left: 1; + width: auto; + height: 3; +} + +/* 用户类型徽章样式 */ +.user-type-badge { + width: 20%; + height: 3; + text-style: bold; + text-align: right; + margin-top: 1; + margin-right: 1; + padding: 0 1; + dock: right; +} + +/* 管理员徽章颜色 */ +.user-type-admin { + color: $warning; +} + +/* 普通用户徽章颜色 */ +.user-type-user { + color: $primary; +} + /* 设置按钮样式 */ .settings-button { content-align: left middle; @@ -290,15 +330,6 @@ BackendRequiredDialog { margin-bottom: 1; } -.confirm-reason { - text-align: left; - color: #cccccc; - height: auto; - padding: 0 1; - margin-bottom: 1; - text-style: italic; -} - .confirm-buttons { height: 3; align: center middle; @@ -351,14 +382,6 @@ BackendRequiredDialog { margin-bottom: 0; } -.param-message { - text-align: left; - color: #ffffff; - height: auto; - padding: 0 1; - margin-bottom: 0; -} - .param-input-compact { width: 100%; margin-bottom: 0; @@ -485,3 +508,164 @@ BackendRequiredDialog { min-height: 3; height: 3; } + +/* 用户配置对话框样式 */ +UserConfigDialog { + align: center middle; +} + +#user-dialog-screen { + align: center middle; + width: 80%; + height: 80%; + max-width: 100; + max-height: 40; +} + +#user-dialog { + border: solid #4963b1; + padding-left: 1; + padding-right: 1; + padding-bottom: 0; + width: 100%; + height: 100%; +} + +/* 标签页容器 */ +#user-tabs { + height: 1fr; + margin: 1 0; +} + +/* 标签页面板 */ +#general-tab, #llm-tab { + layout: vertical; +} + +/* 大模型设置标签页内容容器 */ +.llm-tab-content { + height: 1fr; + width: 100%; +} + +/* 常规设置表单样式 */ +.general-settings-form { + padding: 2; + width: 100%; + height: 1fr; +} + +.form-buttons { + height: 3; + align: center middle; + margin-top: 2; +} + +.form-buttons > Button { + margin: 0 1; + width: auto; + min-height: 3; + height: 3; +} + +/* 模型列表容器 */ +.llm-model-list { + height: auto; + max-height: 1fr; + overflow-y: auto; + scrollbar-size: 1 1; + border: solid #688efd; + padding: 1; + margin: 1 0; +} + +/* 模型项样式 */ +.llm-model-item { + padding: 0 1; + color: #ffffff; +} + +/* 已保存的模型(当前配置中保存的) */ +.llm-model-saved { + color: #888888; + text-style: italic; +} + +/* 已激活的模型(用空格确认,等待保存) */ +.llm-model-activated { + color: #4caf50; + text-style: bold; +} + +/* 光标所在的模型 */ +.llm-model-cursor { + background: rgba(104, 142, 253, 0.3); +} + +/* 模型详情容器 */ +.llm-model-detail { + border: solid #888888; + padding: 1; + margin: 1 0; + background: rgba(73, 99, 177, 0.1); + height: auto; + max-height: 15; + overflow-y: auto; + scrollbar-size: 1 1; + dock: bottom; +} + +/* 详情行 */ +.llm-detail-row { + height: auto; + padding: 0; + margin: 0; +} + +/* 详情标签 */ +.llm-detail-label { + color: #888888; + width: auto; + padding-right: 1; + margin-top: 1; + text-align: right; +} + +/* 详情值 */ +.llm-detail-value { + color: #ffffff; + width: 1fr; +} + +/* 常规设置按钮区域 */ +#general-buttons { + height: auto; + min-height: 3; + align: center middle; + padding: 0; + dock: bottom; +} + +#general-buttons > Button { + margin: 0 1; + width: auto; + min-height: 3; + height: 3; +} + +/* 大模型设置帮助文本 */ +.llm-dialog-help { + text-align: center; + color: #888888; + padding: 0 1; + height: auto; + min-height: 1; + dock: bottom; +} + +/* 加载状态 */ +.llm-loading, .llm-error, .llm-empty { + text-align: center; + color: #888888; + padding: 2; +} diff --git a/src/app/deployment/agent.py b/src/app/deployment/agent.py index 07137eb783ef6641bac12c7b1b6fc6aee6a9fd19..b16fc9b9b7b7ed129bfbf076efcbb5d46ec7dd0a 100644 --- a/src/app/deployment/agent.py +++ b/src/app/deployment/agent.py @@ -6,44 +6,39 @@ Agent 管理模块。 该模块提供: - McpConfig: MCP 配置数据模型 - McpConfigLoader: MCP 配置文件加载器 -- ApiClient: HTTP API 客户端 - AgentManager: 智能体管理器主类 """ from __future__ import annotations import asyncio +import copy import json import subprocess -from dataclasses import dataclass +import tomllib +import uuid +from dataclasses import dataclass, field from pathlib import Path from typing import TYPE_CHECKING, Any -import httpx -import toml +import yaml from config.manager import ConfigManager +from i18n.manager import _ from log.manager import get_logger from .models import AgentInitStatus, DeploymentState if TYPE_CHECKING: - from collections.abc import Awaitable, Callable + from collections.abc import Callable logger = get_logger(__name__) -# HTTP 状态码常量 -HTTP_OK = 200 - class ConfigError(Exception): """配置错误异常""" -class ApiError(Exception): - """API 错误异常""" - - @dataclass class McpConfig: """MCP 配置模型""" @@ -53,26 +48,7 @@ class McpConfig: overview: str config: dict[str, Any] mcp_type: str - - -@dataclass -class McpServerInfo: - """MCP 服务信息""" - - service_id: str - name: str - config_path: Path - config: McpConfig - - -@dataclass -class AgentInfo: - """智能体信息""" - - app_id: str - name: str - description: str - mcp_services: list[str] + author: str = "openEuler" @dataclass @@ -84,6 +60,31 @@ class AppConfig: description: str mcp_path: list[str] published: bool = True + version: str = "1.0.0" + author: str = "openEuler" + history_len: int = 3 + icon: str = "" + + +@dataclass +class AppMetadata: + """智能体元数据(用于写入 YAML 文件)""" + + type: str = "app" + id: str = field(default_factory=lambda: str(uuid.uuid4())) + icon: str = "" + name: str = "" + description: str = "" + version: str = "1.0.0" + author: str = "openEuler" + app_type: str = "agent" + published: bool = True + history_len: int = 3 + mcp_service: list[str] = field(default_factory=list) + llm_id: str = "empty" + permission: dict[str, Any] = field( + default_factory=lambda: {"type": "public", "users": []}, + ) class McpConfigLoader: @@ -93,8 +94,14 @@ class McpConfigLoader: """初始化配置加载器""" self.config_dir = config_dir - def load_all_configs(self) -> list[tuple[Path, McpConfig]]: - """加载所有 MCP 配置""" + def load_all_configs(self) -> list[tuple[str, McpConfig]]: + """ + 加载所有 MCP 配置 + + Returns: + list[tuple[str, McpConfig]]: (目录名, 配置对象) 的列表 + + """ configs = [] if not self.config_dir.exists(): msg = f"配置目录不存在: {self.config_dir}" @@ -107,10 +114,10 @@ class McpConfigLoader: if config_file.exists(): try: config = self._load_config(config_file, subdir.name) - configs.append((config_file, config)) - logger.info("加载 MCP 配置: %s", subdir.name) - except (json.JSONDecodeError, KeyError): - logger.exception("加载配置文件失败: %s", config_file) + configs.append((subdir.name, config)) + logger.debug("成功加载 MCP 配置: %s", subdir.name) + except Exception: + logger.exception("加载 MCP 配置失败: %s", config_file) continue if not configs: @@ -130,260 +137,23 @@ class McpConfigLoader: overview=config_data.get("overview", name), config=config_data.get("config", {}), mcp_type=config_data.get("mcpType", "sse"), + author=config_data.get("author", "openEuler"), ) -class ApiClient: - """API 客户端""" - - def __init__(self, server_ip: str, server_port: int) -> None: - """初始化 API 客户端""" - self.base_url = f"http://{server_ip}:{server_port}" - self.timeout = 10.0 - - async def register_mcp_service(self, config: McpConfig) -> str: - """注册 MCP 服务""" - url = f"{self.base_url}/api/mcp" - payload = { - "name": config.name, - "description": config.description, - "overview": config.overview, - "config": config.config, - "mcpType": config.mcp_type, - } - - logger.info("注册 MCP 服务: %s", config.name) - async with httpx.AsyncClient(timeout=self.timeout) as client: - try: - response = await client.post(url, json=payload) - response.raise_for_status() - - result = response.json() - if result.get("code") != HTTP_OK: - msg = f"注册 MCP 服务失败: {result.get('message', 'Unknown error')}" - logger.error(msg) - raise ApiError(msg) - - service_id = result["result"]["serviceId"] - logger.info("MCP 服务注册成功: %s -> %s", config.name, service_id) - - except httpx.RequestError as e: - msg = f"注册 MCP 服务网络错误: {e}" - logger.exception(msg) - raise ApiError(msg) from e - - else: - return service_id - - async def install_mcp_service(self, service_id: str) -> None: - """安装 MCP 服务""" - url = f"{self.base_url}/api/mcp/{service_id}/install?install=true" - - logger.info("安装 MCP 服务: %s", service_id) - async with httpx.AsyncClient(timeout=self.timeout) as client: - try: - response = await client.post(url) - response.raise_for_status() - logger.info("MCP 服务安装请求已发送: %s", service_id) - except httpx.RequestError as e: - msg = f"安装 MCP 服务网络错误: {e}" - logger.exception(msg) - raise ApiError(msg) from e - - async def check_mcp_service_status(self, service_id: str) -> str | None: - """ - 检查 MCP 服务状态 - - 返回值: - - "ready": 安装完成且成功 - - "failed": 安装失败 - - "cancelled": 安装取消 - - "init": 初始化中 - - "installing": 安装中 - - None: 网络错误或无法获取状态 - """ - url = f"{self.base_url}/api/mcp/{service_id}" - - async with httpx.AsyncClient(timeout=self.timeout) as client: - try: - response = await client.get(url) - response.raise_for_status() - - result = response.json() - # 检查 API 调用是否成功 - if result.get("code") != HTTP_OK: - logger.warning("获取 MCP 服务状态失败: %s", result.get("message", "Unknown error")) - return None - - # 获取服务状态 - service_result = result.get("result", {}) - status = service_result.get("status") - - if status in ("ready", "failed", "cancelled", "init", "installing"): - return status - - logger.warning("未知的 MCP 服务状态: %s", status) - - except httpx.RequestError as e: - logger.debug("检查 MCP 服务状态网络错误: %s", e) - - return None - - async def wait_for_installation( - self, - service_id: str, - max_wait_time: int = 300, - check_interval: int = 2, - ) -> bool: - """ - 等待 MCP 服务安装完成 - - 只要接口能打通、后端返回的状态没有明确成功或失败或取消,就会一直等下去。 - 只有在明确失败或取消时才返回 False。 - """ - logger.info("等待 MCP 服务安装完成: %s", service_id) - - attempt = 0 - while True: - status = await self.check_mcp_service_status(service_id) - - if status == "ready": - logger.info("MCP 服务安装完成: %s", service_id) - return True - - if status in ("failed", "cancelled"): - logger.error("MCP 服务安装失败或被取消: %s (状态: %s)", service_id, status) - return False - - if status in ("init", "installing"): - logger.debug( - "MCP 服务 %s %s中... (第 %d 次检查)", - service_id, - "初始化" if status == "init" else "安装", - attempt + 1, - ) - elif status is None: - logger.debug("MCP 服务 %s 状态检查失败,继续等待... (第 %d 次检查)", service_id, attempt + 1) - else: - logger.debug("MCP 服务 %s 状态未知: %s,继续等待... (第 %d 次检查)", service_id, status, attempt + 1) - - # 只有在超过最大等待时间时才超时返回,但仅在没有明确失败的情况下 - attempt += 1 - if attempt * check_interval >= max_wait_time: - # 这里不返回 False,而是继续等待,因为要求只要接口能打通就一直等 - logger.warning("MCP 服务安装等待超时: %s (已等待 %d 秒,但将继续尝试)", service_id, max_wait_time) - - await asyncio.sleep(check_interval) - - async def activate_mcp_service(self, service_id: str) -> None: - """激活 MCP 服务""" - url = f"{self.base_url}/api/mcp/{service_id}" - payload = {"active": True} - - logger.info("激活 MCP 服务: %s", service_id) - async with httpx.AsyncClient(timeout=self.timeout) as client: - try: - response = await client.post(url, json=payload) - response.raise_for_status() - - result = response.json() - if result.get("code") != HTTP_OK: - msg = f"激活 MCP 服务失败: {result.get('message', 'Unknown error')}" - logger.error(msg) - raise ApiError(msg) - - logger.info("MCP 服务激活成功: %s", service_id) - - except httpx.RequestError as e: - msg = f"激活 MCP 服务网络错误: {e}" - logger.exception(msg) - raise ApiError(msg) from e - - async def create_agent( - self, - name: str, - description: str, - mcp_service_ids: list[str], - ) -> str: - """创建智能体""" - url = f"{self.base_url}/api/app" - payload = { - "appType": "agent", - "name": name, - "description": description, - "mcpService": mcp_service_ids, - "permission": { - "visibility": "public", - }, - } - - logger.info("创建智能体: %s (包含 %d 个 MCP 服务)", name, len(mcp_service_ids)) - async with httpx.AsyncClient(timeout=self.timeout) as client: - try: - response = await client.post(url, json=payload) - response.raise_for_status() - - result = response.json() - if result.get("code") != HTTP_OK: - msg = f"创建智能体失败: {result.get('message', 'Unknown error')}" - logger.error(msg) - raise ApiError(msg) - - app_id = result["result"]["appId"] - logger.info("智能体创建成功: %s -> %s", name, app_id) - - except httpx.RequestError as e: - msg = f"创建智能体网络错误: {e}" - logger.exception(msg) - raise ApiError(msg) from e - - else: - return app_id - - async def publish_agent(self, app_id: str) -> None: - """发布智能体""" - url = f"{self.base_url}/api/app/{app_id}" - - logger.info("发布智能体: %s", app_id) - async with httpx.AsyncClient(timeout=self.timeout) as client: - try: - response = await client.post(url) - response.raise_for_status() - - result = response.json() - if result.get("code") != HTTP_OK: - msg = f"发布智能体失败: {result.get('message', 'Unknown error')}" - logger.error(msg) - raise ApiError(msg) - - logger.info("智能体发布成功: %s", app_id) - - except httpx.RequestError as e: - msg = f"发布智能体网络错误: {e}" - logger.exception(msg) - raise ApiError(msg) from e - - class AgentManager: """智能体管理器""" - def __init__(self, server_ip: str = "127.0.0.1", server_port: int = 8002) -> None: + def __init__(self) -> None: """初始化智能体管理器""" - self.api_client = ApiClient(server_ip, server_port) self.config_manager = ConfigManager() - resource_paths = [ - Path("/usr/lib/euler-copilot-framework/mcp_center"), # 生产环境 - Path("scripts/deploy/5-resource"), # 旧的开发环境(兼容) - Path(__file__).parent.parent.parent / "scripts/deploy/5-resource", # 旧的开发环境(绝对路径兼容) - ] - - self.resource_dir = next((p for p in resource_paths if p.exists()), None) - if not self.resource_dir: - logger.error("[DeploymentHelper] 未找到有效的资源路径") - return - logger.info("[DeploymentHelper] 使用资源路径: %s", self.resource_dir) + self.semantics_dir = Path("/opt/copilot/semantics") + self.mcp_template_dir = self.semantics_dir / "mcp" / "template" + self.app_dir = self.semantics_dir / "app" + # 资源路径 + self.resource_dir = Path("/usr/lib/euler-copilot-framework/mcp_center") self.mcp_config_dir = self.resource_dir / "mcp_config" self.run_script_path = self.resource_dir / "run.sh" self.service_dir = self.resource_dir / "service" @@ -405,14 +175,18 @@ class AgentManager: AgentInitStatus: 初始化状态 (SUCCESS/SKIPPED/FAILED) """ - self._report_progress(state, "[bold blue]开始初始化智能体...[/bold blue]", progress_callback) + self._report_progress( + state, + _("[bold blue]开始初始化智能体...[/bold blue]"), + progress_callback, + ) try: # 执行所有初始化步骤 return await self._execute_initialization_steps(state, progress_callback) except Exception: - error_msg = "智能体初始化失败" + error_msg = _("智能体初始化失败") self._report_progress(state, f"[red]{error_msg}[/red]", progress_callback) logger.exception(error_msg) return AgentInitStatus.FAILED @@ -435,29 +209,64 @@ class AgentManager: if not await self._verify_mcp_services(state, progress_callback): return AgentInitStatus.FAILED - # 4. 加载 MCP 配置并注册服务 - mcp_service_mapping = await self._register_all_mcp_services(state, progress_callback) + # 4. 停止 oi-runtime 服务,准备写入配置 + if not await self._stop_oi_runtime_service(state, progress_callback): + self._report_progress( + state, + _("[yellow]停止 oi-runtime 服务失败,但继续执行[/yellow]"), + progress_callback, + ) + + # 5. 写入 MCP 配置到文件系统 + mcp_service_mapping = await self._write_mcp_configs_to_filesystem( + state, + progress_callback, + ) if not mcp_service_mapping: return AgentInitStatus.FAILED - # 5. 读取应用配置并创建智能体 - default_app_id = await self._create_agents_from_config( + # 6. 读取应用配置并写入智能体元数据 + default_app_id = await self._write_app_metadata_to_filesystem( mcp_service_mapping, state, progress_callback, ) - if default_app_id: + # 7. 重新启动 oi-runtime 服务 + if not await self._start_oi_runtime_service(state, progress_callback): self._report_progress( state, - f"[bold green]智能体初始化完成! 默认 App ID: {default_app_id}[/bold green]", + _("[yellow]启动 oi-runtime 服务失败,请手动检查[/yellow]"), progress_callback, ) - logger.info("智能体初始化成功完成,默认 App ID: %s", default_app_id) + + if default_app_id: + configured = self._update_default_app_config(default_app_id) + + message = ( + _( + "[bold green]智能体初始化完成! 默认 App ID: {app_id}[/bold green]", + ).format(app_id=default_app_id) + if configured + else _( + "[bold yellow]智能体初始化完成,但默认 App 未写入配置[/bold yellow]", + ) + ) + + self._report_progress(state, message, progress_callback) + logger.info( + "智能体初始化成功完成,默认 App ID: %s,写入配置: %s", + default_app_id, + configured, + ) return AgentInitStatus.SUCCESS # 如果没有创建任何智能体,显示警告并返回成功状态 - self._report_progress(state, "[yellow]未能创建任何智能体[/yellow]", progress_callback) + self._report_progress( + state, + _("[yellow]未能创建任何智能体[/yellow]"), + progress_callback, + ) return AgentInitStatus.SUCCESS def _report_progress( @@ -471,960 +280,1053 @@ class AgentManager: if callback: callback(state) - def _get_service_files( + async def _write_mcp_configs_to_filesystem( self, state: DeploymentState, callback: Callable[[DeploymentState], None] | None, - operation_name: str, - ) -> list[Path] | None: + ) -> dict[str, str]: """ - 获取服务文件列表的通用方法 + 将 MCP 配置写入文件系统 Returns: - list[Path]: 服务文件列表,如果应该跳过操作则返回 None - - """ - if not self.service_dir or not self.service_dir.exists(): - self._report_progress( - state, - f"[yellow]服务配置目录不存在: {self.service_dir},跳过{operation_name}[/yellow]", - callback, - ) - logger.warning("服务配置目录不存在: %s", self.service_dir) - return None - - # 获取所有 .service 文件 - service_files = list(self.service_dir.glob("*.service")) - if not service_files: - self._report_progress( - state, - f"[yellow]未找到服务配置文件,跳过{operation_name}[/yellow]", - callback, - ) - return None - - return service_files + dict[str, str]: MCP 路径名 -> 服务 ID (目录名) 的映射 - async def _process_service_files( - self, - service_files: list[Path], - state: DeploymentState, - callback: Callable[[DeploymentState], None] | None, - processor_func: Callable[ - [Path, DeploymentState, Callable[[DeploymentState], None] | None], - Awaitable[tuple[bool, str]], - ], - ) -> tuple[bool, list[str], list[str]]: """ - 处理服务文件的通用框架 + self._report_progress( + state, + _("[cyan]写入 MCP 配置到文件系统...[/cyan]"), + callback, + ) - Args: - service_files: 要处理的服务文件列表 - state: 部署状态 - callback: 进度回调函数 - processor_func: 处理单个文件的函数,返回 (成功标志, 文件名) + # 确保目标目录存在 + self.mcp_template_dir.mkdir(parents=True, exist_ok=True) - Returns: - tuple[bool, list[str], list[str]]: (总体是否成功, 成功的文件列表, 失败的文件列表) + # 加载 MCP 配置 + configs = await self._load_mcp_configs(state, callback) + if not configs: + return {} - """ - success_files = [] - failed_files = [] + mcp_service_mapping = {} - for service_file in service_files: + for dir_name, config in configs: try: - success, file_identifier = await processor_func(service_file, state, callback) - if success: - success_files.append(file_identifier) - else: - failed_files.append(file_identifier) + # 写入配置文件 + service_id = await self._write_single_mcp_config( + dir_name, + config, + state, + callback, + ) + if service_id: + mcp_service_mapping[dir_name] = service_id + except Exception: - file_identifier = service_file.stem self._report_progress( state, - f" [red]处理 {file_identifier} 时发生异常[/red]", + _(" [red]处理 {name} 失败[/red]").format(name=config.name), callback, ) - logger.exception("处理服务文件时发生异常: %s", service_file) - failed_files.append(file_identifier) + logger.exception("处理 MCP 配置失败: %s", config.name) + continue - return len(failed_files) == 0, success_files, failed_files + self._report_progress( + state, + _("[green]MCP 配置写入完成,成功 {count} 个[/green]").format( + count=len(mcp_service_mapping), + ), + callback, + ) + return mcp_service_mapping - async def _install_service_files( + async def _write_single_mcp_config( self, + dir_name: str, + config: McpConfig, state: DeploymentState, callback: Callable[[DeploymentState], None] | None, - ) -> bool: - """安装 systemd 服务文件""" - self._report_progress(state, "[cyan]安装 systemd 服务文件...[/cyan]", callback) - - # 获取服务文件列表 - service_files = self._get_service_files(state, callback, "服务文件安装") - if service_files is None: - return True - - # 处理所有服务文件 - overall_success, installed_files, failed_files = await self._process_service_files( - service_files, + ) -> str | None: + """写入单个 MCP 配置到文件系统""" + self._report_progress( state, + _(" [blue]写入 {name}...[/blue]").format(name=config.name), callback, - self._install_single_service_file, ) - # 如果有成功安装的文件,重新加载 systemd 配置 - if installed_files: - if not await self._reload_systemd_daemon(state, callback): - return False + try: + target_dir = self.mcp_template_dir / dir_name + target_dir.mkdir(parents=True, exist_ok=True) + + config_data = { + "name": config.name, + "overview": config.overview, + "description": config.description, + "type": config.mcp_type, + "author": config.author, + "config": self._normalize_mcp_config(config.config), + } + + config_file = target_dir / "config.json" + with config_file.open("w", encoding="utf-8") as f: + json.dump(config_data, f, ensure_ascii=False, indent=4) + except Exception: self._report_progress( state, - f"[green]成功安装 {len(installed_files)} 个服务文件[/green]", + _(" [red]{name} 写入失败[/red]").format(name=config.name), callback, ) + logger.exception("写入 MCP 配置失败: %s", config.name) + return None + else: + self._report_progress( + state, + _(" [green]{name} 写入成功 -> {path}[/green]").format( + name=config.name, + path=target_dir, + ), + callback, + ) + logger.info("MCP 配置写入成功: %s -> %s", config.name, target_dir) + return dir_name + + def _normalize_mcp_config(self, raw_config: dict[str, Any]) -> dict[str, Any]: + defaults: dict[str, Any] = { + "autoApprove": [], + "disabled": False, + "auto_install": True, + "timeout": 60, + "description": "", + "headers": {}, + } - return True + if not raw_config: + return copy.deepcopy(defaults) - async def _install_single_service_file( + merged = {**defaults, **raw_config} + + if not isinstance(merged.get("autoApprove"), list): + merged["autoApprove"] = [] + + if not isinstance(merged.get("headers"), dict): + merged["headers"] = {} + + return merged + + async def _write_app_metadata_to_filesystem( self, - service_file: Path, + mcp_service_mapping: dict[str, str], state: DeploymentState, callback: Callable[[DeploymentState], None] | None, - ) -> tuple[bool, str]: - """安装单个服务文件""" - service_name = service_file.name - systemd_dir = Path("/etc/systemd/system") - target_path = systemd_dir / service_name + ) -> str | None: + """ + 从配置文件读取应用信息并写入智能体元数据到文件系统 + + Returns: + str | None: 默认智能体的 UUID,如果失败则返回 None + """ self._report_progress( state, - f" [blue]复制服务文件: {service_name}[/blue]", + _("[cyan]写入智能体元数据到文件系统...[/cyan]"), callback, ) - try: - # 复制服务文件到 systemd 目录 - cmd = f"sudo cp {service_file} {target_path}" - process = await asyncio.create_subprocess_shell( - cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) + # 确保目标目录存在 + self.app_dir.mkdir(parents=True, exist_ok=True) + + # 读取应用配置 + app_configs = await self._load_app_configs(state, callback) + if not app_configs: + return None - stdout, stderr = await process.communicate() + created_agents = [] + default_app_id = None - except Exception: - self._report_progress( - state, - f" [red]复制 {service_name} 时发生异常[/red]", - callback, - ) - logger.exception("复制服务文件时发生异常: %s", service_file) - return False, service_name - else: - if process.returncode == 0: + for i, app_config in enumerate(app_configs): + try: + app_id = await self._write_single_app_metadata( + app_config, + mcp_service_mapping, + state, + callback, + ) + if app_id: + created_agents.append((app_config.name, app_id)) + if i == 0: # 第一个作为默认 + default_app_id = app_id + + except Exception: self._report_progress( state, - f" [green]{service_name} 复制成功[/green]", + _(" [red]创建 {name} 元数据失败[/red]").format( + name=app_config.name, + ), callback, ) - logger.info("服务文件复制成功: %s -> %s", service_file, target_path) - return True, service_name + logger.exception("创建智能体元数据失败: %s", app_config.name) + continue - error_output = stderr.decode("utf-8") if stderr else "" + if created_agents: self._report_progress( state, - f" [red]{service_name} 复制失败: {error_output}[/red]", + _("[green]成功创建 {count} 个智能体[/green]").format( + count=len(created_agents), + ), callback, ) - logger.error("服务文件复制失败: %s, 错误: %s", service_file, error_output) - return False, service_name + for name, app_id in created_agents: + logger.info("创建智能体成功: %s (ID: %s)", name, app_id) - async def _reload_systemd_daemon( + return default_app_id + + async def _write_single_app_metadata( self, + app_config: AppConfig, + mcp_service_mapping: dict[str, str], state: DeploymentState, callback: Callable[[DeploymentState], None] | None, - ) -> bool: - """重新加载 systemd 配置""" - self._report_progress(state, "[cyan]重新加载 systemd 配置...[/cyan]", callback) + ) -> str | None: + """写入单个智能体元数据到文件系统""" + self._report_progress( + state, + _("[magenta]创建智能体元数据: {name}[/magenta]").format( + name=app_config.name, + ), + callback, + ) - try: - cmd = "sudo systemctl daemon-reload" - process = await asyncio.create_subprocess_shell( - cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) + # 将 MCP 路径转换为服务 ID + mcp_service_ids, missing_services = self._resolve_mcp_services( + app_config.mcp_path, + mcp_service_mapping, + ) - stdout, stderr = await process.communicate() - - except Exception: + if missing_services: self._report_progress( state, - "[red]重新加载 systemd 配置时发生异常[/red]", + _(" [yellow]警告: 以下 MCP 服务未找到: {services}[/yellow]").format( + services=", ".join(missing_services), + ), callback, ) - logger.exception("重新加载 systemd 配置时发生异常") - return False - else: - if process.returncode == 0: - self._report_progress( - state, - "[green]systemd 配置重新加载成功[/green]", - callback, - ) - logger.info("systemd 配置重新加载成功") - return True + logger.warning( + "智能体 %s 的部分 MCP 服务未找到: %s", + app_config.name, + missing_services, + ) - error_output = stderr.decode("utf-8") if stderr else "" + if not mcp_service_ids: self._report_progress( state, - f"[red]systemd 配置重新加载失败: {error_output}[/red]", + _(" [red]{name} 没有可用的 MCP 服务[/red]").format( + name=app_config.name, + ), callback, ) - logger.error("systemd 配置重新加载失败: %s", error_output) - return False + return None - async def _start_mcp_servers( - self, - state: DeploymentState, - callback: Callable[[DeploymentState], None] | None, - ) -> bool: - """运行脚本拉起 MCP Server 进程""" - self._report_progress(state, "[cyan]启动 MCP Server 进程...[/cyan]", callback) + try: + app_id = str(uuid.uuid4()) + target_dir = self.app_dir / app_id + target_dir.mkdir(parents=True, exist_ok=True) + + metadata = AppMetadata( + id=app_id, + name=app_config.name, + description=app_config.description, + version=app_config.version, + author=app_config.author, + app_type=app_config.app_type, + published=app_config.published, + history_len=app_config.history_len, + mcp_service=mcp_service_ids, + icon=app_config.icon, + ) - if not self.run_script_path or not self.run_script_path.exists(): + metadata_file = target_dir / "metadata.yaml" + metadata_dict = { + "type": metadata.type, + "id": metadata.id, + "icon": metadata.icon, + "name": metadata.name, + "description": metadata.description, + "version": metadata.version, + "author": metadata.author, + "app_type": metadata.app_type, + "published": metadata.published, + "history_len": metadata.history_len, + "mcp_service": metadata.mcp_service, + "llm_id": metadata.llm_id, + "permission": metadata.permission, + } + + with metadata_file.open("w", encoding="utf-8") as f: + yaml.dump( + metadata_dict, + f, + allow_unicode=True, + default_flow_style=False, + sort_keys=False, + ) + + except Exception: self._report_progress( state, - f"[red]MCP 启动脚本不存在: {self.run_script_path}[/red]", + _(" [red]{name} 元数据创建失败[/red]").format(name=app_config.name), callback, ) - logger.error("MCP 启动脚本不存在: %s", self.run_script_path) - return False - - # 1. 先检查并清理可能存在的旧进程 - if not await self._cleanup_old_mcp_processes(state, callback): - # 清理失败不会阻止继续执行,只是记录警告 + logger.exception("创建智能体元数据失败: %s", app_config.name) + return None + else: self._report_progress( state, - "[yellow]清理旧进程时遇到问题,但继续执行启动脚本[/yellow]", + _(" [green]{name} 元数据创建成功 (ID: {app_id})[/green]").format( + name=app_config.name, + app_id=app_id, + ), callback, ) + logger.info("智能体元数据创建成功: %s (ID: %s)", app_config.name, app_id) + return app_id - # 2. 执行启动脚本 + def _update_default_app_config(self, default_app_id: str) -> bool: + """将默认智能体 ID 写入配置文件""" try: - # 执行 run.sh 脚本 - cmd = f"bash {self.run_script_path}" - self._report_progress(state, f" [blue]执行命令: {cmd}[/blue]", callback) - logger.info("执行 MCP 启动脚本: %s", cmd) + self.config_manager.set_default_app(default_app_id) + except Exception: + logger.exception("更新默认智能体配置失败: %s", default_app_id) + return False - process = await asyncio.create_subprocess_shell( - cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.STDOUT, + logger.info("默认智能体配置已更新: %s", default_app_id) + return True + + def _resolve_mcp_services( + self, + mcp_paths: list[str], + mcp_service_mapping: dict[str, str], + ) -> tuple[list[str], list[str]]: + """解析 MCP 路径为服务 ID""" + mcp_service_ids = [] + missing_services = [] + + for mcp_path in mcp_paths: + if mcp_path in mcp_service_mapping: + mcp_service_ids.append(mcp_service_mapping[mcp_path]) + else: + missing_services.append(mcp_path) + + return mcp_service_ids, missing_services + + async def _load_app_configs( + self, + state: DeploymentState, + callback: Callable[[DeploymentState], None] | None, + ) -> list[AppConfig]: + """加载应用配置""" + self._report_progress( + state, + _("[cyan]加载应用配置文件...[/cyan]"), + callback, + ) + + if not self.app_config_path or not self.app_config_path.exists(): + self._report_progress( + state, + _("[red]应用配置文件不存在: {path}[/red]").format( + path=self.app_config_path, + ), + callback, ) + logger.error("应用配置文件不存在: %s", self.app_config_path) + return [] - stdout, _ = await process.communicate() - output = stdout.decode("utf-8") if stdout else "" + try: + with self.app_config_path.open("rb") as f: + toml_data = tomllib.load(f) - if process.returncode == 0: - self._report_progress( - state, - "[green]MCP Server 启动脚本执行成功[/green]", - callback, + app_configs = [] + apps_data = toml_data.get("applications", []) + + for app_data in apps_data: + app_config = AppConfig( + app_type=app_data.get("appType", "agent"), + name=app_data.get("name", ""), + description=app_data.get("description", ""), + mcp_path=app_data.get("mcpPath", []), + published=app_data.get("published", True), + version=app_data.get("version", "1.0.0"), + author=app_data.get("author", "openEuler"), + history_len=app_data.get("historyLen", 3), + icon=app_data.get("icon", ""), ) - logger.info("MCP Server 启动脚本执行成功") - return True + app_configs.append(app_config) except Exception: - error_msg = "执行 MCP Server 启动脚本失败" - self._report_progress(state, f"[red]{error_msg}[/red]", callback) - logger.exception(error_msg) - return False + self._report_progress( + state, + _("[red]加载应用配置失败[/red]"), + callback, + ) + logger.exception("加载应用配置失败: %s", self.app_config_path) + return [] else: self._report_progress( state, - f"[red]MCP Server 启动脚本执行失败 (返回码: {process.returncode})[/red]", + _("[green]成功加载 {count} 个应用配置[/green]").format( + count=len(app_configs), + ), callback, ) - logger.error("MCP Server 启动脚本执行失败: %s, 输出: %s", cmd, output) - return False + return app_configs - async def _cleanup_old_mcp_processes( + async def _load_mcp_configs( self, state: DeploymentState, callback: Callable[[DeploymentState], None] | None, - ) -> bool: - """检查并清理可能存在的旧 MCP 进程""" - # 静默获取服务文件列表 - if not self.service_dir or not self.service_dir.exists(): - return True - - service_files = list(self.service_dir.glob("*.service")) - if not service_files: - return True - - # 静默清理服务 - for service_file in service_files: - service_name = service_file.stem # 去掉 .service 后缀 - await self._stop_service(service_name) + ) -> list[tuple[str, McpConfig]]: + """加载 MCP 配置""" + self._report_progress( + state, + _("[cyan]加载 MCP 配置文件...[/cyan]"), + callback, + ) - return True + config_loader = McpConfigLoader(self.mcp_config_dir) + configs = config_loader.load_all_configs() - async def _stop_service(self, service_name: str) -> None: - """静默停止服务""" - try: - # 检查服务状态 - status_cmd = f"systemctl is-active {service_name}" - status_process = await asyncio.create_subprocess_shell( - status_cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + if not configs: + self._report_progress( + state, + _("[yellow]未找到 MCP 配置[/yellow]"), + callback, ) - stdout, _ = await status_process.communicate() - status = stdout.decode("utf-8").strip() if stdout else "" - # 如果服务正在运行,静默停止它 - if status == "active": - stop_cmd = f"sudo systemctl stop {service_name}" - await asyncio.create_subprocess_shell( - stop_cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - except (OSError, subprocess.SubprocessError): - # 静默忽略任何错误 - logger.debug("静默停止服务时发生异常: %s", service_name) + self._report_progress( + state, + _("[green]成功加载 {count} 个 MCP 配置[/green]").format(count=len(configs)), + callback, + ) + return configs - async def _verify_mcp_services( + # ========== 以下是 systemd 服务相关方法 ========== + + async def _stop_oi_runtime_service( self, state: DeploymentState, callback: Callable[[DeploymentState], None] | None, ) -> bool: - """验证 MCP Server 服务状态""" - self._report_progress(state, "[cyan]验证 MCP Server 服务状态...[/cyan]", callback) - - # 获取服务文件列表 - service_files = self._get_service_files(state, callback, "服务验证") - if service_files is None: - return True - - # 处理所有服务文件 - overall_success, active_services, failed_services = await self._process_service_files( - service_files, + """停止 oi-runtime 服务""" + self._report_progress( state, + _("[cyan]停止 oi-runtime 服务...[/cyan]"), callback, - self._verify_single_service, ) - if failed_services: - self._report_progress( - state, - f"[red]关键服务状态异常: {', '.join(failed_services)},停止初始化[/red]", - callback, + try: + # 先检查服务是否存在 + check_cmd = "systemctl list-unit-files oi-runtime.service" + check_process = await asyncio.create_subprocess_shell( + check_cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, ) - logger.error("关键服务状态异常,停止初始化: %s", failed_services) - return False + stdout, _stderr = await check_process.communicate() + output = stdout.decode("utf-8") if stdout else "" - self._report_progress(state, "[green]MCP Server 服务验证完成[/green]", callback) - return True + if "oi-runtime.service" not in output: + self._report_progress( + state, + _("[yellow]oi-runtime 服务不存在,跳过停止操作[/yellow]"), + callback, + ) + logger.info("oi-runtime 服务不存在,跳过停止操作") + return True - async def _verify_single_service( - self, - service_file: Path, - state: DeploymentState, - callback: Callable[[DeploymentState], None] | None, - retry_count: int = 0, - ) -> tuple[bool, str]: - """验证单个服务状态""" - await asyncio.sleep(0.1) + # 检查服务是否正在运行 + status_cmd = "systemctl is-active oi-runtime" + status_process = await asyncio.create_subprocess_shell( + status_cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, _stderr = await status_process.communicate() + status = stdout.decode("utf-8").strip() if stdout else "" - service_name = service_file.stem # 去掉 .service 后缀 + if status != "active": + self._report_progress( + state, + _("[green]oi-runtime 服务未运行,无需停止[/green]"), + callback, + ) + logger.info("oi-runtime 服务未运行") + return True - # 限制递归次数,最多重试6次(30秒) - max_retries = 6 - if retry_count > max_retries: - self._report_progress( - state, - f" [red]{service_name} 启动超时 (30秒)[/red]", - callback, + # 停止服务 + stop_cmd = "sudo systemctl stop oi-runtime" + stop_process = await asyncio.create_subprocess_shell( + stop_cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, ) - logger.error("服务启动超时: %s", service_name) - return False, service_name - if retry_count == 0: + _stdout, stderr = await stop_process.communicate() + + except Exception: self._report_progress( state, - f" [magenta]检查服务状态: {service_name}[/magenta]", + _("[red]停止 oi-runtime 服务时发生异常[/red]"), callback, ) + logger.exception("停止 oi-runtime 服务时发生异常") + return False else: + if stop_process.returncode == 0: + self._report_progress( + state, + _("[green]oi-runtime 服务已停止[/green]"), + callback, + ) + logger.info("oi-runtime 服务已停止") + return True + + error_output = stderr.decode("utf-8") if stderr else "" self._report_progress( state, - f" [dim]{service_name} 重新检查状态... (第 {retry_count} 次)[/dim]", + _("[red]停止 oi-runtime 服务失败: {error}[/red]").format( + error=error_output, + ), callback, ) + logger.error("停止 oi-runtime 服务失败: %s", error_output) + return False + + async def _start_oi_runtime_service( + self, + state: DeploymentState, + callback: Callable[[DeploymentState], None] | None, + ) -> bool: + """启动 oi-runtime 服务""" + self._report_progress( + state, + _("[cyan]启动 oi-runtime 服务...[/cyan]"), + callback, + ) try: - # 使用 systemctl status 获取详细状态信息 - cmd = f"systemctl status {service_name}" - process = await asyncio.create_subprocess_shell( - cmd, + # 先检查服务是否存在 + check_cmd = "systemctl list-unit-files oi-runtime.service" + check_process = await asyncio.create_subprocess_shell( + check_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - - stdout, stderr = await process.communicate() + stdout, _stderr = await check_process.communicate() output = stdout.decode("utf-8") if stdout else "" + if "oi-runtime.service" not in output: + self._report_progress( + state, + _("[yellow]oi-runtime 服务不存在,跳过启动操作[/yellow]"), + callback, + ) + logger.info("oi-runtime 服务不存在,跳过启动操作") + return True + + # 启动服务 + start_cmd = "sudo systemctl start oi-runtime" + start_process = await asyncio.create_subprocess_shell( + start_cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + + _stdout, stderr = await start_process.communicate() + except Exception: self._report_progress( state, - f" [red]检查 {service_name} 状态失败[/red]", + _("[red]启动 oi-runtime 服务时发生异常[/red]"), callback, ) - logger.exception("检查服务状态失败: %s", service_name) - return False, service_name + logger.exception("启动 oi-runtime 服务时发生异常") + return False else: - # systemctl status 返回码: 0=active, 1=dead, 2=unknown, 3=not-found, 4=permission-denied - if process.returncode == 0 and "active (running)" in output.lower(): - # 服务正常运行 + if start_process.returncode == 0: self._report_progress( state, - f" [green]{service_name} 状态正常 (active running)[/green]", + _("[green]oi-runtime 服务已启动[/green]"), callback, ) - logger.info("服务状态正常: %s", service_name) - return True, service_name + logger.info("oi-runtime 服务已启动") + # 等待服务启动完成 + await asyncio.sleep(2) + return True - # 分析输出内容,检查是否有失败信息 - if "failed" in output.lower() or "code=exited" in output.lower(): - self._report_progress( - state, - f" [red]{service_name} 服务启动失败[/red]", - callback, - ) - logger.error("服务启动失败: %s, 详细信息: %s", service_name, output.strip()) - return False, service_name - - # 检查是否真的在启动中(activating 状态) - if "activating" in output.lower() and "start" in output.lower(): - if retry_count == 0: - self._report_progress( - state, - f" [yellow]{service_name} 正在启动中,等待启动完成...[/yellow]", - callback, - ) - logger.info("服务正在启动中,等待启动完成: %s", service_name) - - # 等待3秒后递归调用自己 - await asyncio.sleep(3) - return await self._verify_single_service(service_file, state, callback, retry_count + 1) - - # 其他状态都认为是异常 + error_output = stderr.decode("utf-8") if stderr else "" self._report_progress( state, - f" [red]{service_name} 状态异常 (返回码: {process.returncode})[/red]", + _("[red]启动 oi-runtime 服务失败: {error}[/red]").format( + error=error_output, + ), callback, ) - logger.warning("服务状态异常: %s, 返回码: %d, 输出: %s", service_name, process.returncode, output.strip()) - return False, service_name + logger.error("启动 oi-runtime 服务失败: %s", error_output) + return False - async def _register_all_mcp_services( + def _get_service_files( self, state: DeploymentState, callback: Callable[[DeploymentState], None] | None, - ) -> dict[str, str]: + operation_name: str, + ) -> list[Path] | None: """ - 注册所有 MCP 服务 + 获取服务文件列表的通用方法 Returns: - dict[str, str]: MCP 路径名 -> 服务 ID 的映射 + list[Path]: 服务文件列表,如果应该跳过操作则返回 None """ - self._report_progress(state, "[cyan]注册 MCP 服务...[/cyan]", callback) - - # 加载 MCP 配置 - configs = await self._load_mcp_configs(state, callback) - if not configs: - return {} + if not self.service_dir or not self.service_dir.exists(): + self._report_progress( + state, + _("[yellow]服务配置目录不存在: {dir},跳过{operation}[/yellow]").format( + dir=self.service_dir, + operation=operation_name, + ), + callback, + ) + logger.warning("服务配置目录不存在: %s", self.service_dir) + return None - mcp_service_mapping = {} + service_files = list(self.service_dir.glob("*.service")) + if not service_files: + self._report_progress( + state, + _("[yellow]未找到服务配置文件,跳过{operation}[/yellow]").format( + operation=operation_name, + ), + callback, + ) + return None - for config_path, config in configs: - service_id = await self._process_mcp_service(config, state, callback) - if service_id: - # 使用配置目录名作为 MCP 路径名 - mcp_path_name = config_path.parent.name - mcp_service_mapping[mcp_path_name] = service_id - self._report_progress( - state, - f" [green]{config.name} 注册成功: {mcp_path_name} -> {service_id}[/green]", - callback, - ) - else: - self._report_progress( - state, - f" [red]MCP 服务 {config.name} 注册失败[/red]", - callback, - ) + return service_files + async def _install_service_files( + self, + state: DeploymentState, + callback: Callable[[DeploymentState], None] | None, + ) -> bool: + """安装 systemd 服务文件""" self._report_progress( state, - f"[green]MCP 服务注册完成,成功 {len(mcp_service_mapping)} 个[/green]", + _("[cyan]安装 systemd 服务文件...[/cyan]"), callback, ) - return mcp_service_mapping - - async def _create_agents_from_config( - self, - mcp_service_mapping: dict[str, str], - state: DeploymentState, - callback: Callable[[DeploymentState], None] | None, - ) -> str | None: - """从配置文件创建智能体""" - self._report_progress(state, "[cyan]读取应用配置并创建智能体...[/cyan]", callback) - # 读取应用配置 - app_configs = await self._load_app_configs(state, callback) - if not app_configs: - return None - - created_agents = [] - default_app_id = None - - for i, app_config in enumerate(app_configs): - app_id = await self._create_single_agent( - app_config, - mcp_service_mapping, - state, - callback, - ) + # 获取服务文件列表 + service_files = self._get_service_files(state, callback, _("服务文件安装")) + if service_files is None: + return True - if app_id: - created_agents.append(app_id) + installed_count = 0 + for service_file in service_files: + try: + if await self._install_single_service_file(service_file, state, callback): + installed_count += 1 + except Exception: + self._report_progress( + state, + _(" [red]安装 {file} 时发生异常[/red]").format( + file=service_file.name, + ), + callback, + ) + logger.exception("安装服务文件时发生异常: %s", service_file) - # 第一个智能体设置为默认智能体 - if i == 0: - default_app_id = app_id - self._report_progress( - state, - f" [dim]设置默认智能体: {app_config.name}[/dim]", - callback, - ) - self.config_manager.set_default_app(app_id) + if installed_count > 0: + if not await self._reload_systemd_daemon(state, callback): + return False - if created_agents: self._report_progress( state, - f"[green]成功创建 {len(created_agents)} 个智能体[/green]", + _("[green]成功安装 {count} 个服务文件[/green]").format( + count=installed_count, + ), callback, ) - return default_app_id - self._report_progress(state, "[red]未能创建任何智能体[/red]", callback) - return None + return True - async def _create_single_agent( + async def _install_single_service_file( self, - app_config: AppConfig, - mcp_service_mapping: dict[str, str], + service_file: Path, state: DeploymentState, callback: Callable[[DeploymentState], None] | None, - ) -> str | None: - """创建单个智能体""" + ) -> bool: + """安装单个服务文件""" + service_name = service_file.name + systemd_dir = Path("/etc/systemd/system") + target_path = systemd_dir / service_name + self._report_progress( state, - f"[magenta]创建智能体: {app_config.name}[/magenta]", + _(" [blue]复制服务文件: {name}[/blue]").format(name=service_name), callback, ) - # 将 MCP 路径转换为服务 ID - mcp_service_ids, missing_services = self._resolve_mcp_services( - app_config.mcp_path, - mcp_service_mapping, - ) + try: + cmd = f"sudo cp {service_file} {target_path}" + process = await asyncio.create_subprocess_shell( + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) - if missing_services: + _stdout, stderr = await process.communicate() + + except Exception: self._report_progress( state, - f" [yellow]缺少 MCP 服务: {', '.join(missing_services)},跳过[/yellow]", + _(" [red]复制 {name} 时发生异常[/red]").format(name=service_name), callback, ) - logger.warning("智能体 %s 缺少 MCP 服务: %s", app_config.name, missing_services) - return None + logger.exception("复制服务文件时发生异常: %s", service_file) + return False + else: + if process.returncode == 0: + self._report_progress( + state, + _(" [green]{name} 复制成功[/green]").format(name=service_name), + callback, + ) + logger.info("服务文件复制成功: %s -> %s", service_file, target_path) + return True - if not mcp_service_ids: + error_output = stderr.decode("utf-8") if stderr else "" self._report_progress( state, - f" [yellow]智能体 {app_config.name} 没有可用的 MCP 服务,跳过[/yellow]", + _(" [red]{name} 复制失败: {error}[/red]").format( + name=service_name, + error=error_output, + ), callback, ) - return None + logger.error("服务文件复制失败: %s, 错误: %s", service_file, error_output) + return False + + async def _reload_systemd_daemon( + self, + state: DeploymentState, + callback: Callable[[DeploymentState], None] | None, + ) -> bool: + """重新加载 systemd 配置""" + self._report_progress( + state, + _("[cyan]重新加载 systemd 配置...[/cyan]"), + callback, + ) try: - # 创建智能体 - app_id = await self.api_client.create_agent( - app_config.name, - app_config.description, - mcp_service_ids, + cmd = "sudo systemctl daemon-reload" + process = await asyncio.create_subprocess_shell( + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, ) - # 发布智能体 - if app_config.published: - await self.api_client.publish_agent(app_id) + _stdout, stderr = await process.communicate() except Exception: self._report_progress( state, - f" [red]创建智能体 {app_config.name} 失败[/red]", + _("[red]重新加载 systemd 配置时发生异常[/red]"), callback, ) - logger.exception("创建智能体失败: %s", app_config.name) - return None + logger.exception("重新加载 systemd 配置时发生异常") + return False else: + if process.returncode == 0: + self._report_progress( + state, + _("[green]systemd 配置重新加载成功[/green]"), + callback, + ) + logger.info("systemd 配置重新加载成功") + return True + + error_output = stderr.decode("utf-8") if stderr else "" self._report_progress( state, - f" [green]智能体 {app_config.name} 创建成功: {app_id}[/green]", + _("[red]systemd 配置重新加载失败: {error}[/red]").format( + error=error_output, + ), callback, ) - return app_id - - def _resolve_mcp_services( - self, - mcp_paths: list[str], - mcp_service_mapping: dict[str, str], - ) -> tuple[list[str], list[str]]: - """解析 MCP 路径为服务 ID""" - mcp_service_ids = [] - missing_services = [] - - for mcp_path in mcp_paths: - if mcp_path in mcp_service_mapping: - mcp_service_ids.append(mcp_service_mapping[mcp_path]) - else: - missing_services.append(mcp_path) - - return mcp_service_ids, missing_services + logger.error("systemd 配置重新加载失败: %s", error_output) + return False - async def _load_app_configs( + async def _start_mcp_servers( self, state: DeploymentState, callback: Callable[[DeploymentState], None] | None, - ) -> list[AppConfig]: - """加载应用配置""" - self._report_progress(state, "[cyan]加载应用配置文件...[/cyan]", callback) + ) -> bool: + """运行脚本拉起 MCP Server 进程""" + self._report_progress( + state, + _("[cyan]启动 MCP Server 进程...[/cyan]"), + callback, + ) - if not self.app_config_path or not self.app_config_path.exists(): + if not self.run_script_path or not self.run_script_path.exists(): self._report_progress( state, - f"[red]应用配置文件不存在: {self.app_config_path}[/red]", + _("[red]MCP 启动脚本不存在: {path}[/red]").format( + path=self.run_script_path, + ), + callback, + ) + logger.error("MCP 启动脚本不存在: %s", self.run_script_path) + return False + + if not await self._cleanup_old_mcp_processes(state, callback): + self._report_progress( + state, + _("[yellow]清理旧进程时遇到问题,但继续执行启动脚本[/yellow]"), callback, ) - logger.error("应用配置文件不存在: %s", self.app_config_path) - return [] try: - with self.app_config_path.open(encoding="utf-8") as f: - config_data = toml.load(f) + cmd = f"bash {self.run_script_path}" + self._report_progress( + state, + _(" [blue]执行命令: {cmd}[/blue]").format(cmd=cmd), + callback, + ) + logger.info("执行 MCP 启动脚本: %s", cmd) - applications = config_data.get("applications", []) - if not applications: - self._report_progress( - state, - "[yellow]配置文件中没有找到应用定义[/yellow]", - callback, - ) - logger.warning("配置文件中没有找到应用定义") - return [] + process = await asyncio.create_subprocess_shell( + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT, + ) - app_configs = [] - for app_data in applications: - try: - app_config = AppConfig( - app_type=app_data.get("appType", "agent"), - name=app_data["name"], - description=app_data["description"], - mcp_path=app_data["mcpPath"], - published=app_data.get("published", True), - ) - app_configs.append(app_config) - except KeyError as e: - self._report_progress( - state, - f" [red]应用配置缺少必需字段: {e}[/red]", - callback, - ) - logger.exception("应用配置缺少必需字段") - continue + stdout, _stderr = await process.communicate() + output = stdout.decode("utf-8") if stdout else "" except Exception: - error_msg = f"加载应用配置文件失败: {self.app_config_path}" + error_msg = _("执行 MCP Server 启动脚本失败") self._report_progress(state, f"[red]{error_msg}[/red]", callback) logger.exception(error_msg) - return [] + return False else: + if process.returncode == 0: + self._report_progress( + state, + _("[green]MCP Server 启动脚本执行成功[/green]"), + callback, + ) + logger.info("MCP Server 启动脚本执行成功") + return True + self._report_progress( state, - f"[green]成功加载 {len(app_configs)} 个应用配置[/green]", + _("[red]MCP Server 启动脚本执行失败 (返回码: {code})[/red]").format( + code=process.returncode, + ), callback, ) - return app_configs + logger.error("MCP Server 启动脚本执行失败: %s, 输出: %s", cmd, output) + return False - async def _load_mcp_configs( + async def _cleanup_old_mcp_processes( self, state: DeploymentState, callback: Callable[[DeploymentState], None] | None, - ) -> list[tuple[Path, McpConfig]]: - """加载 MCP 配置""" - self._report_progress(state, "[cyan]加载 MCP 配置文件...[/cyan]", callback) + ) -> bool: + """检查并清理可能存在的旧 MCP 进程""" + if not self.service_dir or not self.service_dir.exists(): + return True - config_loader = McpConfigLoader(self.mcp_config_dir) - configs = config_loader.load_all_configs() + service_files = list(self.service_dir.glob("*.service")) + if not service_files: + return True - if not configs: - self._report_progress(state, "[yellow]未找到 MCP 配置文件[/yellow]", callback) - return [] + for service_file in service_files: + service_name = service_file.stem + await self._stop_service(service_name) - self._report_progress(state, f"[green]成功加载 {len(configs)} 个 MCP 配置[/green]", callback) - return configs + return True - async def _process_mcp_service( - self, - config: McpConfig, - state: DeploymentState, - callback: Callable[[DeploymentState], None] | None, - ) -> str | None: - """处理单个 MCP 服务""" - # 如果是 SSE 类型,先验证 URL 可用且为 SSE - if config.mcp_type == "sse": - valid = await self._validate_sse_endpoint(config, state, callback) - if not valid: - self._report_progress( - state, - f" [red]MCP 服务 {config.name} SSE Endpoint 验证失败[/red]", - callback, - ) - return None + async def _stop_service(self, service_name: str) -> None: + """静默停止服务""" try: - # 注册服务 - service_id = await self._register_mcp_service(config, state, callback) - - # 安装并等待完成 - if not await self._install_and_wait_mcp_service(service_id, config.name, state, callback): - return None - - # 激活服务 - await self._activate_mcp_service(service_id, config.name, state, callback) - - except (ApiError, httpx.RequestError, Exception) as e: - self._report_progress(state, f" [red]{config.name} 处理失败: {e}[/red]", callback) - logger.exception("MCP 服务 %s 处理失败", config.name) - return None - - return service_id - - async def _register_mcp_service( - self, - config: McpConfig, - state: DeploymentState, - callback: Callable[[DeploymentState], None] | None, - ) -> str: - """注册 MCP 服务""" - self._report_progress(state, f" [blue]注册 {config.name}...[/blue]", callback) - return await self.api_client.register_mcp_service(config) - - async def _install_and_wait_mcp_service( - self, - service_id: str, - config_name: str, - state: DeploymentState, - callback: Callable[[DeploymentState], None] | None, - ) -> bool: - """安装并等待 MCP 服务完成""" - self._report_progress(state, f" [cyan]安装 {config_name} (ID: {service_id})...[/cyan]", callback) - await self.api_client.install_mcp_service(service_id) - - self._report_progress(state, f" [dim]等待 {config_name} 安装完成...[/dim]", callback) - if not await self.api_client.wait_for_installation(service_id): - self._report_progress(state, f" [red]{config_name} 安装超时[/red]", callback) - return False + status_cmd = f"systemctl is-active {service_name}" + status_process = await asyncio.create_subprocess_shell( + status_cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, _ = await status_process.communicate() + status = stdout.decode("utf-8").strip() if stdout else "" - return True + if status == "active": + stop_cmd = f"sudo systemctl stop {service_name}" + await asyncio.create_subprocess_shell( + stop_cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + logger.debug("已停止旧服务: %s", service_name) - async def _activate_mcp_service( - self, - service_id: str, - config_name: str, - state: DeploymentState, - callback: Callable[[DeploymentState], None] | None, - ) -> None: - """激活 MCP 服务""" - self._report_progress(state, f" [yellow]激活 {config_name}...[/yellow]", callback) - await self.api_client.activate_mcp_service(service_id) - self._report_progress(state, f" [green]{config_name} 处理完成[/green]", callback) + except (OSError, subprocess.SubprocessError): + logger.debug("静默停止服务时发生异常: %s", service_name) - async def _validate_sse_endpoint( + async def _verify_mcp_services( self, - config: McpConfig, state: DeploymentState, callback: Callable[[DeploymentState], None] | None, ) -> bool: - """验证 SSE Endpoint 是否可用""" - url = config.config.get("url") or "" + """验证 MCP Server 服务状态""" self._report_progress( state, - f"[magenta]验证 SSE Endpoint: {config.name} -> {url}[/magenta]", + _("[cyan]验证 MCP Server 服务状态...[/cyan]"), callback, ) - # 重试配置 - max_attempts = 5 # 10秒 / 2秒 = 5次 - retry_interval = 2 # 2秒重试间隔 + # 获取服务文件列表 + service_files = self._get_service_files(state, callback, _("服务验证")) + if service_files is None: + return True - for attempt in range(1, max_attempts + 1): - # 方式1:先尝试原来的简单 GET 请求方式 - if await self._try_simple_sse_check(url, config.name, attempt, max_attempts): - self._report_progress( + failed_services = [] + for service_file in service_files: + try: + if not await self._verify_single_service( + service_file, state, - f" [green]{config.name} SSE Endpoint 验证通过[/green]", callback, - ) - logger.info("SSE Endpoint 简单验证成功: %s (尝试 %d 次)", url, attempt) - return True - - # 方式2:如果简单方式失败,尝试 MCP 协议 initialize 方法 - if await self._try_mcp_initialize_check(url, config.name, attempt, max_attempts): + ): + failed_services.append(service_file.stem) + except Exception: self._report_progress( state, - f" [green]{config.name} SSE Endpoint 验证通过[/green]", + _(" [red]验证 {file} 时发生异常[/red]").format( + file=service_file.stem, + ), callback, ) - logger.info("SSE Endpoint MCP 协议验证成功: %s (尝试 %d 次)", url, attempt) - return True + logger.exception("验证服务时发生异常: %s", service_file) + failed_services.append(service_file.stem) - # 如果还有重试机会,等待后继续 - if attempt < max_attempts: - await asyncio.sleep(retry_interval) + if failed_services: + self._report_progress( + state, + _("[red]关键服务状态异常: {services},停止初始化[/red]").format( + services=", ".join(failed_services), + ), + callback, + ) + logger.error("关键服务状态异常,停止初始化: %s", failed_services) + return False - # 所有尝试都失败了 self._report_progress( state, - f" [red]{config.name} SSE Endpoint 验证失败: 30秒内无法连接[/red]", + _("[green]MCP Server 服务验证完成[/green]"), callback, ) - logger.error( - "SSE Endpoint 验证最终失败: %s (尝试了 %d 次,耗时 %d 秒)", - url, - max_attempts, - max_attempts * retry_interval, - ) - return False + return True - async def _try_simple_sse_check( + async def _verify_single_service( self, - url: str, - config_name: str, - attempt: int, - max_attempts: int, + service_file: Path, + state: DeploymentState, + callback: Callable[[DeploymentState], None] | None, + retry_count: int = 0, ) -> bool: - """尝试简单的 SSE 检查(原来的方式)""" - try: - # 使用流式请求,只读取响应头,避免 SSE 连接一直保持开放 - async with ( - httpx.AsyncClient(timeout=self.api_client.timeout) as client, - client.stream("GET", url, headers={"Accept": "text/event-stream"}) as response, - ): - if response.status_code == HTTP_OK: - logger.debug("SSE Endpoint 简单检查成功: %s (尝试 %d 次)", url, attempt) - return True - - logger.debug( - "SSE Endpoint 简单检查响应码非 200: %s, 状态码: %d, 尝试: %d/%d", - url, - response.status_code, - attempt, - max_attempts, - ) + """验证单个服务状态""" + await asyncio.sleep(0.1) + + service_name = service_file.stem + max_retries = 6 + if retry_count > max_retries: + self._report_progress( + state, + _(" [red]{name} 启动超时 (30秒)[/red]").format(name=service_name), + callback, + ) + logger.error("服务启动超时: %s", service_name) + return False + + if retry_count == 0: + self._report_progress( + state, + _(" [magenta]检查服务状态: {name}[/magenta]").format( + name=service_name, + ), + callback, + ) + else: + self._report_progress( + state, + _(" [dim]{name} 重新检查状态... (第 {count} 次)[/dim]").format( + name=service_name, + count=retry_count, + ), + callback, + ) - except (httpx.RequestError, httpx.HTTPStatusError) as e: - logger.debug("SSE Endpoint 简单检查连接失败: %s, 错误: %s, 尝试: %d/%d", url, e, attempt, max_attempts) + try: + cmd = f"systemctl status {service_name}" + process = await asyncio.create_subprocess_shell( + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) - return False + stdout, _stderr = await process.communicate() + output = stdout.decode("utf-8") if stdout else "" - async def _try_mcp_initialize_check( - self, - url: str, - config_name: str, - attempt: int, - max_attempts: int, - ) -> bool: - """尝试 MCP 协议的 initialize 检查""" - # MCP 协议初始化请求负载 - mcp_payload = { - "jsonrpc": "2.0", - "id": "health-check", - "method": "initialize", - "params": { - "protocolVersion": "2024-11-05", - "capabilities": {}, - "clientInfo": { - "name": "openEuler Intelligence", - "version": "1.0", - }, - }, - } + except Exception: + self._report_progress( + state, + _(" [red]检查 {name} 状态失败[/red]").format(name=service_name), + callback, + ) + logger.exception("检查服务状态失败: %s", service_name) + return False + else: + # systemctl status 返回码: 0=active, 1=dead, 2=unknown, 3=not-found, 4=permission-denied + if process.returncode == 0 and "active (running)" in output.lower(): + self._report_progress( + state, + _(" [green]{name} 运行正常[/green]").format(name=service_name), + callback, + ) + logger.info("服务运行正常: %s", service_name) + return True - headers = { - "Content-Type": "application/json", - "Accept": "application/json,text/event-stream", - "MCP-Protocol-Version": "2024-11-05", - } + if "failed" in output.lower() or "code=exited" in output.lower(): + self._report_progress( + state, + _(" [red]{name} 启动失败[/red]").format(name=service_name), + callback, + ) + logger.error("服务启动失败: %s\n%s", service_name, output) + return False - try: - async with httpx.AsyncClient(timeout=self.api_client.timeout) as client: - response = await client.post(url, json=mcp_payload, headers=headers) + if "activating" in output.lower(): + await asyncio.sleep(5) + return await self._verify_single_service( + service_file, + state, + callback, + retry_count + 1, + ) - if response.status_code == HTTP_OK: - # 尝试解析 SSE 响应,确保是有效的 MCP JSON-RPC 响应 - try: - response_text = response.text - - # 检查是否是 SSE 格式的响应 - if "event: message" in response_text and "data: " in response_text: - logger.debug("SSE Endpoint MCP 协议检查成功: %s (尝试 %d 次)", url, attempt) - return True - - # 限制日志输出长度,避免过长的响应内容 - max_log_length = 100 - truncated_response = ( - response_text[:max_log_length] + "..." - if len(response_text) > max_log_length - else response_text - ) - logger.debug( - "SSE Endpoint MCP 响应格式异常: %s, 响应: %s, 尝试: %d/%d", - url, - truncated_response, - attempt, - max_attempts, - ) - except json.JSONDecodeError: - logger.debug( - "SSE Endpoint MCP 响应非 JSON 格式: %s, 尝试: %d/%d", - url, - attempt, - max_attempts, - ) - else: - logger.debug( - "SSE Endpoint MCP 响应码非 200: %s, 状态码: %d, 尝试: %d/%d", - url, - response.status_code, - attempt, - max_attempts, - ) - - except (httpx.RequestError, httpx.HTTPStatusError) as e: - logger.debug("SSE Endpoint MCP 连接失败: %s, 错误: %s, 尝试: %d/%d", url, e, attempt, max_attempts) - - return False + self._report_progress( + state, + _(" [red]{name} 状态异常[/red]").format(name=service_name), + callback, + ) + logger.error("服务状态异常: %s\n%s", service_name, output) + return False diff --git a/src/app/deployment/components/env_check.py b/src/app/deployment/components/env_check.py index af81faa887796399c9c8eb6ae41a16455859ccf4..725fdf45b6a3d7903eee72be9b19f3d44e474ab7 100644 --- a/src/app/deployment/components/env_check.py +++ b/src/app/deployment/components/env_check.py @@ -18,6 +18,7 @@ from textual.widgets import ( from app.deployment.service import DeploymentService from app.deployment.ui import DeploymentConfigScreen +from i18n.manager import _ if TYPE_CHECKING: from textual.app import ComposeResult @@ -88,20 +89,20 @@ class EnvironmentCheckScreen(ModalScreen[bool]): def compose(self) -> ComposeResult: """组合界面组件""" with Container(classes="check-container"): - yield Static("环境检查", classes="check-title") + yield Static(_("环境检查"), classes="check-title") with Horizontal(classes="check-item"): yield Static("", id="os_status", classes="check-status") - yield Static("检查操作系统类型...", id="os_desc", classes="check-description") + yield Static(_("检查操作系统类型..."), id="os_desc", classes="check-description") with Horizontal(classes="check-item"): yield Static("", id="sudo_status", classes="check-status") - yield Static("检查管理员权限...", id="sudo_desc", classes="check-description") + yield Static(_("检查管理员权限..."), id="sudo_desc", classes="check-description") with Horizontal(classes="button-row"): - yield Button("继续配置", id="continue", variant="success", classes="continue-button", disabled=True) - yield Button("返回", id="back", variant="primary", classes="back-button") - yield Button("退出", id="exit", variant="error", classes="exit-button") + yield Button(_("继续配置"), id="continue", variant="success", classes="continue-button", disabled=True) + yield Button(_("返回"), id="back", variant="primary", classes="back-button") + yield Button(_("退出"), id="exit", variant="error", classes="exit-button") async def on_mount(self) -> None: """界面挂载时开始环境检查""" @@ -120,7 +121,7 @@ class EnvironmentCheckScreen(ModalScreen[bool]): self._update_ui_state() except (OSError, RuntimeError) as e: - self.notify(f"环境检查过程中发生异常: {e}", severity="error") + self.notify(_("环境检查过程中发生异常: {error}").format(error=e), severity="error") async def _check_operating_system(self) -> None: """检查操作系统类型""" @@ -133,17 +134,17 @@ class EnvironmentCheckScreen(ModalScreen[bool]): if is_openeuler: os_status.update("[green]✓[/green]") - os_desc.update("操作系统: openEuler (支持)") + os_desc.update(_("操作系统: openEuler (支持)")) else: os_status.update("[red]✗[/red]") - os_desc.update("操作系统: 非 openEuler (不支持)") - self.error_messages.append("仅支持 openEuler 操作系统") + os_desc.update(_("操作系统: 非 openEuler (不支持)")) + self.error_messages.append(_("仅支持 openEuler 操作系统")) except (OSError, RuntimeError) as e: self.check_results["os"] = False self.query_one("#os_status", Static).update("[red]✗[/red]") - self.query_one("#os_desc", Static).update(f"操作系统检查失败: {e}") - self.error_messages.append(f"操作系统检查异常: {e}") + self.query_one("#os_desc", Static).update(_("操作系统检查失败: {error}").format(error=e)) + self.error_messages.append(_("操作系统检查异常: {error}").format(error=e)) async def _check_sudo_privileges(self) -> None: """检查管理员权限""" @@ -156,17 +157,17 @@ class EnvironmentCheckScreen(ModalScreen[bool]): if has_sudo: sudo_status.update("[green]✓[/green]") - sudo_desc.update("管理员权限: 可用") + sudo_desc.update(_("管理员权限: 可用")) else: sudo_status.update("[red]✗[/red]") - sudo_desc.update("管理员权限: 不可用 (需要 sudo)") - self.error_messages.append("需要管理员权限,请确保可以使用 sudo") + sudo_desc.update(_("管理员权限: 不可用 (需要 sudo)")) + self.error_messages.append(_("需要管理员权限,请确保可以使用 sudo")) except (OSError, RuntimeError) as e: self.check_results["sudo"] = False self.query_one("#sudo_status", Static).update("[red]✗[/red]") - self.query_one("#sudo_desc", Static).update(f"权限检查失败: {e}") - self.error_messages.append(f"权限检查异常: {e}") + self.query_one("#sudo_desc", Static).update(_("权限检查失败: {error}").format(error=e)) + self.error_messages.append(_("权限检查异常: {error}").format(error=e)) def _update_ui_state(self) -> None: """更新界面状态""" diff --git a/src/app/deployment/components/modes.py b/src/app/deployment/components/modes.py index f6347f4611399742baa94cebc84e9fd761e567b8..b4b95da279f1a754192e1076dd91b02eb5a27eee 100644 --- a/src/app/deployment/components/modes.py +++ b/src/app/deployment/components/modes.py @@ -8,6 +8,7 @@ from __future__ import annotations import asyncio import os +import webbrowser from typing import TYPE_CHECKING, Any, ClassVar from textual import on @@ -17,9 +18,12 @@ from textual.screen import ModalScreen from textual.widgets import Button, Input, Label, Static from config.manager import ConfigManager -from config.model import Backend +from config.model import Backend, ConfigModel +from i18n.manager import _ from log.manager import get_logger -from tool.validators import validate_oi_connection +from tool.callback_server import CallbackServer +from tool.oi_login import get_auth_url +from tool.validators import is_browser_available, validate_oi_connection from . import EnvironmentCheckScreen @@ -51,7 +55,7 @@ class InitializationModeScreen(ModalScreen[bool]): """ BINDINGS: ClassVar = [ - Binding("escape", "app.quit", "退出"), + Binding("escape", "app.quit", _("退出")), ] def __init__(self) -> None: @@ -61,16 +65,16 @@ class InitializationModeScreen(ModalScreen[bool]): def compose(self) -> ComposeResult: """组合界面组件""" with Container(classes="mode-container"): - yield Static("openEuler Intelligence 初始化", classes="mode-title") + yield Static(_("openEuler Intelligence 初始化"), classes="mode-title") yield Static( - "请选择您的初始化方式:", + _("请选择您的初始化方式:"), classes="mode-description", ) with Horizontal(classes="options-row"): # 连接现有服务选项 yield ModeOptionButton( - "连接现有服务\n\n输入现有服务的 URL 和 Token 即可连接使用", + _("连接现有服务\n\n输入现有服务的 URL 和 Token 即可连接使用"), id="connect_existing", classes="mode-option", variant="default", @@ -78,14 +82,14 @@ class InitializationModeScreen(ModalScreen[bool]): # 部署新服务选项 yield ModeOptionButton( - "部署新服务\n\n在本机部署全新的服务环境和配置", + _("部署新服务\n\n在本机部署全新的服务环境和配置"), id="deploy_new", classes="mode-option", variant="default", ) with Horizontal(classes="mode-button-row"): - yield Button("退出", id="exit", variant="error", classes="exit-button") + yield Button(_("退出"), id="exit", variant="error", classes="exit-button") def on_mount(self) -> None: """组件挂载时的处理""" @@ -141,11 +145,17 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): width: 1fr; margin-left: 1; } + + .get-api-key-button { + width: auto; + min-width: 10; + margin: 0 1; + } """ BINDINGS: ClassVar = [ - Binding("escape", "back", "返回"), - Binding("ctrl+q", "app.quit", "退出"), + Binding("escape", "back", _("返回")), + Binding("ctrl+q", "app.quit", _("退出")), ] def __init__(self) -> None: @@ -153,48 +163,73 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): super().__init__() self.validation_task: asyncio.Task[None] | None = None self.is_validated = False + self.callback_server: CallbackServer | None = None + self.login_task: asyncio.Task[None] | None = None + self.logger = get_logger(__name__) + self.browser_available = is_browser_available() def compose(self) -> ComposeResult: """组合界面组件""" with Container(classes="connect-container"): - yield Static("连接现有 openEuler Intelligence 服务", classes="connect-title") + yield Static(_("连接现有 openEuler Intelligence 服务"), classes="connect-title") yield Static( - "请输入您的 openEuler Intelligence 服务连接信息:", + _("请输入您的 openEuler Intelligence 服务连接信息:"), classes="connect-description", ) with Horizontal(classes="form-row"): - yield Label("服务 URL:", classes="form-label") + yield Label(_("服务 URL:"), classes="form-label") yield Input( - placeholder="例如:http://your-server:8002", + placeholder=_("例如:http://your-server:8002"), id="service_url", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("Access Token:", classes="form-label") + yield Label(_("访问令牌:"), classes="form-label") yield Input( - placeholder="可选,您的访问令牌", + placeholder=_("可选,您的访问令牌"), password=True, id="access_token", classes="form-input", ) + yield Button( + _("获取"), + id="get_api_key", + variant="primary", + classes="get-api-key-button", + disabled=True, + ) - yield Static("未验证", id="validation_status", classes="validation-status") + yield Static(_("未验证"), id="validation_status", classes="validation-status") + + # 根据浏览器可用性生成不同的提示文本 + if self.browser_available: + help_text = _( + "提示:\n" + "• 服务 URL 通常以 http:// 或 https:// 开头\n" + "• 访问令牌为可选项,如果服务无需认证可留空\n" + "• 输入服务 URL 后,可点击 '获取' 按钮通过浏览器获取访问令牌\n" + "• 也可以从 openEuler Intelligence Web 界面手动获取并填入\n" + "• 系统会自动验证连接并保存配置", + ) + else: + help_text = _( + "提示:\n" + "• 服务 URL 通常以 http:// 或 https:// 开头\n" + "• 访问令牌为可选项,如果服务无需认证可留空\n" + "• [yellow]当前环境不支持浏览器,请从 Web 界面手动获取访问令牌[/yellow]\n" + "• 系统会自动验证连接并保存配置", + ) - yield Static( - "提示:\n" - "• 服务 URL 通常以 http:// 或 https:// 开头\n" - "• Access Token 为可选项,如果服务无需认证可留空\n" - "• 如有 Token,可以从 openEuler Intelligence 管理界面获取\n" - "• 系统会自动验证连接并保存配置", - classes="help-text", - ) + yield Static(help_text, classes="help-text") with Horizontal(classes="mode-button-row"): - yield Button("连接并保存", id="connect", variant="success", disabled=True) - yield Button("返回", id="back", variant="primary") - yield Button("退出", id="exit", variant="error") + yield Button(_("连接并保存"), id="connect", variant="success", disabled=True) + yield Button(_("返回"), id="back", variant="primary") + yield Button(_("退出"), id="exit", variant="error") + + # ==================== 事件处理方法 ==================== @on(Input.Changed, "#service_url, #access_token") async def on_field_changed(self, event: Input.Changed) -> None: @@ -203,11 +238,109 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): if self.validation_task and not self.validation_task.done(): self.validation_task.cancel() + # 更新"获取 API Key"按钮状态 + self._update_get_api_key_button() + # 检查是否需要验证 if self._should_validate(): # 延迟验证,避免频繁触发 self.validation_task = asyncio.create_task(self._delayed_validation()) + @on(Button.Pressed, "#get_api_key") + async def on_get_api_key_pressed(self) -> None: + """处理获取 API Key 按钮点击""" + try: + # 检查浏览器是否可用 + if not self.browser_available: + self.notify( + _("当前环境不支持打开浏览器,请手动从 Web 界面获取访问令牌"), + severity="warning", + ) + return + + # 获取服务 URL + url = self.query_one("#service_url", Input).value.strip() + if not url: + self.notify(_("请先输入服务 URL"), severity="warning") + return + + # 禁用按钮,防止重复点击 + get_api_key_btn = self.query_one("#get_api_key", Button) + get_api_key_btn.disabled = True + + # 显示状态 + status_widget = self.query_one("#validation_status", Static) + status_widget.update(_("[yellow]正在获取授权 URL...[/yellow]")) + + # 获取授权 URL + auth_url, _token = get_auth_url(url) + if not auth_url: + status_widget.update(_("[red]✗ 获取授权 URL 失败[/red]")) + get_api_key_btn.disabled = False + return + + # 启动回调服务器 + self.callback_server = CallbackServer() + launcher_url = self.callback_server.start(auth_url) + + # 更新状态并打开浏览器 + status_widget.update(_("[yellow]正在打开浏览器登录...[/yellow]")) + webbrowser.open(launcher_url) + self.notify(_("已打开浏览器,请完成登录"), severity="information") + + # 异步等待登录完成 + self.login_task = asyncio.create_task(self._wait_for_login()) + + except (OSError, RuntimeError, ValueError) as e: + self.logger.exception("获取 API Key 失败") + self.notify(_("获取 API Key 失败: {error}").format(error=e), severity="error") + try: + get_api_key_btn = self.query_one("#get_api_key", Button) + get_api_key_btn.disabled = False + except (AttributeError, ValueError): + pass + + @on(Button.Pressed, "#connect") + async def on_connect_pressed(self) -> None: + """处理连接按钮点击""" + if not self.is_validated: + self.notify(_("请等待连接验证完成"), severity="warning") + return + + try: + # 获取输入值 + url = self.query_one("#service_url", Input).value.strip() + token = self.query_one("#access_token", Input).value.strip() + + # 保存配置 + await self._save_configuration(url, token) + + # 显示成功信息 + self.notify(_("配置已保存,初始化完成!"), severity="information") + self.app.exit() + + except (OSError, RuntimeError, ValueError) as e: + self.notify(_("保存配置时发生错误: {error}").format(error=e), severity="error") + + @on(Button.Pressed, "#back") + async def on_back_pressed(self) -> None: + """处理返回按钮点击""" + await self._cleanup() + self.dismiss(result=False) + + @on(Button.Pressed, "#exit") + async def on_exit_pressed(self) -> None: + """处理退出按钮点击""" + await self._cleanup() + self.app.exit() + + async def action_back(self) -> None: + """键盘返回操作""" + await self._cleanup() + self.dismiss(result=False) + + # ==================== 验证相关的私有方法 ==================== + def _should_validate(self) -> bool: """检查是否应该进行验证""" try: @@ -217,6 +350,16 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): except (AttributeError, ValueError): return False + def _update_get_api_key_button(self) -> None: + """更新获取 API Key 按钮的状态""" + try: + url = self.query_one("#service_url", Input).value.strip() + get_api_key_btn = self.query_one("#get_api_key", Button) + # 只有在浏览器可用且 URL 有效时才启用按钮 + get_api_key_btn.disabled = not (self.browser_available and bool(url)) + except (AttributeError, ValueError): + pass + async def _delayed_validation(self) -> None: """延迟验证""" try: @@ -231,7 +374,7 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): connect_button = self.query_one("#connect", Button) # 更新状态为验证中 - status_widget.update("[yellow]验证连接中...[/yellow]") + status_widget.update(_("[yellow]验证连接中...[/yellow]")) connect_button.disabled = True self.is_validated = False @@ -244,88 +387,122 @@ class ConnectExistingServiceScreen(ModalScreen[bool]): is_valid, message = await validate_oi_connection(url, token) if is_valid: - status_widget.update(f"[green]✓ {message}[/green]") + status_widget.update(_("[green]✓ {message}[/green]").format(message=message)) connect_button.disabled = False self.is_validated = True - self.notify("连接验证成功", severity="information") else: - status_widget.update(f"[red]✗ {message}[/red]") + status_widget.update(_("[red]✗ {message}[/red]").format(message=message)) connect_button.disabled = True self.is_validated = False - self.notify(f"连接验证失败: {message}", severity="error") except (OSError, RuntimeError, ValueError) as e: - status_widget.update(f"[red]✗ 验证异常: {e}[/red]") + status_widget.update(_("[red]✗ 验证异常: {error}[/red]").format(error=e)) connect_button.disabled = True self.is_validated = False - self.notify(f"验证过程中出现异常: {e}", severity="error") - @on(Button.Pressed, "#connect") - async def on_connect_pressed(self) -> None: - """处理连接按钮点击""" - if not self.is_validated: - self.notify("请等待连接验证完成", severity="warning") - return + # ==================== Web 登录相关的私有方法 ==================== + + async def _wait_for_login(self) -> None: + """等待浏览器登录完成""" + status_widget = self.query_one("#validation_status", Static) + get_api_key_btn = self.query_one("#get_api_key", Button) try: - # 获取输入值 - url = self.query_one("#service_url", Input).value.strip() - token = self.query_one("#access_token", Input).value.strip() + if not self.callback_server: + return + + # 等待认证结果(超时 5 分钟) + status_widget.update(_("[yellow]等待登录完成...[/yellow]")) + auth_result = await asyncio.to_thread( + self.callback_server.wait_for_auth, + timeout=300, + ) - # 保存配置 - await self._save_configuration(url, token) + # 处理认证结果 + result_type = auth_result.get("type") - # 显示成功信息 - self.notify("配置已保存,初始化完成!", severity="information") - self.app.exit() + if result_type == "session": + session_id = auth_result.get("sessionId") + if session_id: + # 将 session_id 填入 Access Token 输入框 + token_input = self.query_one("#access_token", Input) + token_input.value = session_id - except (OSError, RuntimeError, ValueError) as e: - self.notify(f"保存配置时发生错误: {e}", severity="error") + status_widget.update(_("[green]✓ 登录成功,已获取 API Key[/green]")) + self.logger.info("浏览器登录成功,已获取 API Key") - async def _save_configuration(self, url: str, token: str) -> None: - """保存连接配置""" - logger = get_logger(__name__) + # 自动触发验证 + await self._validate_connection() + else: + status_widget.update(_("[red]✗ 登录失败:未收到 session ID[/red]")) + get_api_key_btn.disabled = False - try: - # 确定是否为 root 用户 - is_root = os.geteuid() == 0 + elif result_type == "error": + error_desc = auth_result.get("error_description", "未知错误") + status_widget.update(_("[red]✗ 登录失败: {error}[/red]").format(error=error_desc)) + get_api_key_btn.disabled = False - if is_root: - # root 用户:同时更新全局模板和当前配置 - logger.info("检测到 root 用户,将同时更新全局模板和当前配置") + else: + status_widget.update(_("[red]✗ 登录失败:未知结果[/red]")) + get_api_key_btn.disabled = False - # 创建部署专用的配置管理器来操作全局模板 - deployment_manager = ConfigManager.create_deployment_manager() - deployment_manager.set_eulerintelli_url(url) - deployment_manager.set_eulerintelli_key(token) - deployment_manager.set_backend(Backend.EULERINTELLI) + except asyncio.CancelledError: + self.logger.info("登录任务被取消") + status_widget.update(_("[yellow]登录已取消[/yellow]")) + get_api_key_btn.disabled = False + except (OSError, RuntimeError, ValueError) as e: + self.logger.exception("等待登录时发生错误") + status_widget.update(_("[red]✗ 登录异常: {error}[/red]").format(error=e)) + get_api_key_btn.disabled = False + finally: + # 清理回调服务器 + if self.callback_server: + self.callback_server.stop() + self.callback_server = None - # 创建全局模板 - if not deployment_manager.create_global_template(): - logger.warning("创建全局配置模板失败,但会继续保存用户配置") + # ==================== 配置保存相关的私有方法 ==================== - # 更新当前用户配置(无论是否为 root) + async def _save_configuration(self, url: str, token: str) -> None: + """保存连接配置""" + try: + # 更新当前用户配置 config_manager = ConfigManager() config_manager.set_eulerintelli_url(url) config_manager.set_eulerintelli_key(token) config_manager.set_backend(Backend.EULERINTELLI) - logger.info("配置已保存: URL=%s", url) + self.logger.info("用户配置已保存: URL=%s", url) + + # 如果是 root 用户,从用户配置创建全局模板 + is_root = os.geteuid() == 0 + if is_root: + self.logger.info("检测到 root 用户,创建全局配置模板") + + deployment_manager = ConfigManager.create_deployment_manager() + + config_dict = config_manager.data.to_dict() + deployment_manager.data = ConfigModel.from_dict(config_dict) + + if deployment_manager.create_global_template(): + self.logger.info("全局配置模板创建成功") + else: + self.logger.warning("创建全局配置模板失败,但用户配置已保存") except (OSError, RuntimeError, ValueError): - logger.exception("保存配置时发生错误") + self.logger.exception("保存配置时发生错误") raise - @on(Button.Pressed, "#back") - async def on_back_pressed(self) -> None: - """处理返回按钮点击""" - self.dismiss(result=False) + # ==================== 资源清理相关的私有方法 ==================== - @on(Button.Pressed, "#exit") - def on_exit_pressed(self) -> None: - """处理退出按钮点击""" - self.app.exit() + async def _cleanup(self) -> None: + """清理资源""" + # 取消登录任务 + if self.login_task and not self.login_task.done(): + self.login_task.cancel() + # 等待任务取消完成,忽略 CancelledError + await asyncio.sleep(0) - async def action_back(self) -> None: - """键盘返回操作""" - self.dismiss(result=False) + # 停止回调服务器 + if self.callback_server: + self.callback_server.stop() + self.callback_server = None diff --git a/src/app/deployment/models.py b/src/app/deployment/models.py index 29d8098d04eefa3bd5304357bd550ddd216cdc68..b1c83376f47c03c743505ff5dafb1b8d9d6515c0 100644 --- a/src/app/deployment/models.py +++ b/src/app/deployment/models.py @@ -10,6 +10,7 @@ import re from dataclasses import dataclass, field from enum import Enum +from i18n.manager import _ from tool.validators import APIValidator # 常量定义 @@ -64,7 +65,6 @@ class DeploymentConfig: """ # 基础设置 - server_ip: str = "" deployment_mode: str = "light" # light: 轻量部署, full: 全量部署 # LLM 配置 @@ -90,9 +90,6 @@ class DeploymentConfig: """ errors = [] - # 验证基础字段 - errors.extend(self._validate_basic_fields()) - # 验证 LLM 字段 errors.extend(self._validate_llm_fields()) @@ -117,7 +114,7 @@ class DeploymentConfig: """ # 检查必要字段是否完整(只要求端点) if not self.llm.endpoint.strip(): - return False, "LLM API 端点不能为空", {} + return False, _("LLM API 端点不能为空"), {} validator = APIValidator() llm_valid, llm_msg, llm_info = await validator.validate_llm_config( @@ -146,7 +143,7 @@ class DeploymentConfig: """ # 检查必要字段是否完整(只要求端点) if not self.embedding.endpoint.strip(): - return False, "Embedding API 端点不能为空", {} + return False, _("Embedding API 端点不能为空"), {} validator = APIValidator() embed_valid, embed_msg, embed_info = await validator.validate_embedding_config( @@ -164,18 +161,11 @@ class DeploymentConfig: return embed_valid, embed_msg, embed_info - def _validate_basic_fields(self) -> list[str]: - """验证基础字段""" - errors = [] - if not self.server_ip.strip(): - errors.append("服务器 IP 地址不能为空") - return errors - def _validate_llm_fields(self) -> list[str]: """验证 LLM 配置字段""" errors = [] if not self.llm.endpoint.strip(): - errors.append("LLM API 端点不能为空") + errors.append(_("LLM API 端点不能为空")) return errors def _validate_embedding_fields(self) -> list[str]: @@ -195,10 +185,10 @@ class DeploymentConfig: if self.deployment_mode == "light": # 如果用户填了任何 Embedding 字段,则端点必须填写,API Key 和模型名称允许为空 if has_embedding_config and not self.embedding.endpoint.strip(): - errors.append("Embedding API 端点不能为空") + errors.append(_("Embedding API 端点不能为空")) elif not self.embedding.endpoint.strip(): # 全量部署模式下,Embedding 配置是必需的,但只要求端点必填 - errors.append("Embedding API 端点不能为空") + errors.append(_("Embedding API 端点不能为空")) return errors @@ -206,11 +196,16 @@ class DeploymentConfig: """验证数值字段""" errors = [] if self.llm.max_tokens <= 0: - errors.append("LLM max_tokens 必须大于 0") + errors.append(_("LLM max_tokens 必须大于 0")) if not (MIN_TEMPERATURE <= self.llm.temperature <= MAX_TEMPERATURE): - errors.append(f"LLM temperature 必须在 {MIN_TEMPERATURE} 到 {MAX_TEMPERATURE} 之间") + errors.append( + _("LLM temperature 必须在 {min} 到 {max} 之间").format( + min=MIN_TEMPERATURE, + max=MAX_TEMPERATURE, + ), + ) if self.llm.request_timeout <= 0: - errors.append("LLM 请求超时时间必须大于 0") + errors.append(_("LLM 请求超时时间必须大于 0")) return errors diff --git a/src/app/deployment/service.py b/src/app/deployment/service.py index 5b12819abf456d9de9e49928538cb17c8eca30a4..1b59b6fe687860091931840bcd6b3ca19fd00c4c 100644 --- a/src/app/deployment/service.py +++ b/src/app/deployment/service.py @@ -18,6 +18,7 @@ import httpx import toml from config.manager import ConfigManager +from i18n.manager import _ from log.manager import get_logger from .agent import AgentManager @@ -29,6 +30,9 @@ if TYPE_CHECKING: logger = get_logger(__name__) +LOCAL_DEPLOYMENT_HOST = "127.0.0.1" + + class DeploymentResourceManager: """部署资源管理器,管理 RPM 包安装的资源文件""" @@ -62,7 +66,7 @@ class DeploymentResourceManager: return template_path.read_text(encoding="utf-8") except OSError as e: logger.exception("读取模板文件失败 %s", template_path) - msg = f"无法读取模板文件: {template_path}" + msg = _("无法读取模板文件: {path}").format(path=template_path) raise RuntimeError(msg) from e @classmethod @@ -96,14 +100,14 @@ class DeploymentResourceManager: toml_data = toml.loads(content) # 更新服务器 IP - server_ip = str(config.server_ip) + server_host = LOCAL_DEPLOYMENT_HOST if "login" in toml_data and "settings" in toml_data["login"]: - toml_data["login"]["settings"]["host"] = f"http://{server_ip}:8000" - toml_data["login"]["settings"]["login_api"] = f"http://{server_ip}:8080/api/auth/login" + toml_data["login"]["settings"]["host"] = f"http://{server_host}:8000" + toml_data["login"]["settings"]["login_api"] = f"http://{server_host}:8080/api/auth/login" # 更新 fastapi 域名 if "fastapi" in toml_data: - toml_data["fastapi"]["domain"] = server_ip + toml_data["fastapi"]["domain"] = server_host # 更新 LLM 配置 if "llm" in toml_data: @@ -134,11 +138,11 @@ class DeploymentResourceManager: except toml.TomlDecodeError as e: logger.exception("解析 TOML 内容时出错") - msg = f"TOML 格式错误: {e}" + msg = _("TOML 格式错误: {error}").format(error=e) raise ValueError(msg) from e except Exception as e: logger.exception("更新 TOML 配置时发生错误") - msg = f"更新 TOML 配置失败: {e}" + msg = _("更新 TOML 配置失败: {error}").format(error=e) raise RuntimeError(msg) from e @classmethod @@ -184,26 +188,29 @@ class DeploymentService: # 更新状态 if progress_callback: - temp_state.current_step_name = "检查部署依赖" - temp_state.add_log("正在检查部署环境依赖...") + temp_state.current_step_name = _("检查部署依赖") + temp_state.add_log(_("正在检查部署环境依赖...")) progress_callback(temp_state) # 检查操作系统 if not self.detect_openeuler(): - errors.append("仅支持 openEuler 操作系统") + errors.append(_("仅支持 openEuler 操作系统")) return False, errors # 检查 Python 版本兼容性 python_version = sys.version_info current_version = f"{python_version.major}.{python_version.minor}" if python_version < (3, 10) and progress_callback: - temp_state.add_log(f"⚠ 检测到 Python {current_version},低于 3.10 版本将不支持全量部署模式") + warning_msg = _("⚠ 检测到 Python {version},低于 3.10 版本将不支持全量部署模式").format( + version=current_version, + ) + temp_state.add_log(warning_msg) progress_callback(temp_state) # 检查并安装 openeuler-intelligence-installer if not self.resource_manager.check_installer_available(): if progress_callback: - temp_state.add_log("缺少 openeuler-intelligence-installer 包,正在尝试安装...") + temp_state.add_log(_("缺少 openeuler-intelligence-installer 包,正在尝试安装...")) progress_callback(temp_state) success, install_errors = await self._install_intelligence_installer(progress_callback) @@ -213,11 +220,11 @@ class DeploymentService: # 检查 sudo 权限 if not await self.check_sudo_privileges(): - errors.append("需要管理员权限,请确保可以使用 sudo") + errors.append(_("需要管理员权限,请确保可以使用 sudo")) return False, errors if progress_callback: - temp_state.add_log("✓ 部署环境依赖检查完成") + temp_state.add_log(_("✓ 部署环境依赖检查完成")) progress_callback(temp_state) return True, [] @@ -263,17 +270,17 @@ class DeploymentService: # 检查是否低于 3.10 if python_version < (3, 10) and deployment_mode == "full": - return False, ( + return False, _( "当前 openEuler 版本低于 24.03 LTS," - "不支持全量部署模式。请使用轻量部署模式或升级到 openEuler 24.03+ 版本" + "不支持全量部署模式。请使用轻量部署模式或升级到 openEuler 24.03+ 版本", ) except Exception as e: logger.exception("检查 Python 环境版本时发生错误") - return False, f"无法检查 Python 环境: {e}" + return False, _("无法检查 Python 环境: {error}").format(error=e) else: # Python 版本符合要求 - return True, f"Python 环境版本 {current_version} 符合要求" + return True, _("Python 环境版本 {version} 符合要求").format(version=current_version) async def check_sudo_privileges(self) -> bool: """检查 sudo 权限""" @@ -329,8 +336,8 @@ class DeploymentService: logger.exception("部署过程中发生错误") self.state.is_running = False self.state.is_failed = True - self.state.error_message = "部署过程中发生异常" - self.state.add_log("✗ 部署失败") + self.state.error_message = _("部署过程中发生异常") + self.state.add_log(_("✗ 部署失败")) if progress_callback: progress_callback(self.state) @@ -340,7 +347,7 @@ class DeploymentService: # 部署完成,创建全局配置模板供其他用户使用 self.state.is_running = False self.state.is_completed = True - self.state.add_log("✓ openEuler Intelligence 后端部署完成!") + self.state.add_log(_("✓ openEuler Intelligence 后端部署完成!")) # 创建全局配置模板,包含部署时的配置信息 await self._create_global_config_template(config) @@ -378,7 +385,7 @@ class DeploymentService: try: temp_state = DeploymentState() if progress_callback: - temp_state.add_log("正在安装 openeuler-intelligence-installer...") + temp_state.add_log(_("正在安装 openeuler-intelligence-installer...")) progress_callback(temp_state) # 执行安装命令 @@ -389,21 +396,21 @@ class DeploymentService: # 验证安装是否成功 if self.resource_manager.check_installer_available(): if progress_callback: - temp_state.add_log("✓ openeuler-intelligence-installer 安装成功") + temp_state.add_log(_("✓ openeuler-intelligence-installer 安装成功")) progress_callback(temp_state) return True, [] - errors.append("openeuler-intelligence-installer 安装后资源文件仍然缺失") + errors.append(_("openeuler-intelligence-installer 安装后资源文件仍然缺失")) return False, errors - errors.append("安装 openeuler-intelligence-installer 失败") + errors.append(_("安装 openeuler-intelligence-installer 失败")) # 添加安装输出到错误信息 if output_lines: - errors.append("安装输出:") + errors.append(_("安装输出:")) errors.extend(output_lines[-5:]) # 只显示最后5行 except Exception as e: - errors.append(f"安装过程中发生异常: {e}") + errors.append(_("安装过程中发生异常: {error}").format(error=e)) logger.exception("安装 openeuler-intelligence-installer 时发生异常") return False, errors @@ -418,7 +425,6 @@ class DeploymentService: if not await self._check_and_stop_old_service(progress_callback): return False - # 定义基础部署步骤 steps = [ self._setup_deploy_mode, self._check_environment, @@ -426,23 +432,13 @@ class DeploymentService: self._run_install_dependency_script, self._generate_config_files, self._run_init_config_script, + self._run_agent_init, ] - # 轻量化部署模式下才自动执行 Agent 初始化 - if config.deployment_mode == "light": - steps.append(self._run_agent_init) - - # 依次执行每个步骤 for step in steps: if not await step(config, progress_callback): return False - # 如果是全量部署模式,提示用户到网页端完成 Agent 配置 - if config.deployment_mode == "full": - self.state.add_log("✓ 基础服务部署完成") - self.state.add_log("请访问网页管理界面完成 Agent 服务配置") - self.state.add_log(f"管理界面地址: http://{config.server_ip}:8080") - return True async def _execute_install_command( @@ -478,36 +474,36 @@ class DeploymentService: ) -> bool: """检查系统环境和资源""" self.state.current_step = 1 - self.state.current_step_name = "检查系统环境" - self.state.add_log("正在检查系统环境...") + self.state.current_step_name = _("检查系统环境") + self.state.add_log(_("正在检查系统环境...")) if progress_callback: progress_callback(self.state) # 检查操作系统 if not self.detect_openeuler(): - self.state.add_log("✗ 错误: 仅支持 openEuler 操作系统") + self.state.add_log(_("✗ 错误: 仅支持 openEuler 操作系统")) return False - self.state.add_log("✓ 检测到 openEuler 操作系统") + self.state.add_log(_("✓ 检测到 openEuler 操作系统")) # 检查 openEuler & Python 版本是否支持指定的部署模式 python_check_ok, python_msg = self.check_python_version_for_deployment(config.deployment_mode) if not python_check_ok: - self.state.add_log(f"✗ 错误: {python_msg}") + self.state.add_log(_("✗ 错误: {msg}").format(msg=python_msg)) return False # 检查安装器资源 if not self.resource_manager.check_installer_available(): - self.state.add_log("✗ 错误: openeuler-intelligence-installer 包未安装或资源缺失") - self.state.add_log("请先安装: sudo dnf install -y openeuler-intelligence-installer") + self.state.add_log(_("✗ 错误: openeuler-intelligence-installer 包未安装或资源缺失")) + self.state.add_log(_("请先安装: sudo dnf install -y openeuler-intelligence-installer")) return False - self.state.add_log("✓ openeuler-intelligence-installer 资源可用") + self.state.add_log(_("✓ openeuler-intelligence-installer 资源可用")) # 检查权限 if not await self.check_sudo_privileges(): - self.state.add_log("✗ 错误: 需要管理员权限") + self.state.add_log(_("✗ 错误: 需要管理员权限")) return False - self.state.add_log("✓ 具有管理员权限") + self.state.add_log(_("✓ 具有管理员权限")) return True @@ -518,8 +514,8 @@ class DeploymentService: ) -> bool: """设置部署模式""" self.state.current_step = 0 - self.state.current_step_name = "初始化部署配置" - self.state.add_log("正在设置部署模式...") + self.state.current_step_name = _("初始化部署配置") + self.state.add_log(_("正在设置部署模式...")) if progress_callback: progress_callback(self.state) @@ -542,19 +538,20 @@ class DeploymentService: stderr=asyncio.subprocess.PIPE, ) - stdout, stderr = await process.communicate(mode_content.encode()) + _stdout, stderr = await process.communicate(mode_content.encode()) if process.returncode != 0: error_msg = stderr.decode("utf-8", errors="ignore").strip() - self.state.add_log(f"✗ 设置部署模式失败: {error_msg}") + self.state.add_log(_("✗ 设置部署模式失败: {error}").format(error=error_msg)) return False - web_status = "启用" if config.enable_web else "禁用" - rag_status = "启用" if config.enable_rag else "禁用" - self.state.add_log(f"✓ 部署模式设置完成 (Web界面: {web_status}, RAG: {rag_status})") + web_status = _("启用") if config.enable_web else _("禁用") + rag_status = _("启用") if config.enable_rag else _("禁用") + status_msg = _("✓ 部署模式设置完成 (Web界面: {web}, RAG: {rag})").format(web=web_status, rag=rag_status) + self.state.add_log(status_msg) except Exception as e: - self.state.add_log(f"✗ 设置部署模式失败: {e}") + self.state.add_log(_("✗ 设置部署模式失败: {error}").format(error=e)) logger.exception("设置部署模式失败") return False @@ -567,17 +564,17 @@ class DeploymentService: ) -> bool: """运行环境检查脚本""" self.state.current_step = 1 - self.state.current_step_name = "检查系统环境" - self.state.add_log("正在执行系统环境检查...") + self.state.current_step_name = _("检查系统环境") + self.state.add_log(_("正在执行系统环境检查...")) if progress_callback: progress_callback(self.state) try: script_path = self.resource_manager.INSTALLER_BASE_PATH / "1-check-env" / "check_env.sh" - return await self._run_script(script_path, "环境检查脚本", progress_callback) + return await self._run_script(script_path, _("环境检查脚本"), progress_callback) except Exception as e: - self.state.add_log(f"✗ 环境检查失败: {e}") + self.state.add_log(_("✗ 环境检查失败: {error}").format(error=e)) logger.exception("环境检查脚本执行失败") return False @@ -588,8 +585,8 @@ class DeploymentService: ) -> bool: """运行依赖安装脚本""" self.state.current_step = 2 - self.state.current_step_name = "安装依赖组件" - self.state.add_log("正在安装 openEuler Intelligence 依赖组件...") + self.state.current_step_name = _("安装依赖组件") + self.state.add_log(_("正在安装 openEuler Intelligence 依赖组件...")) if progress_callback: progress_callback(self.state) @@ -598,9 +595,9 @@ class DeploymentService: script_path = ( self.resource_manager.INSTALLER_BASE_PATH / "2-install-dependency" / "install_openEulerIntelligence.sh" ) - return await self._run_script(script_path, "依赖安装脚本", progress_callback) + return await self._run_script(script_path, _("依赖安装脚本"), progress_callback) except Exception as e: - self.state.add_log(f"✗ 依赖安装失败: {e}") + self.state.add_log(_("✗ 依赖安装失败: {error}").format(error=e)) logger.exception("依赖安装脚本执行失败") return False @@ -611,17 +608,17 @@ class DeploymentService: ) -> bool: """运行配置初始化脚本""" self.state.current_step = 4 - self.state.current_step_name = "初始化配置和服务" - self.state.add_log("正在初始化配置和启动服务...") + self.state.current_step_name = _("初始化配置和服务") + self.state.add_log(_("正在初始化配置和启动服务...")) if progress_callback: progress_callback(self.state) try: script_path = self.resource_manager.INSTALLER_BASE_PATH / "3-install-server" / "init_config.sh" - return await self._run_script(script_path, "配置初始化脚本", progress_callback) + return await self._run_script(script_path, _("配置初始化脚本"), progress_callback) except Exception as e: - self.state.add_log(f"✗ 配置初始化失败: {e}") + self.state.add_log(_("✗ 配置初始化失败: {error}").format(error=e)) logger.exception("配置初始化脚本执行失败") return False @@ -633,7 +630,7 @@ class DeploymentService: ) -> bool: """运行部署脚本""" if not script_path.exists(): - self.state.add_log(f"✗ 脚本文件不存在: {script_path}") + self.state.add_log(_("✗ 脚本文件不存在: {path}").format(path=script_path)) return False try: @@ -671,16 +668,16 @@ class DeploymentService: self._process = None if return_code == 0: - self.state.add_log(f"✓ {script_name}执行成功") + self.state.add_log(_("✓ {name}执行成功").format(name=script_name)) return True except Exception as e: - self.state.add_log(f"✗ 运行{script_name}时发生错误: {e}") + self.state.add_log(_("✗ 运行{name}时发生错误: {error}").format(name=script_name, error=e)) logger.exception("运行脚本失败: %s", script_path) return False else: - self.state.add_log(f"✗ {script_name}执行失败,返回码: {return_code}") + self.state.add_log(_("✗ {name}执行失败,返回码: {code}").format(name=script_name, code=return_code)) return False async def _heartbeat_progress(self, progress_callback: Callable[[DeploymentState], None] | None) -> None: @@ -701,8 +698,8 @@ class DeploymentService: ) -> bool: """生成配置文件""" self.state.current_step = 3 - self.state.current_step_name = "更新配置文件" - self.state.add_log("正在更新配置文件...") + self.state.current_step_name = _("更新配置文件") + self.state.add_log(_("正在更新配置文件...")) if progress_callback: progress_callback(self.state) @@ -710,14 +707,14 @@ class DeploymentService: try: # 更新 env 文件 await self._update_env_file(config) - self.state.add_log("✓ 更新 env 配置文件") + self.state.add_log(_("✓ 更新 env 配置文件")) # 更新 config.toml 文件 await self._update_config_toml(config) - self.state.add_log("✓ 更新 config.toml 配置文件") + self.state.add_log(_("✓ 更新 config.toml 配置文件")) except Exception as e: - self.state.add_log(f"✗ 更新配置文件失败: {e}") + self.state.add_log(_("✗ 更新配置文件失败: {error}").format(error=e)) logger.exception("更新配置文件失败") return False @@ -746,11 +743,11 @@ class DeploymentService: stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - _, backup_stderr = await backup_process.communicate() + _backup_stdout, backup_stderr = await backup_process.communicate() if backup_process.returncode != 0: error_msg = backup_stderr.decode("utf-8", errors="ignore").strip() - msg = f"备份 env 文件失败: {error_msg}" + msg = _("备份 env 文件失败: {error}").format(error=error_msg) raise RuntimeError(msg) # 写入更新后的内容 @@ -761,12 +758,11 @@ class DeploymentService: stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - - _, write_stderr = await process.communicate(updated_content.encode()) + _write_stdout, write_stderr = await process.communicate(updated_content.encode()) if process.returncode != 0: error_msg = write_stderr.decode("utf-8", errors="ignore").strip() - msg = f"写入 env 文件失败: {error_msg}" + msg = _("写入 env 文件失败: {error}").format(error=error_msg) raise RuntimeError(msg) async def _update_config_toml(self, config: DeploymentConfig) -> None: @@ -792,11 +788,11 @@ class DeploymentService: stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - _, backup_stderr = await backup_process.communicate() + _backup_stdout, backup_stderr = await backup_process.communicate() if backup_process.returncode != 0: error_msg = backup_stderr.decode("utf-8", errors="ignore").strip() - msg = f"备份 config.toml 文件失败: {error_msg}" + msg = _("备份 config.toml 文件失败: {error}").format(error=error_msg) raise RuntimeError(msg) # 写入更新后的内容 @@ -807,12 +803,11 @@ class DeploymentService: stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - - _, write_stderr = await process.communicate(updated_content.encode()) + _write_stdout, write_stderr = await process.communicate(updated_content.encode()) if process.returncode != 0: error_msg = write_stderr.decode("utf-8", errors="ignore").strip() - msg = f"写入 config.toml 文件失败: {error_msg}" + msg = _("写入 config.toml 文件失败: {error}").format(error=error_msg) raise RuntimeError(msg) async def _read_process_output_lines(self, process: asyncio.subprocess.Process) -> AsyncGenerator[str, None]: @@ -839,7 +834,7 @@ class DeploymentService: async def _check_framework_service_health( self, - server_ip: str, + server_host: str, server_port: int, progress_callback: Callable[[DeploymentState], None] | None, ) -> bool: @@ -849,7 +844,7 @@ class DeploymentService: return False # 2. 检查 HTTP API 接口连通性 - return await self._check_framework_api_health(server_ip, server_port, progress_callback) + return await self._check_framework_api_health(server_host, server_port, progress_callback) async def _check_systemctl_service_status( self, @@ -860,7 +855,12 @@ class DeploymentService: check_interval = 2.0 # 2秒 for attempt in range(1, max_attempts + 1): - self.state.add_log(f"检查 oi-runtime 服务状态 ({attempt}/{max_attempts})...") + self.state.add_log( + _("检查 oi-runtime 服务状态 ({current}/{total})...").format( + current=attempt, + total=max_attempts, + ), + ) if progress_callback: progress_callback(self.state) @@ -875,40 +875,40 @@ class DeploymentService: stderr=asyncio.subprocess.PIPE, ) - stdout, stderr = await process.communicate() + stdout, _stderr = await process.communicate() status = stdout.decode("utf-8").strip() if process.returncode == 0 and status == "active": - self.state.add_log("✓ Framework 服务状态正常") + self.state.add_log(_("✓ Framework 服务状态正常")) return True - self.state.add_log(f"Framework 服务状态: {status}") + self.state.add_log(_("Framework 服务状态: {status}").format(status=status)) if attempt < max_attempts: - self.state.add_log(f"等待 {check_interval} 秒后重试...") + self.state.add_log(_("等待 {seconds} 秒后重试...").format(seconds=check_interval)) await asyncio.sleep(check_interval) except (OSError, TimeoutError) as e: - self.state.add_log(f"检查服务状态时发生错误: {e}") + self.state.add_log(_("检查服务状态时发生错误: {error}").format(error=e)) if attempt < max_attempts: await asyncio.sleep(check_interval) - self.state.add_log("✗ Framework 服务状态检查超时失败") + self.state.add_log(_("✗ Framework 服务状态检查超时失败")) return False async def _check_framework_api_health( self, - server_ip: str, + server_host: str, server_port: int, progress_callback: Callable[[DeploymentState], None] | None, ) -> bool: """检查 oi-runtime API 健康状态,每10秒检查一次,5分钟后超时""" max_attempts = 30 check_interval = 10.0 # 10秒 - api_url = f"http://{server_ip}:{server_port}/api/user" + api_url = f"http://{server_host}:{server_port}/api/user" http_ok = 200 # HTTP OK 状态码 - self.state.add_log("等待 openEuler Intelligence 服务就绪") + self.state.add_log(_("等待 openEuler Intelligence 服务就绪")) async with httpx.AsyncClient(timeout=httpx.Timeout(5.0)) as client: for attempt in range(1, max_attempts + 1): @@ -920,20 +920,20 @@ class DeploymentService: response = await client.get(api_url) if response.status_code == http_ok: - self.state.add_log("✓ openEuler Intelligence 服务已就绪") + self.state.add_log(_("✓ openEuler Intelligence 服务已就绪")) return True except httpx.ConnectError: pass except httpx.TimeoutException: - self.state.add_log(f"连接 {api_url} 超时") + self.state.add_log(_("连接 {url} 超时").format(url=api_url)) except (httpx.RequestError, OSError) as e: - self.state.add_log(f"API 连通性检查时发生错误: {e}") + self.state.add_log(_("API 连通性检查时发生错误: {error}").format(error=e)) if attempt < max_attempts: await asyncio.sleep(check_interval) - self.state.add_log("✗ openEuler Intelligence API 服务检查超时失败") + self.state.add_log(_("✗ openEuler Intelligence API 服务检查超时失败")) return False async def _run_agent_init( @@ -943,36 +943,36 @@ class DeploymentService: ) -> bool: """运行 Agent 初始化脚本""" self.state.current_step = 5 - self.state.current_step_name = "初始化 Agent 服务" - self.state.add_log("正在检查 openEuler Intelligence 后端服务状态...") + self.state.current_step_name = _("初始化 Agent 服务") + self.state.add_log(_("正在检查 openEuler Intelligence 后端服务状态...")) if progress_callback: progress_callback(self.state) - # 使用配置中的服务器 IP 和默认端口 - server_ip = config.server_ip or "127.0.0.1" + # 使用固定的本地服务地址和默认端口 + server_host = LOCAL_DEPLOYMENT_HOST server_port = 8002 # 检查 openEuler Intelligence 后端服务状态 - if not await self._check_framework_service_health(server_ip, server_port, progress_callback): - self.state.add_log("✗ openEuler Intelligence 服务检查失败") + if not await self._check_framework_service_health(server_host, server_port, progress_callback): + self.state.add_log(_("✗ openEuler Intelligence 服务检查失败")) return False - self.state.add_log("✓ openEuler Intelligence 服务检查通过,开始初始化 Agent...") + self.state.add_log(_("✓ openEuler Intelligence 服务检查通过,开始初始化 Agent...")) if progress_callback: progress_callback(self.state) # 初始化 Agent 和 MCP 服务 - agent_manager = AgentManager(server_ip=server_ip, server_port=server_port) + agent_manager = AgentManager() init_status = await agent_manager.initialize_agents(self.state, progress_callback) if init_status == AgentInitStatus.SUCCESS: - self.state.add_log("✓ Agent 初始化完成") + self.state.add_log(_("✓ Agent 初始化完成")) return True if init_status == AgentInitStatus.SKIPPED: - self.state.add_log("⚠ Agent 初始化已跳过(RPM 包不可用),但部署将继续进行") + self.state.add_log(_("⚠ Agent 初始化已跳过(RPM 包不可用),但部署将继续进行")) return True # 跳过不算失败,继续部署 # FAILED @@ -1011,22 +1011,22 @@ class DeploymentService: success = template_manager.create_global_template() if success: - self.state.add_log("✓ 全局配置模板创建成功,其他用户可正常使用") + self.state.add_log(_("✓ 全局配置模板创建成功,其他用户可正常使用")) logger.info("全局配置模板创建成功,包含部署时的完整配置信息") else: - self.state.add_log("⚠ 全局配置模板创建失败,可能影响其他用户使用") + self.state.add_log(_("⚠ 全局配置模板创建失败,可能影响其他用户使用")) logger.warning("全局配置模板创建失败") except Exception: logger.exception("创建全局配置模板时发生异常") - self.state.add_log("⚠ 配置模板创建异常,可能影响其他用户使用") + self.state.add_log(_("⚠ 配置模板创建异常,可能影响其他用户使用")) def _update_backend_url_config(self, config: DeploymentConfig) -> None: """ 更新当前用户的配置 - 在部署开始时根据用户填写的服务器IP和部署模式 - 更新 openEuler Intelligence 后端 URL + 在部署开始时根据部署模式 + 更新 openEuler Intelligence 后端的 URL 配置 Args: config: 部署配置 @@ -1036,13 +1036,13 @@ class DeploymentService: config_manager = ConfigManager() # 根据部署配置更新 openEuler Intelligence 后端 URL - server_ip = config.server_ip or "127.0.0.1" + server_host = LOCAL_DEPLOYMENT_HOST if config.deployment_mode == "full": # 全量部署模式:有 nginx,端口是 8080 - eulerintelli_url = f"http://{server_ip}:8080" + eulerintelli_url = f"http://{server_host}:8080" else: # 轻量部署模式:无 nginx,端口是 8002 - eulerintelli_url = f"http://{server_ip}:8002" + eulerintelli_url = f"http://{server_host}:8002" config_manager.set_eulerintelli_url(eulerintelli_url) logger.info("已更新当前用户 openEuler Intelligence 后端 URL: %s", eulerintelli_url) @@ -1080,7 +1080,7 @@ class DeploymentService: stderr=asyncio.subprocess.PIPE, ) - stdout, stderr = await process.communicate() + stdout, _stderr = await process.communicate() status = stdout.decode("utf-8").strip() if process.returncode == 0 and status == "active": diff --git a/src/app/deployment/ui.py b/src/app/deployment/ui.py index e07f1ee086f4756e1c2ace48b4cc4a4f466aa208..3ad6d948500bae0dd0872a7002eac93d3eec9331 100644 --- a/src/app/deployment/ui.py +++ b/src/app/deployment/ui.py @@ -26,9 +26,10 @@ from textual.widgets import ( ) from app.tui_header import OIHeader +from i18n.manager import _ from .models import DeploymentConfig, DeploymentState, EmbeddingConfig, LLMConfig -from .service import DeploymentService +from .service import LOCAL_DEPLOYMENT_HOST, DeploymentService if TYPE_CHECKING: from textual.app import ComposeResult @@ -137,18 +138,18 @@ class DeploymentConfigScreen(ModalScreen[bool]): yield OIHeader() with TabbedContent(): - with TabPane("基础配置", id="basic"): + with TabPane(_("基础配置"), id="basic"): yield from self._compose_basic_config() - with TabPane("LLM 配置", id="llm"): + with TabPane(_("LLM 配置"), id="llm"): yield from self._compose_llm_config() - with TabPane("Embedding 配置", id="embedding"): + with TabPane(_("Embedding 配置"), id="embedding"): yield from self._compose_embedding_config() with Horizontal(classes="button-row"): - yield Button("开始部署", id="deploy", variant="success") - yield Button("取消", id="cancel", variant="error") + yield Button(_("开始部署"), id="deploy", variant="success") + yield Button(_("取消"), id="cancel", variant="error") async def on_mount(self) -> None: """界面挂载时初始化状态""" @@ -165,11 +166,11 @@ class DeploymentConfigScreen(ModalScreen[bool]): desc = self.query_one("#deployment_mode_desc", Static) if self.config.deployment_mode == "full": - btn.label = "全量部署" - desc.update("全量部署:部署框架服务 + Web 界面 + RAG 组件,自动初始化 Agent。") + btn.label = _("全量部署") + desc.update(_("全量部署:部署框架服务 + Web 界面 + RAG 组件,自动初始化 Agent。")) else: - btn.label = "轻量部署" - desc.update("轻量部署:仅部署框架服务,自动初始化 Agent。") + btn.label = _("轻量部署") + desc.update(_("轻量部署:仅部署框架服务,自动初始化 Agent。")) except (ValueError, AttributeError): # 如果 UI 组件还没初始化完成,忽略错误 @@ -185,7 +186,7 @@ class DeploymentConfigScreen(ModalScreen[bool]): # 如果不需要验证 Embedding,显示相应状态 try: embedding_status = self.query_one("#embedding_validation_status", Static) - embedding_status.update("[dim]不需要验证[/dim]") + embedding_status.update(_("[dim]不需要验证[/dim]")) except (ValueError, AttributeError): pass @@ -195,26 +196,21 @@ class DeploymentConfigScreen(ModalScreen[bool]): def _compose_basic_config(self) -> ComposeResult: """组合基础配置组件""" with Vertical(): - yield Static("基础配置", classes="form-label") + yield Static(_("基础配置"), classes="form-label") with Horizontal(classes="form-row"): - yield Label("服务器 IP 地址:", classes="form-label") - yield Input( - value="127.0.0.1", # 默认为本地地址 - placeholder="例如:127.0.0.1", - id="server_ip", - classes="form-input", - ) + yield Label(_("服务器 IP 地址:"), classes="form-label") + yield Static(LOCAL_DEPLOYMENT_HOST, classes="form-input") with Horizontal(classes="form-row"): - yield Label("部署模式:", classes="form-label") + yield Label(_("部署模式:"), classes="form-label") # 使用按钮在轻量/全量间切换,按钮文本显示当前选择(不包含括号描述) - yield Button("轻量部署", id="deployment_mode_btn", classes="form-input", variant="primary") + yield Button(_("轻量部署"), id="deployment_mode_btn", classes="form-input", variant="primary") # 描述区域,显示当前部署模式的详细说明 with Horizontal(classes="form-row"): yield Static( - "轻量部署:仅部署框架服务,自动初始化 Agent。", + _("轻量部署:仅部署框架服务,自动初始化 Agent。"), id="deployment_mode_desc", classes="form-input", ) @@ -222,18 +218,18 @@ class DeploymentConfigScreen(ModalScreen[bool]): def _compose_llm_config(self) -> ComposeResult: """组合 LLM 配置组件""" with Vertical(classes="llm-config-container"): - yield Static("大语言模型配置", classes="form-label") + yield Static(_("大语言模型配置"), classes="form-label") with Horizontal(classes="form-row"): - yield Label("API 端点:", classes="form-label") + yield Label(_("API 端点:"), classes="form-label") yield Input( - placeholder="例如:http://localhost:11434/v1", + placeholder=_("例如:http://localhost:11434/v1"), id="llm_endpoint", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("API 密钥:", classes="form-label") + yield Label(_("API 密钥:"), classes="form-label") yield Input( placeholder="sk-123456", password=True, @@ -242,19 +238,19 @@ class DeploymentConfigScreen(ModalScreen[bool]): ) with Horizontal(classes="form-row"): - yield Label("模型名称:", classes="form-label") + yield Label(_("模型名称:"), classes="form-label") yield Input( - placeholder="例如:deepseek-llm-7b-chat", + placeholder=_("例如:deepseek-llm-7b-chat"), id="llm_model", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("验证状态:", classes="form-label") - yield Static("未验证", id="llm_validation_status", classes="form-input") + yield Label(_("验证状态:"), classes="form-label") + yield Static(_("未验证"), id="llm_validation_status", classes="form-input") with Horizontal(classes="form-row"): - yield Label("最大输出令牌数:", classes="form-label") + yield Label(_("最大输出令牌数:"), classes="form-label") yield Input( value="8192", id="llm_max_tokens", @@ -262,7 +258,7 @@ class DeploymentConfigScreen(ModalScreen[bool]): ) with Horizontal(classes="form-row"): - yield Label("Temperature:", classes="form-label") + yield Label(_("Temperature:"), classes="form-label") yield Input( value="0.7", id="llm_temperature", @@ -270,7 +266,7 @@ class DeploymentConfigScreen(ModalScreen[bool]): ) with Horizontal(classes="form-row"): - yield Label("请求超时 (秒):", classes="form-label") + yield Label(_("请求超时 (秒):"), classes="form-label") yield Input( value="300", id="llm_timeout", @@ -280,25 +276,25 @@ class DeploymentConfigScreen(ModalScreen[bool]): def _compose_embedding_config(self) -> ComposeResult: """组合 Embedding 配置组件""" with Vertical(classes="embedding-config-container"): - yield Static("嵌入模型配置", classes="form-label") + yield Static(_("嵌入模型配置"), classes="form-label") # 添加轻量部署说明 yield Static( - "[dim]轻量部署模式下,Embedding 配置为可选项。[/dim]", + _("[dim]轻量部署模式下,Embedding 配置为可选项。[/dim]"), id="embedding_mode_hint", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("API 端点:", classes="form-label") + yield Label(_("API 端点:"), classes="form-label") yield Input( - placeholder="例如:http://localhost:11434/v1", + placeholder=_("例如:http://localhost:11434/v1"), id="embedding_endpoint", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("API 密钥:", classes="form-label") + yield Label(_("API 密钥:"), classes="form-label") yield Input( placeholder="sk-123456", password=True, @@ -307,16 +303,16 @@ class DeploymentConfigScreen(ModalScreen[bool]): ) with Horizontal(classes="form-row"): - yield Label("模型名称:", classes="form-label") + yield Label(_("模型名称:"), classes="form-label") yield Input( - placeholder="例如:bge-m3", + placeholder=_("例如:bge-m3"), id="embedding_model", classes="form-input", ) with Horizontal(classes="form-row"): - yield Label("验证状态:", classes="form-label") - yield Static("未验证", id="embedding_validation_status", classes="form-input") + yield Label(_("验证状态:"), classes="form-label") + yield Static(_("未验证"), id="embedding_validation_status", classes="form-input") @on(Button.Pressed, "#deploy") async def on_deploy_button_pressed(self) -> None: @@ -326,7 +322,7 @@ class DeploymentConfigScreen(ModalScreen[bool]): is_valid, errors = self.config.validate() if not is_valid: await self.app.push_screen( - ErrorMessageScreen("配置验证失败", errors), + ErrorMessageScreen(_("配置验证失败"), errors), ) return @@ -365,11 +361,11 @@ class DeploymentConfigScreen(ModalScreen[bool]): hint_widget = self.query_one("#embedding_mode_hint", Static) if is_light_mode: hint_widget.update( - "[dim]轻量部署模式下,Embedding 配置为可选项。如果不填写,将跳过 RAG 功能。[/dim]", + _("[dim]轻量部署模式下,Embedding 配置为可选项。如果不填写,将跳过 RAG 功能。[/dim]"), ) else: hint_widget.update( - "[dim]全量部署模式下,Embedding 配置为必填项,用于支持 RAG 功能。[/dim]", + _("[dim]全量部署模式下,Embedding 配置为必填项,用于支持 RAG 功能。[/dim]"), ) except (AttributeError, ValueError): # 如果控件不存在,忽略错误 @@ -514,24 +510,21 @@ class DeploymentConfigScreen(ModalScreen[bool]): supports_function_call = info.get("supports_function_call", False) if supports_function_call: self.llm_validation_status = ValidationStatus.VALID - status_widget.update(f"[green]✓ {message}[/green]") - self.notify("LLM 验证成功,支持工具调用功能", severity="information") + status_widget.update(_("[green]✓ {message}[/green]").format(message=message)) else: self.llm_validation_status = ValidationStatus.INVALID - status_widget.update("[red]✗ 不支持工具调用[/red]") + status_widget.update(_("[red]✗ 不支持工具调用[/red]")) self.notify( - "LLM 验证失败:模型不支持工具调用功能,无法用于部署。请选择支持工具调用的模型。", + _("LLM 验证失败:模型不支持工具调用功能,无法用于部署。请选择支持工具调用的模型。"), severity="error", ) else: self.llm_validation_status = ValidationStatus.INVALID - status_widget.update(f"[red]✗ {message}[/red]") - self.notify(f"LLM 验证失败: {message}", severity="error") + status_widget.update(_("[red]✗ {message}[/red]").format(message=message)) except (OSError, ValueError, TypeError) as e: self.llm_validation_status = ValidationStatus.INVALID - status_widget.update(f"[red]✗ 验证异常: {e}[/red]") - self.notify(f"LLM 验证过程中出现异常: {e}", severity="error") + status_widget.update(_("[red]✗ 验证异常: {error}[/red]").format(error=e)) # 更新部署按钮状态 self._update_deploy_button_state() @@ -554,19 +547,18 @@ class DeploymentConfigScreen(ModalScreen[bool]): # 更新验证状态 if is_valid: self.embedding_validation_status = ValidationStatus.VALID - status_widget.update(f"[green]✓ {message}[/green]") - # 显示维度信息 dimension = info.get("dimension", "未知") - self.notify(f"Embedding 验证成功,向量维度: {dimension}", severity="information") + status_widget.update(_("[green]✓ {message} (维度: {dimension})[/green]").format( + message=message, + dimension=dimension, + )) else: self.embedding_validation_status = ValidationStatus.INVALID - status_widget.update(f"[red]✗ {message}[/red]") - self.notify(f"Embedding 验证失败: {message}", severity="error") + status_widget.update(_("[red]✗ {message}[/red]").format(message=message)) except (OSError, ValueError, TypeError) as e: self.embedding_validation_status = ValidationStatus.INVALID - status_widget.update(f"[red]✗ 验证异常: {e}[/red]") - self.notify(f"Embedding 验证过程中出现异常: {e}", severity="error") + status_widget.update(_("[red]✗ 验证异常: {error}[/red]").format(error=e)) # 更新部署按钮状态 self._update_deploy_button_state() @@ -599,9 +591,6 @@ class DeploymentConfigScreen(ModalScreen[bool]): def _collect_config(self) -> bool: """收集用户配置""" try: - # 基础配置 - self.config.server_ip = self.query_one("#server_ip", Input).value.strip() - # LLM 配置 self.config.llm = LLMConfig( endpoint=self.query_one("#llm_endpoint", Input).value.strip(), @@ -710,17 +699,17 @@ class DeploymentProgressScreen(ModalScreen[bool]): yield OIHeader() with Vertical(classes="progress-section"): - yield Static("部署进度:", id="progress_label") - yield Static("准备开始部署...", id="step_label") + yield Static(_("部署进度:"), id="progress_label") + yield Static(_("准备开始部署..."), id="step_label") with Container(classes="log-section"): yield RichLog(id="deployment_log", highlight=True, markup=True) with Horizontal(classes="button-section"): - yield Button("完成", id="finish", variant="success", disabled=True) - yield Button("重试", id="retry", variant="warning", disabled=True) - yield Button("重新配置", id="reconfigure", variant="primary", disabled=True) - yield Button("取消部署", id="cancel", variant="error") + yield Button(_("完成"), id="finish", variant="success", disabled=True) + yield Button(_("重试"), id="retry", variant="warning", disabled=True) + yield Button(_("重新配置"), id="reconfigure", variant="primary", disabled=True) + yield Button(_("取消部署"), id="cancel", variant="error") async def on_mount(self) -> None: """界面挂载时开始部署""" @@ -755,8 +744,8 @@ class DeploymentProgressScreen(ModalScreen[bool]): self.deployment_cancelled = True # 更新界面 - self.query_one("#step_label", Static).update("部署已取消") - self.query_one("#deployment_log", RichLog).write("部署已被用户取消") + self.query_one("#step_label", Static).update(_("部署已取消")) + self.query_one("#deployment_log", RichLog).write(_("部署已被用户取消")) # 等待任务真正结束 with contextlib.suppress(asyncio.CancelledError): @@ -819,8 +808,8 @@ class DeploymentProgressScreen(ModalScreen[bool]): self.set_interval(0.1, self._check_deployment_status) except (OSError, RuntimeError) as e: - self.query_one("#step_label", Static).update("部署启动失败") - self.query_one("#deployment_log", RichLog).write(f"部署启动失败: {e}") + self.query_one("#step_label", Static).update(_("部署启动失败")) + self.query_one("#deployment_log", RichLog).write(_("部署启动失败: {error}").format(error=e)) self._update_buttons_after_failure() def _check_deployment_status(self) -> None: @@ -836,23 +825,23 @@ class DeploymentProgressScreen(ModalScreen[bool]): except asyncio.CancelledError: if not self.deployment_cancelled: self.deployment_cancelled = True - self.query_one("#step_label", Static).update("部署已取消") - self.query_one("#deployment_log", RichLog).write("部署被取消") + self.query_one("#step_label", Static).update(_("部署已取消")) + self.query_one("#deployment_log", RichLog).write(_("部署被取消")) self._update_buttons_after_failure() except (OSError, RuntimeError, ValueError) as e: - self.query_one("#step_label", Static).update("部署异常") - self.query_one("#deployment_log", RichLog).write(f"部署异常: {e}") + self.query_one("#step_label", Static).update(_("部署异常")) + self.query_one("#deployment_log", RichLog).write(_("部署异常: {error}").format(error=e)) self._update_buttons_after_failure() async def _execute_deployment(self) -> None: """执行部署过程""" try: # 步骤1:检查并安装依赖 - self.query_one("#step_label", Static).update("正在检查部署环境...") + self.query_one("#step_label", Static).update(_("正在检查部署环境...")) success, errors = await self.service.check_and_install_dependencies(self._on_progress_update) if not success: - self.query_one("#step_label", Static).update("环境检查失败") + self.query_one("#step_label", Static).update(_("环境检查失败")) for error in errors: self.query_one("#deployment_log", RichLog).write(f"[red]✗ {error}[/red]") self.deployment_errors.append(error) @@ -860,35 +849,34 @@ class DeploymentProgressScreen(ModalScreen[bool]): return # 步骤2:执行部署 - self.query_one("#step_label", Static).update("正在执行部署...") + self.query_one("#step_label", Static).update(_("正在执行部署...")) success = await self.service.deploy(self.config, self._on_progress_update) # 更新界面状态 if success: self.deployment_success = True - self.query_one("#step_label", Static).update("部署完成!") + self.query_one("#step_label", Static).update(_("部署完成!")) self.query_one("#deployment_log", RichLog).write( - "[bold green]部署成功完成![/bold green]", + _("[bold green]部署成功完成![/bold green]"), ) self._update_buttons_after_success() - self.notify("部署成功完成!", severity="information") + self.notify(_("部署成功完成!"), severity="information") else: - self.query_one("#step_label", Static).update("部署失败") + self.query_one("#step_label", Static).update(_("部署失败")) self.query_one("#deployment_log", RichLog).write( - "[bold red]部署失败,请查看上面的错误信息[/bold red]", + _("[bold red]部署失败,请查看上面的错误信息[/bold red]"), ) - self.deployment_errors.append("部署执行失败") + self.deployment_errors.append(_("部署执行失败")) self._update_buttons_after_failure() - self.notify("部署失败,可以重试或重新配置参数", severity="error") + self.notify(_("部署失败,可以重试或重新配置参数"), severity="error") except OSError as e: - error_msg = f"部署过程中发生异常: {e}" - self.query_one("#step_label", Static).update("部署异常") + error_msg = _("部署过程中发生异常: {error}").format(error=e) + self.query_one("#step_label", Static).update(_("部署异常")) self.query_one("#deployment_log", RichLog).write(f"[bold red]{error_msg}[/bold red]") self.deployment_errors.append(error_msg) self._update_buttons_after_failure() - self.notify("部署异常,可以重试或重新配置参数", severity="error") def _on_progress_update(self, state: DeploymentState) -> None: """处理进度更新""" @@ -902,7 +890,11 @@ class DeploymentProgressScreen(ModalScreen[bool]): self.deployment_progress_value = progress # 更新步骤标签 - step_text = f"步骤 {state.current_step}/{state.total_steps}: {state.current_step_name}" + step_text = _("步骤 {current}/{total}: {name}").format( + current=state.current_step, + total=state.total_steps, + name=state.current_step_name, + ) self.query_one("#step_label", Static).update(step_text) # 添加最新的日志条目 @@ -971,13 +963,13 @@ class ErrorMessageScreen(ModalScreen[None]): def compose(self) -> ComposeResult: """组合界面组件""" with Container(classes="error-container"): - yield Static(self.title or "错误", classes="error-title") + yield Static(self.title or _("错误"), classes="error-title") with Vertical(classes="error-list"): for message in self.messages: yield Static(f"• {message}") - yield Button("确定", id="ok", variant="primary") + yield Button(_("确定"), id="ok", variant="primary") @on(Button.Pressed, "#ok") def on_ok_button_pressed(self) -> None: diff --git a/src/app/dialogs/__init__.py b/src/app/dialogs/__init__.py index b73f00caeed85cc2636726e7ad9c556392c6ce6c..eb86d08b20bb8cd38253f8d7b018acd82503584a 100644 --- a/src/app/dialogs/__init__.py +++ b/src/app/dialogs/__init__.py @@ -2,5 +2,6 @@ from .agent import AgentSelectionDialog, BackendRequiredDialog from .common import ExitDialog +from .user import UserConfigDialog -__all__ = ["AgentSelectionDialog", "BackendRequiredDialog", "ExitDialog"] +__all__ = ["AgentSelectionDialog", "BackendRequiredDialog", "ExitDialog", "UserConfigDialog"] diff --git a/src/app/dialogs/agent.py b/src/app/dialogs/agent.py index b1cd115262d9d504b397ad9c1878a8ca3e96d728..1e405318e4df4859a8855ed167b6f77c73d0eb23 100644 --- a/src/app/dialogs/agent.py +++ b/src/app/dialogs/agent.py @@ -122,6 +122,7 @@ class AgentSelectionDialog(ModalScreen): """处理键盘事件""" if event.key == "escape": self.app.pop_screen() + event.stop() elif event.key == "enter": # 确保有智能体可选择 if self.agents and 0 <= self.selected_index < len(self.agents): @@ -130,6 +131,7 @@ class AgentSelectionDialog(ModalScreen): selected_agent = ("", _("智能问答")) self.callback(selected_agent) self.app.pop_screen() + event.stop() elif event.key == "up" and self.selected_index > 0: self.selected_index -= 1 self._adjust_view_to_selection() diff --git a/src/app/dialogs/user.py b/src/app/dialogs/user.py new file mode 100644 index 0000000000000000000000000000000000000000..f62d0415425f6bbd7612ec1efd1ef17194fd7287 --- /dev/null +++ b/src/app/dialogs/user.py @@ -0,0 +1,402 @@ +"""用户配置对话框""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from textual import on +from textual.containers import Container, Horizontal +from textual.screen import ModalScreen +from textual.widgets import Button, Label, Static, TabbedContent, TabPane + +from backend import HermesChatClient +from backend.models import LLMType, ModelInfo +from i18n.manager import _ + +if TYPE_CHECKING: + from textual.app import ComposeResult + from textual.events import Key + + from backend import LLMClientBase + from config.manager import ConfigManager + + +class UserConfigDialog(ModalScreen): + """用户配置对话框""" + + def __init__( + self, + config_manager: ConfigManager, + llm_client: LLMClientBase, + ) -> None: + """ + 初始化用户配置对话框 + + Args: + config_manager: 配置管理器 + llm_client: LLM 客户端(用于获取模型列表) + + """ + super().__init__() + self.config_manager = config_manager + self.llm_client = llm_client + + # 模型数据 + self.all_models: list[ModelInfo] = [] + self.chat_models: list[ModelInfo] = [] + + # 当前已保存的模型(从配置读取) + self.saved_chat_model = config_manager.get_llm_chat_model() + # 已激活的模型(用空格键确认,等待保存) + self.activated_chat_model = self.saved_chat_model + + # 用户名(临时状态,未保存) + self.username = "" + if isinstance(self.llm_client, HermesChatClient): + self.username = self.llm_client.get_user_name() + + # 是否管理员 + self.is_admin = False + if isinstance(self.llm_client, HermesChatClient): + self.is_admin = self.llm_client.is_admin() + + # MCP 工具授权相关状态 + self.auto_execute_status = False # 默认为手动确认 + self.mcp_status_loaded = False # 是否已成功加载状态 + if isinstance(self.llm_client, HermesChatClient): + self.auto_execute_status = self.llm_client.get_auto_execute_status() + self.mcp_status_loaded = True + + # 当前标签页 + self.current_tab = "general" + + # 模型列表光标位置 + self.chat_cursor = 0 + + # 加载状态 + self.models_loaded = False + self.loading_error = False + + def compose(self) -> ComposeResult: + """构建对话框""" + with ( + Container(id="user-dialog-screen"), + Container(id="user-dialog"), + TabbedContent( + id="user-tabs", + initial="general-tab", + ), + ): + yield TabPane(_("常规设置"), id="general-tab") + yield TabPane(_("大模型设置"), id="llm-tab") + + async def on_mount(self) -> None: + """组件挂载时加载模型列表和渲染内容""" + await self._load_models() + self._render_all_tabs() + self._update_cursor_positions() + + async def _load_models(self) -> None: + """异步加载模型列表""" + try: + self.all_models = await self.llm_client.get_available_models() + self.chat_models = [model for model in self.all_models if LLMType.CHAT in model.llm_type] + self.models_loaded = True + self.loading_error = False + except (OSError, ValueError, RuntimeError): + self.models_loaded = False + self.loading_error = True + + def _render_all_tabs(self) -> None: + """渲染所有标签页内容""" + self._render_general_tab() + self._render_chat_llm_tab() + + def _render_general_tab(self) -> None: + """渲染常规设置标签页""" + tab_pane = self.query_one("#general-tab", TabPane) + + # 清空现有内容 + tab_pane.remove_children() + + # 创建表单容器 + form_container = Container(classes="general-settings-form") + + # 先挂载表单容器到 tab_pane + tab_pane.mount(form_container) + + # 用户名和用户类型显示(用户类型在右侧) + user_row = Horizontal(classes="settings-option") + form_container.mount(user_row) + + # 左侧:标签 + user_row.mount(Label(_("用户名:"), classes="settings-label")) + + # 右侧:值容器(包含用户名和徽章) + value_container = Horizontal(classes="settings-value-container") + user_row.mount(value_container) + + # 用户名 + value_container.mount( + Static( + self.username if self.username else _("未登录"), + id="username-display", + classes="settings-value", + ), + ) + + # 用户类型徽章 + if self.username: + user_type_text = _("管理员") if self.is_admin else _("普通用户") + user_type_class = "user-type-admin" if self.is_admin else "user-type-user" + value_container.mount( + Static( + user_type_text, + id="user-type-display", + classes=f"user-type-badge {user_type_class}", + ), + ) + + # MCP 工具授权设置(仅当支持时显示) + if self.mcp_status_loaded: + form_container.mount( + Horizontal( + Label(_("MCP 工具授权:"), classes="settings-label"), + Button( + _("自动执行") if self.auto_execute_status else _("手动确认"), + id="mcp-toggle-btn", + classes="settings-button", + ), + classes="settings-option", + ), + ) + + # 按钮区域 + button_container = Horizontal(id="general-buttons", classes="form-buttons") + tab_pane.mount(button_container) + + # 创建按钮并挂载到按钮容器 + button_container.mount(Button(_("保存"), id="save-user-settings-btn", variant="primary")) + button_container.mount(Button(_("取消"), id="cancel-general-btn", variant="default")) + + def _render_chat_llm_tab(self) -> None: + """渲染大模型设置标签页""" + self._render_tab_content("llm-tab", self.chat_models, self.activated_chat_model, self.chat_cursor) + + def _render_tab_content( + self, + tab_id: str, + models: list[ModelInfo], + activated_llm_id: str, + cursor_index: int, + ) -> None: + """ + 渲染标签页内容 + + Args: + tab_id: 标签页 ID + models: 模型列表 + activated_llm_id: 已激活的模型 llmId(用空格键确认) + cursor_index: 光标位置 + + """ + tab_pane = self.query_one(f"#{tab_id}", TabPane) + + # 清空现有内容 + tab_pane.remove_children() + + # 创建内容容器 + content_container = Container(classes="llm-tab-content") + tab_pane.mount(content_container) + + if not self.models_loaded: + if self.loading_error: + content_container.mount(Static(_("加载模型失败"), classes="llm-error")) + else: + content_container.mount(Static(_("加载中..."), classes="llm-loading")) + + elif not models: + content_container.mount(Static(_("暂无可用模型"), classes="llm-empty")) + + else: + # 渲染模型列表 + model_list_container = Container(classes="llm-model-list") + + # 将容器挂载到父容器 + content_container.mount(model_list_container) + # 向容器添加子组件 + for i, model in enumerate(models): + is_saved = model.llm_id == self.saved_chat_model # 已保存的 + is_activated = model.llm_id == activated_llm_id # 已激活的(用空格确认) + is_cursor = i == cursor_index # 光标选中的 + + # 构建样式类 + classes = "llm-model-item" + if is_saved: + classes += " llm-model-saved" + if is_activated: + classes += " llm-model-activated" + if is_cursor: + classes += " llm-model-cursor" + + model_item = Static(model.llm_id or "", classes=classes) + model_list_container.mount(model_item) + + # 显示当前光标所指模型的详细信息 + if 0 <= cursor_index < len(models): + current_model = models[cursor_index] + detail_container = Container(classes="llm-model-detail") + content_container.mount(detail_container) + self._populate_model_detail(detail_container, current_model) + + # 添加帮助文本 + tab_pane.mount( + Static( + _("↑↓: 选择模型 空格: 激活 回车: 保存 ESC: 取消"), + classes="llm-dialog-help", + ), + ) + + def _populate_model_detail(self, detail_container: Container, model: ModelInfo) -> None: + """ + 填充模型详情到已挂载的容器中 + + Args: + detail_container: 已挂载的详情容器 + model: 模型信息 + + """ + # 模型描述 + if model.llm_description: + detail_container.mount( + Horizontal( + Label(_("描述: "), classes="llm-detail-label"), + Static(model.llm_description, classes="llm-detail-value"), + classes="llm-detail-row", + ), + ) + + # 模型类型标签 + if model.llm_type: + type_str = ", ".join([t.value for t in model.llm_type]) + detail_container.mount( + Horizontal( + Label(_("类型: "), classes="llm-detail-label"), + Static(type_str, classes="llm-detail-value"), + classes="llm-detail-row", + ), + ) + + # 最大 token 数 + if model.max_tokens: + detail_container.mount( + Horizontal( + Label(_("最大 Token: "), classes="llm-detail-label"), + Static(str(model.max_tokens), classes="llm-detail-value"), + classes="llm-detail-row", + ), + ) + + def _update_cursor_positions(self) -> None: + """根据已保存的配置更新光标位置""" + # 基础模型光标 + if self.activated_chat_model: + for i, model in enumerate(self.chat_models): + if model.llm_id == self.activated_chat_model: + self.chat_cursor = i + break + + def on_key(self, event: Key) -> None: + """处理按键事件""" + # ESC 键在任何情况下都可以取消并关闭对话框 + if event.key == "escape": + self.action_cancel() + event.prevent_default() + event.stop() + return + + # 以下按键仅在大模型设置标签页且有可用模型时生效 + if self.current_tab == "llm" and self.chat_models: + if event.key == "up": + self.action_previous_model() + event.prevent_default() + event.stop() + elif event.key == "down": + self.action_next_model() + event.prevent_default() + event.stop() + elif event.key == "space": + self.action_activate_model() + event.prevent_default() + event.stop() + elif event.key == "enter": + self.action_save_llm_settings() + event.prevent_default() + event.stop() + + @on(TabbedContent.TabActivated) + def on_tab_changed(self, event: TabbedContent.TabActivated) -> None: + """标签页切换事件""" + pane_id = event.pane.id if event.pane else None + if pane_id == "general-tab": + self.current_tab = "general" + elif pane_id == "llm-tab": + self.current_tab = "llm" + + @on(Button.Pressed, "#save-user-settings-btn") + async def on_save_user_settings(self) -> None: + """保存用户设置(MCP 自动执行状态)""" + # 调用后端 API 保存 MCP 自动执行状态 + if isinstance(self.llm_client, HermesChatClient): + await self.llm_client.update_user_info( + auto_execute=self.auto_execute_status, + ) + + # 保存成功后关闭对话框 + self.app.pop_screen() + + @on(Button.Pressed, "#cancel-general-btn") + def on_cancel_general(self) -> None: + """取消常规设置的修改,关闭对话框""" + self.app.pop_screen() + + @on(Button.Pressed, "#mcp-toggle-btn") + def on_toggle_mcp(self) -> None: + """切换 MCP 工具授权模式(仅改变本地临时状态)""" + if not self.mcp_status_loaded: + return + + # 切换本地状态 + self.auto_execute_status = not self.auto_execute_status + + # 更新按钮显示 + mcp_btn = self.query_one("#mcp-toggle-btn", Button) + mcp_btn.label = _("自动执行") if self.auto_execute_status else _("手动确认") + + def action_previous_model(self) -> None: + """选择上一个模型""" + self.chat_cursor = max(0, self.chat_cursor - 1) + self._render_chat_llm_tab() + + def action_next_model(self) -> None: + """选择下一个模型""" + self.chat_cursor = min(len(self.chat_models) - 1, self.chat_cursor + 1) + self._render_chat_llm_tab() + + def action_activate_model(self) -> None: + """激活当前光标所在的模型(用空格键),等待保存""" + if 0 <= self.chat_cursor < len(self.chat_models): + self.activated_chat_model = self.chat_models[self.chat_cursor].llm_id or "" + self._render_chat_llm_tab() + + def action_save_llm_settings(self) -> None: + """保存大模型设置(用回车键)""" + # 保存已激活的模型到配置 + self.config_manager.set_llm_chat_model(self.activated_chat_model) + self.saved_chat_model = self.activated_chat_model + # 关闭对话框 + self.app.pop_screen() + + def action_cancel(self) -> None: + """取消并关闭对话框""" + self.app.pop_screen() diff --git a/src/app/mcp_widgets.py b/src/app/mcp_widgets.py index 7fb5d27f126fc0eb9063e97d7dfcd0aee782f9b0..37b5562eeabe534426b36d8456282b73385fa128 100644 --- a/src/app/mcp_widgets.py +++ b/src/app/mcp_widgets.py @@ -44,7 +44,6 @@ class MCPConfirmWidget(Container): step_name = self.event.get_step_name() content = self.event.get_content() risk = content.get("risk", "unknown") - reason = content.get("reason", _("需要用户确认是否执行此工具")) # 风险级别文本和图标 risk_info = { @@ -62,20 +61,6 @@ class MCPConfirmWidget(Container): classes=f"confirm-info risk-{risk}", markup=False, ) - # 显示简化的说明文字,确保按钮可见 - if len(reason) > MAX_DISPLAY_LENGTH: - # 如果说明太长,显示省略号 - yield Static( - f"💭 {reason[:TRUNCATE_LENGTH]}...", - classes="confirm-reason", - markup=False, - ) - else: - yield Static( - f"💭 {reason}", - classes="confirm-reason", - markup=False, - ) # 确保按钮始终显示 with Horizontal(classes="confirm-buttons"): yield Button(_("✓ 确认"), variant="success", id="mcp-confirm-yes") @@ -84,12 +69,12 @@ class MCPConfirmWidget(Container): @on(Button.Pressed, "#mcp-confirm-yes") def confirm_execution(self) -> None: """确认执行""" - self.post_message(MCPConfirmResult(confirmed=True, task_id=self.event.get_task_id())) + self.post_message(MCPConfirmResult(confirmed=True, conversation_id=self.event.get_conversation_id())) @on(Button.Pressed, "#mcp-confirm-no") def cancel_execution(self) -> None: """取消执行""" - self.post_message(MCPConfirmResult(confirmed=False, task_id=self.event.get_task_id())) + self.post_message(MCPConfirmResult(confirmed=False, conversation_id=self.event.get_conversation_id())) def on_key(self, event) -> None: # noqa: ANN001 """处理键盘事件""" @@ -161,18 +146,12 @@ class MCPParameterWidget(Container): """构建参数输入界面""" step_name = self.event.get_step_name() content = self.event.get_content() - message = content.get("message", _("需要补充参数")) params = content.get("params", {}) with Vertical(classes="mcp-content"): # 紧凑的参数输入标题 yield Static(_("📝 参数输入"), classes="param-header", markup=False) yield Static(f"🔧 {step_name}", classes="param-tool", markup=False) - # 显示说明文字,超长时显示省略号 - if len(message) > MAX_DISPLAY_LENGTH: - yield Static(f"💭 {message[:TRUNCATE_LENGTH]}...", classes="param-message", markup=False) - else: - yield Static(f"💭 {message}", classes="param-message", markup=False) # 垂直布局的参数输入,更节省空间 for param_name, param_value in params.items(): @@ -185,16 +164,6 @@ class MCPParameterWidget(Container): self.param_inputs[param_name] = param_input yield param_input - # 简化的补充说明输入 - if params: # 只有在有其他参数时才显示补充说明 - description_input = Input( - placeholder=_("补充说明(可选)"), - id="param_description", - classes="param-input-compact", - ) - self.param_inputs["description"] = description_input - yield description_input - # 紧凑的按钮行 with Horizontal(classes="param-buttons"): yield Button(_("✓ 提交"), variant="success", id="mcp-param-submit") @@ -204,45 +173,36 @@ class MCPParameterWidget(Container): def submit_parameters(self) -> None: """提交参数""" # 收集用户输入的参数 - content_params = {} - description = "" + params = {} for param_name, input_widget in self.param_inputs.items(): value = input_widget.value.strip() - if param_name == "description": - description = value - elif value: - content_params[param_name] = value - - # 构建参数结构 - params = { - "content": content_params, - "description": description, - } + if value: + params[param_name] = value - self.post_message(MCPParameterResult(params=params, task_id=self.event.get_task_id())) + self.post_message(MCPParameterResult(params=params, conversation_id=self.event.get_conversation_id())) @on(Button.Pressed, "#mcp-param-cancel") def cancel_parameters(self) -> None: """取消参数输入""" - self.post_message(MCPParameterResult(params=None, task_id=self.event.get_task_id())) + self.post_message(MCPParameterResult(params=None, conversation_id=self.event.get_conversation_id())) class MCPConfirmResult(Message): """MCP 确认结果消息""" - def __init__(self, *, confirmed: bool, task_id: str) -> None: + def __init__(self, *, confirmed: bool, conversation_id: str) -> None: """初始化确认结果""" super().__init__() self.confirmed = confirmed - self.task_id = task_id + self.conversation_id = conversation_id class MCPParameterResult(Message): """MCP 参数结果消息""" - def __init__(self, *, params: dict | None, task_id: str) -> None: + def __init__(self, *, params: dict | None, conversation_id: str) -> None: """初始化参数结果""" super().__init__() self.params = params - self.task_id = task_id + self.conversation_id = conversation_id diff --git a/src/app/settings.py b/src/app/settings.py index 4d2ec66313c7a26990d3c58b77323f18dadffb00..0510c6ace819450bc8fd2d399f8cb20d5dce2597 100644 --- a/src/app/settings.py +++ b/src/app/settings.py @@ -11,9 +11,7 @@ from textual.css.query import NoMatches from textual.screen import ModalScreen from textual.widgets import Button, Input, Label, Static -from app.dialogs import ExitDialog -from backend.hermes import HermesChatClient -from backend.openai import OpenAIClient +from app.dialogs import ExitDialog, UserConfigDialog from config import Backend, ConfigManager from i18n.manager import _ from log import get_logger @@ -23,7 +21,7 @@ if TYPE_CHECKING: from textual.app import ComposeResult from textual.events import Key - from backend.base import LLMClientBase + from backend import LLMClientBase class SettingsScreen(ModalScreen): @@ -41,10 +39,6 @@ class SettingsScreen(ModalScreen): # 添加保存任务的集合 self.background_tasks: set[asyncio.Task] = set() - # MCP 工具授权相关状态 - self.auto_execute_status = False # 默认为手动确认 - self.mcp_status_loaded = False # 是否已成功加载状态 - # 验证相关状态 self.is_validated = False self.validation_message = "" @@ -61,71 +55,8 @@ class SettingsScreen(ModalScreen): yield Container( Container( Label(_("设置"), id="settings-title"), - # 后端选择 - Horizontal( - Label(_("后端:"), classes="settings-label"), - Button( - f"{self.backend.get_display_name()}", - id="backend-btn", - classes="settings-button", - ), - classes="settings-option", - ), - # Base URL 输入 - Horizontal( - Label(_("Base URL:"), classes="settings-label"), - Input( - value=self.config_manager.get_base_url() - if self.backend == Backend.OPENAI - else self.config_manager.get_eulerintelli_url(), - classes="settings-input", - id="base-url", - ), - classes="settings-option", - ), - # API Key 输入 - Horizontal( - Label(_("API Key:"), classes="settings-label"), - Input( - value=self.config_manager.get_api_key() - if self.backend == Backend.OPENAI - else self.config_manager.get_eulerintelli_key(), - classes="settings-input", - id="api-key", - placeholder=_("API 访问密钥,可选"), - ), - classes="settings-option", - ), - # 模型选择(仅 OpenAI 后端显示) - *( - [ - Horizontal( - Label(_("模型:"), classes="settings-label"), - Input( - value=self.selected_model, - classes="settings-input", - id="model-input", - placeholder=_("模型名称,可选"), - ), - id="model-section", - classes="settings-option", - ), - ] - if self.backend == Backend.OPENAI - else [ - Horizontal( - Label(_("MCP 工具授权:"), classes="settings-label"), - Button( - _("自动执行") if self.auto_execute_status else _("手动确认"), - id="mcp-btn", - classes="settings-button", - disabled=not self.mcp_status_loaded, - ), - id="mcp-section", - classes="settings-option", - ), - ] - ), + *self._create_common_widgets(), + *self._create_backend_widgets(), # 添加一个空白区域,确保操作按钮始终可见 Static("", id="spacer"), # 操作按钮 @@ -142,48 +73,15 @@ class SettingsScreen(ModalScreen): def on_mount(self) -> None: """组件挂载时加载可用模型""" - if self.backend == Backend.EULERINTELLI: - task = asyncio.create_task(self.load_mcp_status()) - # 保存任务引用 - self.background_tasks.add(task) - task.add_done_callback(self.background_tasks.discard) - # 启动配置验证 self._schedule_validation() # 确保操作按钮始终可见 self._ensure_buttons_visible() - async def load_mcp_status(self) -> None: - """异步加载 MCP 工具授权状态""" - try: - # 只有 EULERINTELLI 后端才支持 MCP 状态 - if self.backend != Backend.EULERINTELLI: - return - - # 从 Hermes 客户端获取自动执行状态 - if hasattr(self.llm_client, "get_auto_execute_status"): - self.auto_execute_status = await self.llm_client.get_auto_execute_status() # type: ignore[attr-defined] - self.mcp_status_loaded = True - else: - self.auto_execute_status = False - self.mcp_status_loaded = False - - # 更新 MCP 按钮文本和状态 - mcp_btn = self.query_one("#mcp-btn", Button) - mcp_btn.label = _("自动执行") if self.auto_execute_status else _("手动确认") - mcp_btn.disabled = not self.mcp_status_loaded - - except (OSError, ValueError, RuntimeError): - self.auto_execute_status = False - self.mcp_status_loaded = False - mcp_btn = self.query_one("#mcp-btn", Button) - mcp_btn.label = _("手动确认") - mcp_btn.disabled = True - @on(Input.Changed, "#base-url, #api-key, #model-input") def on_config_changed(self) -> None: - """当 Base URL、API Key 或模型改变时更新客户端并验证配置""" + """当 Base URL、API Key 或模型改变时验证配置""" if self.backend == Backend.OPENAI: # 获取当前模型输入值 try: @@ -193,104 +91,26 @@ class SettingsScreen(ModalScreen): # 如果模型输入框不存在,跳过 pass - self._update_llm_client() - else: # EULERINTELLI - self._update_llm_client() - # 重新加载 MCP 状态 - task = asyncio.create_task(self.load_mcp_status()) - self.background_tasks.add(task) - task.add_done_callback(self.background_tasks.discard) - # 重新验证配置 self._schedule_validation() @on(Button.Pressed, "#backend-btn") def toggle_backend(self) -> None: """切换后端""" - current = self.backend - new = Backend.EULERINTELLI if current == Backend.OPENAI else Backend.OPENAI - self.backend = new + # 切换后端类型 + self.backend = ( + Backend.EULERINTELLI if self.backend == Backend.OPENAI else Backend.OPENAI + ) - # 更新按钮文本 + # 更新后端按钮文本 backend_btn = self.query_one("#backend-btn", Button) - backend_btn.label = new.get_display_name() - - # 更新 URL 和 API Key - base_url = self.query_one("#base-url", Input) - api_key = self.query_one("#api-key", Input) - - if new == Backend.OPENAI: - base_url.value = self.config_manager.get_base_url() - api_key.value = self.config_manager.get_api_key() - - # 创建新的 OpenAI 客户端 - self._update_llm_client() - - # 移除 MCP 工具授权部分 - mcp_section = self.query("#mcp-section") - if mcp_section: - mcp_section[0].remove() - - # 添加模型选择部分 - if not self.query("#model-section"): - container = self.query_one("#settings-container") - spacer = self.query_one("#spacer") - model_section = Horizontal( - Label(_("模型:"), classes="settings-label"), - Input( - value=self.selected_model, - classes="settings-input", - id="model-input", - placeholder=_("模型名称,可选"), - ), - id="model-section", - classes="settings-option", - ) + backend_btn.label = self.backend.get_display_name() - # 在 spacer 前面添加 model_section - if spacer: - container.mount(model_section, before=spacer) - else: - container.mount(model_section) - else: - base_url.value = self.config_manager.get_eulerintelli_url() - api_key.value = self.config_manager.get_eulerintelli_key() + # 更新配置输入框的值 + self._load_config_inputs() - # 创建新的 Hermes 客户端 - self._update_llm_client() - - # 移除模型选择部分 - model_section = self.query("#model-section") - if model_section: - model_section[0].remove() - - # 添加 MCP 工具授权部分 - if not self.query("#mcp-section"): - container = self.query_one("#settings-container") - spacer = self.query_one("#spacer") - mcp_section = Horizontal( - Label(_("MCP 工具授权:"), classes="settings-label"), - Button( - _("自动执行") if self.auto_execute_status else _("手动确认"), - id="mcp-btn", - classes="settings-button", - disabled=not self.mcp_status_loaded, - ), - id="mcp-section", - classes="settings-option", - ) - - # 在spacer前面添加mcp_section - if spacer: - container.mount(mcp_section, before=spacer) - else: - container.mount(mcp_section) - - # 重新加载 MCP 状态 - task = asyncio.create_task(self.load_mcp_status()) - # 保存任务引用 - self.background_tasks.add(task) - task.add_done_callback(self.background_tasks.discard) + # 替换后端特定的 UI 组件 + self._replace_backend_widgets() # 确保按钮可见 self._ensure_buttons_visible() @@ -298,77 +118,47 @@ class SettingsScreen(ModalScreen): # 切换后端后重新验证配置 self._schedule_validation() - @on(Button.Pressed, "#mcp-btn") - def toggle_mcp_authorization(self) -> None: - """切换 MCP 工具授权模式""" - if not self.mcp_status_loaded: - return - - # 创建切换任务 - task = asyncio.create_task(self._toggle_mcp_authorization_async()) - self.background_tasks.add(task) - task.add_done_callback(self.background_tasks.discard) + @on(Button.Pressed, "#user-config-btn") + def open_user_config(self) -> None: + """打开用户配置对话框""" + dialog = UserConfigDialog(self.config_manager, self.llm_client) + self.app.push_screen(dialog) @on(Button.Pressed, "#save-btn") def save_settings(self) -> None: """保存设置""" # 取消所有后台任务 - for task in self.background_tasks: - if not task.done(): - task.cancel() - self.background_tasks.clear() + self._cancel_background_tasks() # 检查验证状态 if not self.is_validated: return - self.config_manager.set_backend(self.backend) - - base_url = self.query_one("#base-url", Input).value - api_key = self.query_one("#api-key", Input).value + # 获取旧配置 + old_backend, old_base, old_key = self._get_old_config() - if self.backend == Backend.OPENAI: - # 获取模型输入值 - try: - model_input = self.query_one("#model-input", Input) - self.selected_model = model_input.value.strip() - except NoMatches: - # 如果模型输入框不存在,保持当前选择的模型 - pass + # 保存新配置 + base_url, api_key = self._save_new_config() - self.config_manager.set_base_url(base_url) - self.config_manager.set_api_key(api_key) - self.config_manager.set_model(self.selected_model) - else: # eulerintelli - self.config_manager.set_eulerintelli_url(base_url) - self.config_manager.set_eulerintelli_key(api_key) + # 判断是否需要刷新客户端 + need_refresh = self._should_refresh_client(old_backend, old_base, old_key, base_url, api_key) - # 通知主应用刷新客户端 - refresh_method = getattr(self.app, "refresh_llm_client", None) - if refresh_method: - refresh_method() + # 刷新客户端 + if need_refresh: + self._refresh_app_client() self.app.pop_screen() @on(Button.Pressed, "#cancel-btn") def cancel_settings(self) -> None: """取消设置""" - # 取消所有后台任务 - for task in self.background_tasks: - if not task.done(): - task.cancel() - self.background_tasks.clear() - + self._cancel_background_tasks() self.app.pop_screen() def on_key(self, event: Key) -> None: """处理键盘事件""" if event.key == "escape": - # 取消所有后台任务 - for task in self.background_tasks: - if not task.done(): - task.cancel() - self.background_tasks.clear() + self._cancel_background_tasks() # ESC 键退出设置页面,等效于取消 self.app.pop_screen() if event.key == "ctrl+q": @@ -376,6 +166,156 @@ class SettingsScreen(ModalScreen): event.prevent_default() event.stop() + def _create_common_widgets(self) -> list: + """创建通用的 UI 组件(所有后端共享)""" + return [ + # 后端选择 + Horizontal( + Label(_("后端:"), classes="settings-label"), + Button( + f"{self.backend.get_display_name()}", + id="backend-btn", + classes="settings-button", + ), + classes="settings-option", + ), + # Base URL 输入 + Horizontal( + Label(_("Base URL:"), classes="settings-label"), + Input( + value=self.config_manager.get_base_url() + if self.backend == Backend.OPENAI + else self.config_manager.get_eulerintelli_url(), + classes="settings-input", + id="base-url", + ), + classes="settings-option", + ), + # API Key 输入 + Horizontal( + Label(_("API Key:"), classes="settings-label"), + Input( + value=self.config_manager.get_api_key() + if self.backend == Backend.OPENAI + else self.config_manager.get_eulerintelli_key(), + classes="settings-input", + id="api-key", + placeholder=_("API 访问密钥,可选"), + ), + classes="settings-option"), + ] + + def _create_backend_widgets(self) -> list: + """创建后端特定的 UI 组件""" + if self.backend == Backend.OPENAI: + return [ + Horizontal( + Label(_("模型:"), classes="settings-label"), + Input( + value=self.selected_model, + classes="settings-input", + id="model-input", + placeholder=_("模型名称,可选"), + ), + id="model-section", + classes="settings-option", + ), + ] + + # EULERINTELLI 后端 + return [ + Horizontal( + Label(_("用户设置:"), classes="settings-label"), + Button( + _("更改用户设置"), + id="user-config-btn", + classes="settings-button", + ), + id="user-config-section", + classes="settings-option", + ), + ] + + def _cancel_background_tasks(self) -> None: + """取消所有后台任务""" + for task in self.background_tasks: + if not task.done(): + task.cancel() + self.background_tasks.clear() + + def _get_old_config(self) -> tuple[Backend, str, str]: + """获取旧配置""" + old_backend = self.config_manager.get_backend() + + if old_backend == Backend.OPENAI: + old_base = self.config_manager.get_base_url() + old_key = self.config_manager.get_api_key() + else: + old_base = self.config_manager.get_eulerintelli_url() + old_key = self.config_manager.get_eulerintelli_key() + + return old_backend, old_base, old_key + + def _save_new_config(self) -> tuple[str, str]: + """保存新配置并返回 base_url 和 api_key""" + self.config_manager.set_backend(self.backend) + + base_url = self.query_one("#base-url", Input).value + api_key = self.query_one("#api-key", Input).value + + if self.backend == Backend.OPENAI: + self._save_openai_config(base_url, api_key) + else: # eulerintelli + self.config_manager.set_eulerintelli_url(base_url) + self.config_manager.set_eulerintelli_key(api_key) + + return base_url, api_key + + def _save_openai_config(self, base_url: str, api_key: str) -> None: + """保存 OpenAI 配置""" + # 获取模型输入值 + try: + model_input = self.query_one("#model-input", Input) + self.selected_model = model_input.value.strip() + except NoMatches: + # 如果模型输入框不存在,保持当前选择的模型 + pass + + self.config_manager.set_base_url(base_url) + self.config_manager.set_api_key(api_key) + self.config_manager.set_model(self.selected_model) + + def _should_refresh_client( + self, + old_backend: Backend, + old_base: str, + old_key: str, + new_base: str, + new_key: str, + ) -> bool: + """判断是否需要刷新客户端""" + # 后端类型变化 + if old_backend != self.backend: + return True + + # 同一后端,URL 或 Key 变化 + if old_base != new_base or old_key != new_key: + return True + + # OpenAI 后端,检查模型是否变化 + if self.backend == Backend.OPENAI: + old_model = self.config_manager.get_model() + if old_model != self.selected_model: + return True + + return False + + def _refresh_app_client(self) -> None: + """刷新应用客户端""" + refresh_method = getattr(self.app, "refresh_llm_client", None) + if refresh_method: + refresh_method() + def _schedule_validation(self) -> None: """调度验证任务,带防抖机制""" # 增加验证代数,使旧的验证任务失效 @@ -426,6 +366,36 @@ class SettingsScreen(ModalScreen): self.background_tasks.add(task) task.add_done_callback(self.background_tasks.discard) + def _load_config_inputs(self) -> None: + """根据当前后端载入配置输入框的值""" + base_url = self.query_one("#base-url", Input) + api_key = self.query_one("#api-key", Input) + + if self.backend == Backend.OPENAI: + base_url.value = self.config_manager.get_base_url() + api_key.value = self.config_manager.get_api_key() + else: # EULERINTELLI + base_url.value = self.config_manager.get_eulerintelli_url() + api_key.value = self.config_manager.get_eulerintelli_key() + + def _replace_backend_widgets(self) -> None: + """替换后端特定的 UI 组件""" + container = self.query_one("#settings-container") + spacer = self.query_one("#spacer") + + # 移除所有后端特定的组件 + for section_id in ["#model-section", "#user-config-section"]: + sections = self.query(section_id) + for section in sections: + section.remove() + + # 添加新的后端特定组件 + for widget in self._create_backend_widgets(): + if spacer: + container.mount(widget, before=spacer) + else: + container.mount(widget) + async def _validate_configuration(self) -> None: """验证当前配置""" try: @@ -485,72 +455,3 @@ class SettingsScreen(ModalScreen): save_btn.disabled = False else: save_btn.disabled = True - - def _update_llm_client(self) -> None: - """根据当前UI中的配置更新LLM客户端""" - base_url_input = self.query_one("#base-url", Input) - api_key_input = self.query_one("#api-key", Input) - - # 保存当前智能体状态(如果是Hermes客户端) - current_agent_id = "" - if isinstance(self.llm_client, HermesChatClient): - current_agent_id = getattr(self.llm_client, "current_agent_id", "") - - if self.backend == Backend.OPENAI: - # 获取模型输入值,如果输入框不存在则使用当前选择的模型 - try: - model_input = self.query_one("#model-input", Input) - model = model_input.value.strip() - except NoMatches: - model = self.selected_model - - self.llm_client = OpenAIClient( - base_url=base_url_input.value, - model=model, - api_key=api_key_input.value, - ) - else: # EULERINTELLI - self.llm_client = HermesChatClient( - base_url=base_url_input.value, - auth_token=api_key_input.value, - ) - # 恢复智能体状态 - if current_agent_id: - self.llm_client.set_current_agent(current_agent_id) - - async def _toggle_mcp_authorization_async(self) -> None: - """异步切换 MCP 工具授权模式""" - try: - # 检查客户端是否支持 MCP 操作 - if ( - not hasattr(self.llm_client, "enable_auto_execute") - or not hasattr(self.llm_client, "disable_auto_execute") - ): - return - - # 先禁用按钮防止重复点击 - mcp_btn = self.query_one("#mcp-btn", Button) - mcp_btn.disabled = True - mcp_btn.label = _("切换中...") - - # 根据当前状态调用相应的方法 - if self.auto_execute_status: - # 当前是自动执行,切换为手动确认 - await self.llm_client.disable_auto_execute() # type: ignore[attr-defined] - else: - # 当前是手动确认,切换为自动执行 - await self.llm_client.enable_auto_execute() # type: ignore[attr-defined] - - # 重新获取状态以确保同步 - if hasattr(self.llm_client, "get_auto_execute_status"): - self.auto_execute_status = await self.llm_client.get_auto_execute_status() # type: ignore[attr-defined] - - # 更新按钮状态 - mcp_btn.label = _("自动执行") if self.auto_execute_status else _("手动确认") - mcp_btn.disabled = False - - except (OSError, ValueError, RuntimeError): - # 发生错误时恢复按钮状态 - mcp_btn = self.query_one("#mcp-btn", Button) - mcp_btn.label = _("自动执行") if self.auto_execute_status else _("手动确认") - mcp_btn.disabled = False diff --git a/src/app/tui.py b/src/app/tui.py index 292662c9ff5501f2d55cbc7d03c5196c4fd8efb5..cdff8c505bdafa54345747910cea9d0555fdeca1 100644 --- a/src/app/tui.py +++ b/src/app/tui.py @@ -19,8 +19,7 @@ from app.mcp_widgets import MCPConfirmResult, MCPConfirmWidget, MCPParameterResu from app.settings import SettingsScreen from app.tui_header import OIHeader from app.tui_mcp_handler import TUIMCPEventHandler -from backend.factory import BackendFactory -from backend.hermes import HermesChatClient +from backend import BackendFactory, HermesChatClient, OpenAIClient from backend.hermes.mcp_helpers import ( MCPTags, extract_mcp_tag, @@ -33,14 +32,14 @@ from config.model import Backend from i18n.manager import _ from log.manager import get_logger, log_exception from tool.command_processor import process_command -from tool.validators import APIValidator, validate_oi_connection +from tool.validators import APIValidator if TYPE_CHECKING: from textual.events import Key as KeyEvent from textual.events import Mount from textual.visual import VisualType - from backend.base import LLMClientBase + from backend import LLMClientBase class ContentChunkParams(NamedTuple): @@ -263,7 +262,7 @@ class IntelligentTerminal(App): self.current_agent: tuple[str, str] = self._get_initial_agent() # MCP 状态 self._mcp_mode: str = "normal" # "normal", "confirm", "parameter" - self._current_mcp_task_id: str = "" + self._current_mcp_conversation_id: str = "" # 创建日志实例 self.logger = get_logger(__name__) # 进度消息跟踪 @@ -303,6 +302,8 @@ class IntelligentTerminal(App): output_container.remove_children() # 清理进度消息跟踪 self._current_progress_lines.clear() + # 清理 MCP 会话 ID + self._current_mcp_conversation_id = "" def action_choose_agent(self) -> None: """选择智能体的动作""" @@ -379,7 +380,7 @@ class IntelligentTerminal(App): """初始化完成时设置焦点和绑定""" # 确保初始状态是正常模式 self._mcp_mode = "normal" - self._current_mcp_task_id = "" + self._current_mcp_conversation_id = "" # 清理任何可能的重复组件 try: @@ -422,23 +423,34 @@ class IntelligentTerminal(App): return self._llm_client def refresh_llm_client(self) -> None: - """刷新 LLM 客户端实例,用于配置更改后重新创建客户端""" - # 保存当前智能体状态 + """重新创建 LLM 客户端实例,用于后端/URL/API Key/模型 变更后刷新连接""" + # 保存当前智能体状态以便恢复 current_agent_id = self.current_agent[0] if self.current_agent else "" + # 保存 OpenAI 客户端的对话历史 + conversation_history = None + if isinstance(self._llm_client, OpenAIClient): + conversation_history = self._llm_client.conversation_history + self._llm_client = BackendFactory.create_client(self.config_manager) + # 恢复 OpenAI 客户端的对话历史 + if conversation_history is not None and isinstance(self._llm_client, OpenAIClient): + self._llm_client.conversation_history = conversation_history + # 恢复智能体状态到新的客户端 if current_agent_id and isinstance(self._llm_client, HermesChatClient): self._llm_client.set_current_agent(current_agent_id) - # 为 Hermes 客户端设置 MCP 事件处理器 + # 为 Hermes 客户端设置 MCP 事件处理器并加载用户信息 if isinstance(self._llm_client, HermesChatClient): mcp_handler = TUIMCPEventHandler(self, self._llm_client) self._llm_client.set_mcp_handler(mcp_handler) - # 后端切换时重新初始化智能体状态 - self._reinitialize_agent_state() + # 创建异步任务加载用户信息并同步 personalToken + task = asyncio.create_task(self._ensure_hermes_user_info()) + self.background_tasks.add(task) + task.add_done_callback(self.background_tasks.discard) def exit(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 """退出应用前取消所有后台任务""" @@ -460,6 +472,9 @@ class IntelligentTerminal(App): @on(Input.Submitted, "#command-input") def handle_input(self, event: Input.Submitted) -> None: """处理命令输入""" + if not self._is_in_main_interface(): + return + user_input = event.value.strip() if not user_input or self.processing: return @@ -487,40 +502,41 @@ class IntelligentTerminal(App): def handle_switch_to_mcp_confirm(self, message: SwitchToMCPConfirm) -> None: """处理切换到 MCP 确认界面的消息""" self._mcp_mode = "confirm" - self._current_mcp_task_id = message.event.get_task_id() + self._current_mcp_conversation_id = message.event.get_conversation_id() self._replace_input_with_mcp_widget(MCPConfirmWidget(message.event, widget_id="mcp-confirm")) @on(SwitchToMCPParameter) def handle_switch_to_mcp_parameter(self, message: SwitchToMCPParameter) -> None: """处理切换到 MCP 参数输入界面的消息""" self._mcp_mode = "parameter" - self._current_mcp_task_id = message.event.get_task_id() + self._current_mcp_conversation_id = message.event.get_conversation_id() self._replace_input_with_mcp_widget(MCPParameterWidget(message.event, widget_id="mcp-parameter")) @on(MCPConfirmResult) def handle_mcp_confirm_result(self, message: MCPConfirmResult) -> None: """处理 MCP 确认结果""" - # 检查是否是当前任务且未在处理中 - if message.task_id == self._current_mcp_task_id and not self.processing: + # 检查是否是当前会话且未在处理中 + if message.conversation_id == self._current_mcp_conversation_id and not self.processing: self.processing = True # 设置处理标志,防止重复处理 # 立即恢复正常输入界面 self._restore_normal_input() # 发送 MCP 响应并处理结果 - task = asyncio.create_task(self._send_mcp_response(message.task_id, params=message.confirmed)) + params = {"confirm": message.confirmed} + task = asyncio.create_task(self._send_mcp_response(message.conversation_id, params=params)) self.background_tasks.add(task) task.add_done_callback(self._task_done_callback) @on(MCPParameterResult) def handle_mcp_parameter_result(self, message: MCPParameterResult) -> None: """处理 MCP 参数结果""" - # 检查是否是当前任务且未在处理中 - if message.task_id == self._current_mcp_task_id and not self.processing: + # 检查是否是当前会话且未在处理中 + if message.conversation_id == self._current_mcp_conversation_id and not self.processing: self.processing = True # 设置处理标志,防止重复处理 # 立即恢复正常输入界面 self._restore_normal_input() # 发送 MCP 响应并处理结果 - params = message.params if message.params is not None else False - task = asyncio.create_task(self._send_mcp_response(message.task_id, params=params)) + params = message.params if message.params is not None else {} + task = asyncio.create_task(self._send_mcp_response(message.conversation_id, params=params)) self.background_tasks.add(task) task.add_done_callback(self._task_done_callback) @@ -634,9 +650,9 @@ class IntelligentTerminal(App): "is_first_content": True, "received_any_content": False, "start_time": start_time, - "timeout_seconds": 1800.0, # 30分钟超时,与HTTP层面保持一致 + "timeout_seconds": None, # 无总体超时限制,支持超长时间任务 "last_content_time": start_time, - "no_content_timeout": 300.0, # 5分钟无内容超时 + "no_content_timeout": 1800.0, # 30分钟无内容超时 } async def _process_stream( @@ -679,8 +695,8 @@ class IntelligentTerminal(App): output_container: Container, ) -> bool: """检查各种超时条件,返回是否应该中断处理""" - # 检查总体超时 - if current_time - stream_state["start_time"] > stream_state["timeout_seconds"]: + timeout_seconds = stream_state["timeout_seconds"] + if timeout_seconds is not None and current_time - stream_state["start_time"] > timeout_seconds: output_container.mount(OutputLine(_("Request timeout, processing stopped"), command=False)) return True @@ -1015,6 +1031,38 @@ class IntelligentTerminal(App): # 等待一个小的延迟,确保UI有时间更新 await asyncio.sleep(0.01) + async def _ensure_hermes_user_info(self) -> None: + """确保 Hermes 用户信息已加载并同步 personalToken 到配置""" + if not isinstance(self._llm_client, HermesChatClient): + return + + try: + # 加载用户信息 + success = await self._llm_client.ensure_user_info_loaded() + if not success: + self.logger.warning("加载用户信息失败") + return + + # 获取 personalToken + personal_token = self._llm_client.get_personal_token() + if not personal_token: + self.logger.info("服务器未返回 personalToken,跳过同步") + return + + # 获取当前配置中的 personalToken + current_token = self.config_manager.get_eulerintelli_key() + + # 如果 personalToken 不一致,更新配置 + if personal_token != current_token: + self.logger.info("检测到 personalToken 变更,正在同步到配置...") + self.config_manager.set_eulerintelli_key(personal_token) + self.logger.info("PersonalToken 已同步到配置文件") + else: + self.logger.info("PersonalToken 与配置一致,无需同步") + + except (OSError, ValueError, RuntimeError) as e: + log_exception(self.logger, "加载用户信息或同步 personalToken 时发生错误", e) + async def _cleanup_llm_client(self) -> None: """异步清理 LLM 客户端""" if self._llm_client is not None: @@ -1041,7 +1089,7 @@ class IntelligentTerminal(App): llm_client = self.get_llm_client() # 构建智能体列表 - 默认第一项为"智能问答"(无智能体) - agent_list = [("", "智能问答")] + agent_list = [("", _("智能问答"))] # 尝试获取可用智能体 if hasattr(llm_client, "get_available_agents"): @@ -1067,7 +1115,7 @@ class IntelligentTerminal(App): except (OSError, ValueError, RuntimeError) as e: log_exception(self.logger, "显示智能体选择对话框失败", e) # 即使出错也显示默认选项 - agent_list = [("", "智能问答")] + agent_list = [("", _("智能问答"))] try: llm_client = self.get_llm_client() await self._display_agent_dialog(agent_list, llm_client) @@ -1126,7 +1174,7 @@ class IntelligentTerminal(App): # 重置 MCP 状态 self._mcp_mode = "normal" - self._current_mcp_task_id = "" + self._current_mcp_conversation_id = "" # 切换回正常模式样式 input_container.remove_class("mcp-mode") @@ -1146,9 +1194,9 @@ class IntelligentTerminal(App): self.logger.exception("恢复正常输入组件失败") # 如果恢复失败,至少要重置状态 self._mcp_mode = "normal" - self._current_mcp_task_id = "" + self._current_mcp_conversation_id = "" - async def _send_mcp_response(self, task_id: str, *, params: bool | dict[str, Any]) -> None: + async def _send_mcp_response(self, conversation_id: str, *, params: dict[str, Any]) -> None: """发送 MCP 响应并处理结果""" output_container: Container | None = None @@ -1160,7 +1208,7 @@ class IntelligentTerminal(App): llm_client = self.get_llm_client() if hasattr(llm_client, "send_mcp_response"): success = await self._handle_mcp_response_stream( - task_id, + conversation_id, params=params, output_container=output_container, llm_client=llm_client, @@ -1190,9 +1238,9 @@ class IntelligentTerminal(App): async def _handle_mcp_response_stream( self, - task_id: str, + conversation_id: str, *, - params: bool | dict[str, Any], + params: dict[str, Any], output_container: Container, llm_client: LLMClientBase, ) -> bool: @@ -1204,50 +1252,38 @@ class IntelligentTerminal(App): # 使用统一的流状态管理,与 _handle_command_stream 保持一致 stream_state = self._init_stream_state() - timeout_seconds = 1800.0 # 30分钟超时,与HTTP层面保持一致 try: - # 使用 asyncio.wait_for 包装整个流处理过程 - async def _process_stream() -> bool: - async for content in llm_client.send_mcp_response(task_id, params=params): - if not content.strip(): - continue - - stream_state["received_any_content"] = True - current_time = asyncio.get_event_loop().time() - - # 更新最后收到内容的时间 - if content.strip(): - stream_state["last_content_time"] = current_time - - # 检查超时 - if self._check_timeouts(current_time, stream_state, output_container): - break - - # 判断是否为 LLM 输出内容 - tool_name, _cleaned_content = extract_mcp_tag(content) - is_llm_output = tool_name is None - - # 处理内容 - await self._process_stream_content( - content, - stream_state, - output_container, - is_llm_output=is_llm_output, - ) - - # 滚动到底部 - await self._scroll_to_end() - - return stream_state["received_any_content"] + async for content in llm_client.send_mcp_response(conversation_id, params=params): + if not content.strip(): + continue + + stream_state["received_any_content"] = True + current_time = asyncio.get_event_loop().time() + + # 更新最后收到内容的时间 + if content.strip(): + stream_state["last_content_time"] = current_time + + # 检查超时 + if self._check_timeouts(current_time, stream_state, output_container): + break + + # 判断是否为 LLM 输出内容 + tool_name, _cleaned_content = extract_mcp_tag(content) + is_llm_output = tool_name is None + + # 处理内容 + await self._process_stream_content( + content, + stream_state, + output_container, + is_llm_output=is_llm_output, + ) - # 执行流处理,添加超时 - return await asyncio.wait_for(_process_stream(), timeout=timeout_seconds) + # 滚动到底部 + await self._scroll_to_end() - except TimeoutError: - output_container.mount( - OutputLine(_("⏱️ MCP response timeout ({seconds} seconds)").format(seconds=timeout_seconds)), - ) return stream_state["received_any_content"] except asyncio.CancelledError: output_container.mount(OutputLine(_("🚫 MCP response cancelled"))) @@ -1261,7 +1297,7 @@ class IntelligentTerminal(App): # 这里先返回 ID 和 ID 作为临时方案,后续在智能体列表加载后更新名称 return (default_app, default_app) # 如果没有配置默认智能体,使用智能问答 - return ("", "智能问答") + return ("", _("智能问答")) def _reinitialize_agent_state(self) -> None: """重新初始化智能体状态,用于后端切换时""" @@ -1301,10 +1337,9 @@ class IntelligentTerminal(App): async def _validate_backend_configuration(self, backend: Backend) -> bool: """验证后端配置""" try: - validator = APIValidator() - if backend == Backend.OPENAI: # 验证 OpenAI 配置 + validator = APIValidator() base_url = self.config_manager.get_base_url() api_key = self.config_manager.get_api_key() model = self.config_manager.get_model() @@ -1317,11 +1352,11 @@ class IntelligentTerminal(App): return valid if backend == Backend.EULERINTELLI: - # 验证 openEuler Intelligence 配置 - base_url = self.config_manager.get_eulerintelli_url() - api_key = self.config_manager.get_eulerintelli_key() - valid, _ = await validate_oi_connection(base_url, api_key) - return valid + # 验证 Hermes 配置 + llm_client = self.get_llm_client() + if isinstance(llm_client, HermesChatClient): + return await llm_client.ensure_user_info_loaded() + return False except Exception: self.logger.exception("验证后端配置时发生错误") @@ -1405,7 +1440,7 @@ class IntelligentTerminal(App): llm_client = self.get_llm_client() if hasattr(llm_client, "get_available_agents"): available_agents = await llm_client.get_available_agents() # type: ignore[attr-defined] - app_id, _ = self.current_agent + app_id, _name = self.current_agent # 查找匹配的智能体 agent_found = False @@ -1423,7 +1458,7 @@ class IntelligentTerminal(App): if not agent_found and app_id: self.logger.warning("配置的默认智能体 '%s' 不存在,回退到智能问答并清理配置", app_id) # 回退到智能问答 - self.current_agent = ("", "智能问答") + self.current_agent = ("", _("智能问答")) # 清理配置中的无效ID self.config_manager.set_default_app("") # 确保客户端也切换到智能问答 diff --git a/src/backend/__init__.py b/src/backend/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..62d732cd487ae2d9c3b61ac6816a85b0921a35c1 100644 --- a/src/backend/__init__.py +++ b/src/backend/__init__.py @@ -0,0 +1,9 @@ +"""后端模块""" + +from .base import LLMClientBase +from .factory import BackendFactory +from .hermes import HermesChatClient +from .models import LLMType, ModelInfo +from .openai import OpenAIClient + +__all__ = ["BackendFactory", "HermesChatClient", "LLMClientBase", "LLMType", "ModelInfo", "OpenAIClient"] diff --git a/src/backend/base.py b/src/backend/base.py index 65dab5c1a489431bc32c94a22e2aac29001075bf..395f13fc54f84a65f59462c50c3fed73f0039e10 100644 --- a/src/backend/base.py +++ b/src/backend/base.py @@ -9,6 +9,8 @@ if TYPE_CHECKING: from collections.abc import AsyncGenerator from types import TracebackType + from .models import ModelInfo + class LLMClientBase(ABC): """LLM 客户端基类""" @@ -35,12 +37,12 @@ class LLMClientBase(ABC): """ @abstractmethod - async def get_available_models(self) -> list[str]: + async def get_available_models(self) -> list[ModelInfo]: """ - 获取当前 LLM 服务中可用的模型,返回名称列表 + 获取当前 LLM 服务中可用的模型,返回模型信息列表 Returns: - list[str]: 可用的模型名称列表 + list[ModelInfo]: 可用的模型信息列表 """ diff --git a/src/backend/factory.py b/src/backend/factory.py index e5f2dfc8864684a4f563c6315dba5859da8a4c77..7dfa80c766e2bf9e25a231ad64884fe77cf67ea2 100644 --- a/src/backend/factory.py +++ b/src/backend/factory.py @@ -43,6 +43,7 @@ class BackendFactory: return HermesChatClient( base_url=config_manager.get_eulerintelli_url(), auth_token=config_manager.get_eulerintelli_key(), + config_manager=config_manager, ) msg = f"不支持的后端类型: {backend}" raise ValueError(msg) diff --git a/src/backend/hermes/__init__.py b/src/backend/hermes/__init__.py index 4e2b2e33e22ac70c002fb4622d71c82b4eb3ab10..3e7ae0f1e0cfbbd9543f90c3d8e5cee9e7761d7b 100644 --- a/src/backend/hermes/__init__.py +++ b/src/backend/hermes/__init__.py @@ -2,7 +2,7 @@ from .client import HermesChatClient from .exceptions import HermesAPIError -from .models import HermesAgent, HermesApp, HermesChatRequest, HermesFeatures, HermesMessage +from .models import HermesAgent, HermesApp, HermesChatRequest, HermesMessage from .services.agent import HermesAgentManager from .services.conversation import HermesConversationManager from .services.http import HermesHttpManager @@ -17,7 +17,6 @@ __all__ = [ "HermesChatClient", "HermesChatRequest", "HermesConversationManager", - "HermesFeatures", "HermesHttpManager", "HermesMessage", "HermesModelManager", diff --git a/src/backend/hermes/client.py b/src/backend/hermes/client.py index c13ab83ba899086f26cfea911695ee73bcea32af..b8667d0d49a5930e4309941a2cac745d1d5de9a5 100644 --- a/src/backend/hermes/client.py +++ b/src/backend/hermes/client.py @@ -4,18 +4,18 @@ from __future__ import annotations import json import time -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING, Any, Self from urllib.parse import urljoin import httpx from backend.base import LLMClientBase -from i18n.manager import get_locale +from i18n.manager import _, get_locale from log.manager import get_logger, log_exception from .constants import HTTP_OK from .exceptions import HermesAPIError -from .models import HermesApp, HermesChatRequest, HermesFeatures +from .models import HermesApp, HermesChatRequest from .services import ( HermesAgentManager, HermesConversationManager, @@ -30,6 +30,8 @@ if TYPE_CHECKING: from types import TracebackType from backend.mcp_handler import MCPEventHandler + from backend.models import ModelInfo + from config.manager import ConfigManager from .models import HermesAgent @@ -37,12 +39,20 @@ if TYPE_CHECKING: class HermesChatClient(LLMClientBase): """Hermes Chat API 客户端 - 重构版本""" - def __init__(self, base_url: str, auth_token: str = "") -> None: - """初始化 Hermes Chat API 客户端""" + def __init__(self, base_url: str, auth_token: str = "", config_manager: ConfigManager | None = None) -> None: + """ + 初始化 Hermes Chat API 客户端 + + Args: + base_url: API 基础 URL + auth_token: 认证令牌 + config_manager: 配置管理器(用于动态获取 llm_id) + + """ self.logger = get_logger(__name__) self.current_agent_id: str = "" # 当前选择的智能体 ID - self.current_task_id: str = "" # 当前正在运行的任务 ID + self.config_manager = config_manager # 配置管理器,用于动态获取 llm_id # HTTP 管理器 - 立即初始化 self.http_manager = HermesHttpManager(base_url, auth_token) @@ -57,6 +67,9 @@ class HermesChatClient(LLMClientBase): # MCP 事件处理器(可选) self._mcp_handler: MCPEventHandler | None = None + # 用户信息缓存(在初始化时加载) + self._user_info: dict[str, Any] | None = None + self.logger.info("Hermes 客户端初始化成功 - URL: %s", base_url) @property @@ -109,6 +122,94 @@ class HermesChatClient(LLMClientBase): self.current_agent_id = agent_id self.logger.info("设置当前智能体ID: %s", agent_id or "无智能体") + async def ensure_user_info_loaded(self) -> bool: + """ + 确保用户信息已加载 + + 在 Hermes 后端初始化时调用,加载并缓存用户信息。 + 后续可以直接通过 get_user_xxx 方法从内存获取,无需重复请求。 + + Returns: + bool: 是否成功加载用户信息 + + """ + if self._user_info is not None: + return True + + self.logger.info("开始加载用户信息...") + self._user_info = await self.user_manager.get_user_info() + + if self._user_info is not None: + self.logger.info( + "用户信息加载成功 - ID: %s, 用户名: %s", + self._user_info.get("userId"), + self._user_info.get("userName"), + ) + return True + + self.logger.warning("用户信息加载失败") + return False + + def get_personal_token(self) -> str: + """ + 获取个人令牌(从内存缓存) + + Returns: + str: 个人令牌,如果未加载则返回空字符串 + + """ + if self._user_info is None: + return "" + return self._user_info.get("personalToken", "") + + def get_user_id(self) -> str | None: + """获取用户ID(从内存缓存)""" + if self._user_info is None: + return None + return self._user_info.get("userId") + + def get_user_name(self) -> str: + """获取用户名(从内存缓存)""" + if self._user_info is None: + return "" + return self._user_info.get("userName", "") + + def get_auto_execute_status(self) -> bool: + """获取自动执行状态(从内存缓存)""" + if self._user_info is None: + return False + return self._user_info.get("autoExecute", False) + + def is_admin(self) -> bool: + """获取管理员状态(从内存缓存)""" + if self._user_info is None: + return False + return self._user_info.get("isAdmin", False) + + async def update_user_info(self, *, auto_execute: bool) -> bool: + """ + 更新用户信息 + + 更新成功后会自动更新内存中的缓存。 + + Args: + auto_execute: 是否启用自动执行 + + Returns: + bool: 更新是否成功 + + """ + success = await self.user_manager.update_user_info( + auto_execute=auto_execute, + ) + + if success and self._user_info is not None: + # 更新内存缓存 + self._user_info["autoExecute"] = auto_execute + self.logger.info("已更新内存中的用户信息缓存") + + return success + def reset_conversation(self) -> None: """重置会话,下次聊天时会创建新的会话""" if self._conversation_manager is not None: @@ -127,9 +228,12 @@ class HermesChatClient(LLMClientBase): str: 流式响应的文本内容 Raises: - HermesAPIError: 当 API 调用失败时 + HermesAPIError: 当 API 调用失败时或 llm_id 未配置时 """ + # 验证 llm_id 是否已配置 + self._validate_llm_id() + # 如果有未完成的会话,先停止它 await self._stop() @@ -141,9 +245,12 @@ class HermesChatClient(LLMClientBase): start_time = time.time() try: - # 确保有会话 ID - conversation_id = await self.conversation_manager.ensure_conversation() - self.logger.info("使用会话ID: %s", conversation_id) + # 获取当前会话ID(可能为空) + conversation_id = self.conversation_manager.get_conversation_id() + if conversation_id: + self.logger.info("使用现有会话ID: %s", conversation_id) + else: + self.logger.info("没有会话ID,后端将自动创建新会话") # 创建聊天请求 app = HermesApp(self.current_agent_id) @@ -154,10 +261,10 @@ class HermesChatClient(LLMClientBase): request = HermesChatRequest( app=app, - conversation_id=conversation_id, question=prompt, - features=HermesFeatures(), + conversation_id=conversation_id, language=language, + llm_id=self._get_llm_id(), ) # 直接传递异常,不在这里处理 @@ -172,11 +279,11 @@ class HermesChatClient(LLMClientBase): log_exception(self.logger, "Hermes 流式聊天请求失败", e) raise - async def get_available_models(self) -> list[str]: + async def get_available_models(self) -> list[ModelInfo]: """ - 获取当前 LLM 服务中可用的模型,返回名称列表 + 获取当前 LLM 服务中可用的模型,返回模型信息列表 - 通过调用 /api/llm 接口获取可用的大模型列表。 + 通过调用 /api/llm/provider 接口获取可用的大模型列表。 如果调用失败或没有返回,使用空列表,后端接口会自动使用默认模型。 """ return await self.model_manager.get_available_models() @@ -196,13 +303,15 @@ class HermesChatClient(LLMClientBase): """ return await self.agent_manager.get_available_agents() - async def send_mcp_response(self, task_id: str, *, params: bool | dict) -> AsyncGenerator[str, None]: + async def send_mcp_response(self, conversation_id: str, *, params: dict) -> AsyncGenerator[str, None]: """ 发送 MCP 响应并获取流式回复 Args: - task_id: 任务ID - params: 响应参数(bool 表示确认/取消,dict 表示参数补全) + conversation_id: 会话ID + params: 响应参数 + - 对于 MCP 确认消息: {"confirm": true/false} + - 对于参数补全: 包含补全参数的字典 Yields: str: 流式响应的文本内容 @@ -212,34 +321,28 @@ class HermesChatClient(LLMClientBase): """ # 不在 MCP 响应时重置状态跟踪,保持去重机制有效 - self.logger.info("发送 MCP 响应 - 任务ID: %s", task_id) + self.logger.info("发送 MCP 响应 - 会话ID: %s, 参数类型: %s", conversation_id, type(params).__name__) start_time = time.time() try: # 构建 MCP 响应请求 - client = await self.http_manager.get_client() - chat_url = urljoin(self.http_manager.base_url, "/api/chat") - headers = self.http_manager.build_headers() + app = HermesApp(self.current_agent_id, params=params) - request_data = { - "taskId": task_id, - "params": params, - } + current_locale = get_locale() + language = "zh" if current_locale.startswith("zh") else "en" - self.logger.info("准备发送 MCP 响应请求 - URL: %s, 任务ID: %s", chat_url, task_id) - self.logger.debug("请求头: %s", headers) - self.logger.debug("请求内容: %s", request_data) + request = HermesChatRequest( + app=app, + question="", + conversation_id=conversation_id, + language=language, + llm_id=self._get_llm_id(), + ) - async with client.stream( - "POST", - chat_url, - json=request_data, - headers=headers, - ) as response: - self.logger.info("收到 MCP 响应 - 状态码: %d", response.status_code) - await self._validate_chat_response(response) - async for text in self._process_stream_events(response): - yield text + self.logger.debug("MCP 响应请求数据: %s", request.to_dict()) + + async for text in self._chat_stream(request): + yield text duration = time.time() - start_time self.logger.info("MCP 响应请求完成 - 耗时: %.3fs", duration) @@ -249,46 +352,6 @@ class HermesChatClient(LLMClientBase): log_exception(self.logger, "MCP 响应请求失败", e) raise - async def get_auto_execute_status(self) -> bool: - """ - 获取用户自动执行状态 - - 这是一个便捷方法,从用户信息中提取自动执行状态。 - 默认情况下返回 False。 - - Returns: - bool: 自动执行状态,默认为 False - - """ - user_info = await self.user_manager.get_user_info() - if user_info is None: - self.logger.warning("无法获取用户信息,自动执行状态默认为 False") - return False - - auto_execute = user_info.get("auto_execute", False) - self.logger.debug("当前自动执行状态: %s", auto_execute) - return auto_execute - - async def enable_auto_execute(self) -> None: - """ - 启用自动执行 - - Returns: - bool: 更新是否成功 - - """ - await self.user_manager.update_auto_execute(auto_execute=True) - - async def disable_auto_execute(self) -> None: - """ - 禁用自动执行 - - Returns: - bool: 更新是否成功 - - """ - await self.user_manager.update_auto_execute(auto_execute=False) - async def interrupt(self) -> None: """ 中断当前正在进行的请求 @@ -309,6 +372,46 @@ class HermesChatClient(LLMClientBase): log_exception(self.logger, "关闭 Hermes 客户端失败", e) raise + def _get_llm_id(self) -> str: + """ + 从配置管理器获取当前的 llm_id + + Returns: + str: 当前配置的 llm_id,如果未配置则返回空字符串 + + """ + if self.config_manager is None: + return "" + return self.config_manager.get_llm_chat_model() + + def _validate_llm_id(self) -> None: + """ + 验证 llm_id 是否已配置 + + Raises: + HermesAPIError: 当 llm_id 未配置时 + + """ + llm_id = self._get_llm_id() + if not llm_id: + main_message = _("未配置 Chat 模型") + hint_prefix = _("配置步骤") + step1 = _("按 Ctrl+S 打开设置") + step2 = _("确认后端为 openEuler Intelligence") + step3 = _('点击 "更改用户设置" 按钮') + step4 = _('切换到 "大模型设置" 标签页') + step5 = _("使用 ↑↓ 键选择模型,空格激活,回车保存") + error_message = ( + f"{main_message}\n\n" + f"{hint_prefix}:\n" + f" 1. {step1}\n" + f" 2. {step2}\n" + f" 3. {step3}\n" + f" 4. {step4}\n" + f" 5. {step5}" + ) + raise HermesAPIError(400, error_message) + async def _chat_stream( self, request: HermesChatRequest, @@ -377,13 +480,12 @@ class HermesChatClient(LLMClientBase): event_count += 1 self.logger.info("解析到事件 #%d - 类型: %s", event_count, event.event_type) - # 处理任务ID - self._handle_task_id(event) + # 处理会话ID + self._handle_conversation_id(event) # 处理特殊事件类型 should_break, break_message = self.stream_processor.handle_special_events(event) if should_break: - self._cleanup_task_id("回答结束") if break_message: has_error_message = True yield break_message @@ -403,7 +505,6 @@ class HermesChatClient(LLMClientBase): except Exception: self.logger.exception("处理流式响应事件时出错") - self._cleanup_task_id("发生异常") raise # 只有在没有内容且没有错误消息的情况下才显示无内容消息 @@ -422,18 +523,12 @@ class HermesChatClient(LLMClientBase): self.logger.warning("无法解析 SSE 事件") return event - def _handle_task_id(self, event: HermesStreamEvent) -> None: - """处理事件中的任务ID""" - task_id = event.get_task_id() - if task_id and not self.current_task_id: - self.current_task_id = task_id - self.logger.debug("设置当前任务ID: %s", task_id) - - def _cleanup_task_id(self, context: str) -> None: - """清理任务ID""" - if self.current_task_id: - self.logger.debug("%s清理任务ID: %s", context, self.current_task_id) - self.current_task_id = "" + def _handle_conversation_id(self, event: HermesStreamEvent) -> None: + """处理事件中的会话ID""" + conversation_id = event.get_conversation_id() + if conversation_id: + # 通过 conversation_manager 存储会话ID + self.conversation_manager.set_conversation_id(conversation_id) async def _handle_event_content(self, event: HermesStreamEvent) -> AsyncGenerator[str, None]: """处理单个事件的内容""" @@ -462,9 +557,7 @@ class HermesChatClient(LLMClientBase): async def _stop(self) -> None: """停止当前会话""" if self._conversation_manager is not None: - await self._conversation_manager.stop_conversation(self.current_task_id) - # 停止后清理任务ID - self._cleanup_task_id("手动停止") + await self._conversation_manager.stop_conversation() async def __aenter__(self) -> Self: """异步上下文管理器入口""" diff --git a/src/backend/hermes/mcp_helpers.py b/src/backend/hermes/mcp_helpers.py index f43b333ff2f8643b7dd8471f0e8736343871c141..dff342be9f643a7968881962fbf419e985853e52 100644 --- a/src/backend/hermes/mcp_helpers.py +++ b/src/backend/hermes/mcp_helpers.py @@ -28,8 +28,8 @@ class MCPEmojis: INIT = "🔧" INPUT = "📥" OUTPUT = "✅" - CANCEL = "❌" - ERROR = "⚠️" + CANCEL = "⏹️" + ERROR = "❌" WAITING_START = "⏸️" WAITING_PARAM = "📝" diff --git a/src/backend/hermes/models.py b/src/backend/hermes/models.py index c7b891915f52cdd78c481ec941f0e90643117ca1..c23923c108601360b0764ff46546676f0529ef2c 100644 --- a/src/backend/hermes/models.py +++ b/src/backend/hermes/models.py @@ -58,64 +58,94 @@ class HermesMessage: return {"role": self.role, "content": self.content} -class HermesFeatures: - """Hermes 功能特性配置""" - - def __init__(self, max_tokens: int = 8192, context_num: int = 10) -> None: - """初始化功能特性配置""" - self.max_tokens = max_tokens - self.context_num = context_num - - def to_dict(self) -> dict[str, int]: - """转换为字典格式""" - return { - "max_tokens": self.max_tokens, - "context_num": self.context_num, - } - - class HermesApp: """Hermes 应用配置""" - def __init__(self, app_id: str, flow_id: str = "") -> None: - """初始化应用配置""" + def __init__( + self, + app_id: str, + flow_id: str = "", + *, + params: dict[str, Any] | None = None, + ) -> None: + """ + 初始化应用配置 + + Args: + app_id: 应用ID + flow_id: 流ID + params: MCP 响应参数 + - 对于 MCP 确认消息: {"confirm": true/false} + - 对于参数补全: 包含补全参数的字典 + + """ self.app_id = app_id self.flow_id = flow_id + self.params = params def to_dict(self) -> dict[str, Any]: """转换为字典格式""" - return { + app_dict: dict[str, Any] = { "appId": self.app_id, - "auth": {}, "flowId": self.flow_id, - "params": {}, } + # 如果有 MCP 响应参数,直接使用 params 的值 + if self.params is not None: + app_dict["params"] = self.params + else: + # 没有 params 时,添加空的 params 字段(保持向后兼容) + app_dict["params"] = {} + + return app_dict + class HermesChatRequest: """Hermes Chat 请求类""" - def __init__( + def __init__( # noqa: PLR0913 self, app: HermesApp, - conversation_id: str, question: str, - features: HermesFeatures | None = None, - language: str = "zh_cn", + conversation_id: str = "", + language: str = "zh", + llm_id: str = "", + kb_ids: list[str] | None = None, ) -> None: - """初始化 Hermes Chat 请求""" + """ + 初始化 Hermes Chat 请求 + + Args: + app: 应用配置 + question: 用户问题 + conversation_id: 会话ID + language: 语言 + llm_id: 大模型ID + kb_ids: 知识库ID列表 + + """ self.app = app self.conversation_id = conversation_id self.question = question - self.features = features or HermesFeatures() self.language = language + self.llm_id = llm_id + self.kb_ids = kb_ids or [] def to_dict(self) -> dict[str, Any]: """转换为请求字典格式""" - return { - "app": self.app.to_dict(), - "conversationId": self.conversation_id, - "features": self.features.to_dict(), - "language": self.language, + request_dict: dict[str, Any] = { "question": self.question, + "language": self.language, + "llmId": self.llm_id, } + + if self.app and self.app.app_id: + request_dict["app"] = self.app.to_dict() + + if self.conversation_id: + request_dict["conversationId"] = self.conversation_id + + if self.kb_ids: + request_dict["kbIds"] = self.kb_ids + + return request_dict diff --git a/src/backend/hermes/services/conversation.py b/src/backend/hermes/services/conversation.py index 86c2aecb48a69a85bfb48dc4773d8ef5c326f1d8..048071a2af5443b72eb378b26477e1c23dd0277f 100644 --- a/src/backend/hermes/services/conversation.py +++ b/src/backend/hermes/services/conversation.py @@ -27,59 +27,37 @@ class HermesConversationManager: self._conversation_id: str | None = None def reset_conversation(self) -> None: - """重置会话,下次聊天时会创建新的会话""" + """重置会话,清除当前会话ID,下次聊天时后端会自动创建新的会话""" + if self._conversation_id: + self.logger.info("重置会话 - 清除会话ID: %s", self._conversation_id) self._conversation_id = None - async def ensure_conversation(self, llm_id: str = "") -> str: + def get_conversation_id(self) -> str: """ - 确保有可用的会话 ID,智能重用空对话或创建新会话 - - 优先使用已存在的空对话,如果没有空对话或获取失败,则创建新对话。 - 这样可以避免产生过多的空对话记录。 - - Args: - llm_id: 指定的 LLM ID + 获取当前会话ID Returns: - str: 可用的会话 ID + str: 当前会话ID,如果没有会话则返回空字符串 """ - if self._conversation_id is None: - try: - # 先尝试获取现有对话列表 - conversation_list = await self._get_conversation_list() - - # 如果有对话,检查最新的对话是否为空 - if conversation_list: - latest_conversation_id = conversation_list[0] # 已经按时间排序,第一个是最新的 - try: - # 检查最新对话是否为空 - if await self._is_conversation_empty(latest_conversation_id): - self.logger.info("重用空对话 - ID: %s", latest_conversation_id) - self._conversation_id = latest_conversation_id - return self._conversation_id - except HermesAPIError: - # 如果检查对话记录失败,继续创建新对话 - self.logger.warning("检查对话记录失败,将创建新对话") - - # 如果没有对话或最新对话不为空,创建新对话 - self._conversation_id = await self._create_conversation(llm_id) - - except HermesAPIError: - # 如果获取对话列表失败,直接创建新对话 - self.logger.warning("获取对话列表失败,将创建新对话") - self._conversation_id = await self._create_conversation(llm_id) - - return self._conversation_id - - async def stop_conversation(self, task_id: str = "") -> None: + return self._conversation_id or "" + + def set_conversation_id(self, conversation_id: str) -> None: """ - 停止当前会话 + 设置会话ID + + 当从后端 SSE 流中接收到会话ID时调用此方法存储。 Args: - task_id: 可选的任务ID,如果提供且非空,则作为查询参数发送 + conversation_id: 从后端接收到的会话ID """ + if conversation_id and self._conversation_id != conversation_id: + self.logger.info("更新会话ID: %s -> %s", self._conversation_id or "空", conversation_id) + self._conversation_id = conversation_id + + async def stop_conversation(self) -> None: + """停止当前会话""" if self.http_manager.client is None or self.http_manager.client.is_closed: return @@ -87,12 +65,7 @@ class HermesConversationManager: stop_url = urljoin(self.http_manager.base_url, "/api/stop") headers = self.http_manager.build_headers() - # 构建请求参数 - params = {} - if task_id: - params["taskId"] = task_id - - response = await self.http_manager.client.post(stop_url, headers=headers, params=params) + response = await self.http_manager.client.post(stop_url, headers=headers) if response.status_code != HTTP_OK: error_text = await response.aread() diff --git a/src/backend/hermes/services/http.py b/src/backend/hermes/services/http.py index 0dce361703a69e391024bba80e0456725a06c5b0..e36ea9b02709521fcae27fa5bad69f8cfac89244 100644 --- a/src/backend/hermes/services/http.py +++ b/src/backend/hermes/services/http.py @@ -44,7 +44,7 @@ class HermesHttpManager: timeout = httpx.Timeout( connect=30.0, # 连接超时,允许30秒建立连接 - read=1800.0, # 读取超时,支持长时间SSE流(30分钟) + read=None, # 读取超时,无限制以支持超长时间SSE流 write=30.0, # 写入超时 pool=30.0, # 连接池超时 ) diff --git a/src/backend/hermes/services/model.py b/src/backend/hermes/services/model.py index ba1b7afdc1e2f30d52e1d320b4fdd425d4152e68..62d79cdd3feb4c31491bd2fb2155c45efd627452 100644 --- a/src/backend/hermes/services/model.py +++ b/src/backend/hermes/services/model.py @@ -10,6 +10,7 @@ from urllib.parse import urljoin import httpx from backend.hermes.constants import HTTP_OK +from backend.models import ModelInfo from log.manager import get_logger, log_api_request, log_exception if TYPE_CHECKING: @@ -24,19 +25,26 @@ class HermesModelManager: self.logger = get_logger(__name__) self.http_manager = http_manager - async def get_available_models(self) -> list[str]: + async def get_available_models(self) -> list[ModelInfo]: """ - 获取当前 LLM 服务中可用的模型,返回名称列表 + 获取当前 LLM 服务中可用的模型,返回模型信息列表 - 通过调用 /api/llm 接口获取可用的大模型列表。 + 通过调用 /api/llm/provider 接口获取可用的大模型列表。 如果调用失败或没有返回,使用空列表,后端接口会自动使用默认模型。 + + 返回的 ModelInfo 包含以下字段: + - model_name: 模型名称 + - llm_id: LLM ID + - llm_description: LLM 描述 + - llm_type: LLM 类型列表 + - max_tokens: 最大 token 数 """ start_time = time.time() self.logger.info("开始请求 Hermes 模型列表 API") try: client = await self.http_manager.get_client() - llm_url = urljoin(self.http_manager.base_url, "/api/llm") + llm_url = urljoin(self.http_manager.base_url, "/api/llm/provider") headers = self.http_manager.build_headers() response = await client.get(llm_url, headers=headers) @@ -84,14 +92,28 @@ class HermesModelManager: self.logger.warning("Hermes 模型列表 API result字段不是数组,返回空列表") return [] - # 提取模型名称 + # 解析模型信息 models = [] for llm_info in result: - if isinstance(llm_info, dict): - # 优先使用 modelName,如果没有则使用 llmId - model_name = llm_info.get("modelName") or llm_info.get("llmId") - if model_name: - models.append(model_name) + if not isinstance(llm_info, dict): + continue + + llm_id = llm_info.get("llmId") + if not llm_id: + continue + + # 解析并验证 llmType 字段 + llm_types = ModelInfo.parse_llm_types(llm_info.get("llmType")) + + # 构建 ModelInfo 对象 + model_info = ModelInfo( + model_name=llm_info.get("modelName") or llm_id, + llm_id=llm_id, + llm_description=llm_info.get("llmDescription"), + llm_type=llm_types, + max_tokens=llm_info.get("maxTokens"), + ) + models.append(model_info) # 记录成功的API请求 log_api_request( diff --git a/src/backend/hermes/services/user.py b/src/backend/hermes/services/user.py index 0db67b503c8b4a03c4d53169275e0320f6757e1b..96901805cca5c04cd0751d9cf76d31fb0455c040 100644 --- a/src/backend/hermes/services/user.py +++ b/src/backend/hermes/services/user.py @@ -30,17 +30,18 @@ class HermesUserManager: """ 获取用户信息 - 通过调用 GET /api/auth/user 接口获取当前用户信息, - 包括用户标识、权限、自动执行设置等。 + 通过调用 GET /api/user 接口获取当前用户信息, + 包括用户标识、用户名、权限、个人令牌、自动执行设置等。 Returns: dict[str, Any] | None: 用户信息字典,如果请求失败返回 None 返回数据格式: { - "user_sub": str, # 用户标识 - "revision": bool, # 权限标识 - "is_admin": bool, # 是否管理员 - "auto_execute": bool # 是否自动执行 + "userId": str, # 用户ID + "userName": str, # 用户名 + "isAdmin": bool, # 是否管理员 + "personalToken": str, # 个人令牌 + "autoExecute": bool # 是否自动执行 } """ @@ -49,7 +50,7 @@ class HermesUserManager: try: client = await self.http_manager.get_client() - user_url = urljoin(self.http_manager.base_url, "/api/auth/user") + user_url = urljoin(self.http_manager.base_url, "/api/user") headers = self.http_manager.build_headers() response = await client.get(user_url, headers=headers) @@ -83,10 +84,11 @@ class HermesUserManager: user_info = data["result"] self.logger.info( - "获取用户信息成功 - 用户: %s, 自动执行: %s, 管理员: %s", - user_info.get("user_sub", "未知"), - user_info.get("auto_execute", False), - user_info.get("is_admin", False), + "获取用户信息成功 - 用户ID: %s, 用户名: %s, 自动执行: %s, 管理员: %s", + user_info.get("userId", "未知"), + user_info.get("userName", "未知"), + user_info.get("autoExecute", False), + user_info.get("isAdmin", False), ) except (httpx.HTTPError, httpx.InvalidURL) as e: @@ -96,7 +98,7 @@ class HermesUserManager: log_api_request( self.logger, "GET", - f"{self.http_manager.base_url}/api/auth/user", + f"{self.http_manager.base_url}/api/user", 500, duration, error=str(e), @@ -106,9 +108,9 @@ class HermesUserManager: else: return user_info - async def update_auto_execute(self, *, auto_execute: bool) -> None: + async def update_user_info(self, *, auto_execute: bool = False) -> bool: """ - 更新用户自动执行设置 + 更新用户信息 通过调用 POST /api/user 接口更新当前用户的自动执行设置。 @@ -120,7 +122,10 @@ class HermesUserManager: """ start_time = time.time() - self.logger.info("开始请求 Hermes 用户设置更新 API - auto_execute: %s", auto_execute) + self.logger.info( + "开始请求 Hermes 用户信息更新 API - auto_execute: %s", + auto_execute, + ) try: client = await self.http_manager.get_client() @@ -132,7 +137,7 @@ class HermesUserManager: ) # 构建请求体 - request_data = { + request_data: dict[str, Any] = { "autoExecute": auto_execute, } @@ -150,15 +155,13 @@ class HermesUserManager: # 处理HTTP错误状态 if response.status_code != HTTP_OK: error_msg = f"API 调用失败,状态码: {response.status_code}" - self.logger.warning("更新用户设置失败: %s", error_msg) - return - - self.logger.info("更新用户设置成功") + self.logger.warning("更新用户信息失败: %s", error_msg) + return False except (httpx.HTTPError, httpx.InvalidURL) as e: # 网络请求异常 duration = time.time() - start_time - log_exception(self.logger, "Hermes 用户设置更新 API 请求异常", e) + log_exception(self.logger, "Hermes 用户信息更新 API 请求异常", e) log_api_request( self.logger, "POST", @@ -167,8 +170,11 @@ class HermesUserManager: duration, error=str(e), ) - self.logger.warning("Hermes 用户设置更新 API 请求异常") - return + self.logger.warning("Hermes 用户信息更新 API 请求异常") + return False + else: + self.logger.info("更新用户信息成功") + return True def _validate_user_response(self, data: dict[str, Any]) -> bool: """验证用户信息 API 响应结构""" @@ -189,7 +195,7 @@ class HermesUserManager: return False # 检查必要字段是否存在 - required_fields = ["user_sub", "auto_execute"] + required_fields = ["userId", "userName", "isAdmin", "autoExecute"] for field in required_fields: if field not in result: self.logger.warning("用户信息缺少必要字段: %s", field) diff --git a/src/backend/hermes/stream.py b/src/backend/hermes/stream.py index 020c089e8799759fff5d56a0f663ceab9ef48caa..5cf3b89f2cf047174b69e132fcaeb91179c01a1d 100644 --- a/src/backend/hermes/stream.py +++ b/src/backend/hermes/stream.py @@ -77,14 +77,20 @@ class HermesStreamEvent: flow = self.get_flow_info() return flow.get("stepId", "") + def get_step_status(self) -> str: + """获取步骤状态""" + flow = self.get_flow_info() + return flow.get("stepStatus", "") + + def get_executor_status(self) -> str: + """获取执行器状态""" + flow = self.get_flow_info() + return flow.get("executorStatus", "") + def get_conversation_id(self) -> str: """获取会话ID""" return self.data.get("conversationId", "") - def get_task_id(self) -> str: - """获取任务ID""" - return self.data.get("taskId", "") - def get_content(self) -> dict[str, Any]: """获取内容部分""" return self.data.get("content", {}) @@ -93,17 +99,6 @@ class HermesStreamEvent: """判断是否为 MCP 步骤相关事件""" return self.event_type in MCPEventTypes.ALL_STEP_EVENTS - def is_flow_event(self) -> bool: - """判断是否为流相关事件""" - flow_events = { - "flow.start", - "flow.stop", - "flow.failed", - "flow.success", - "flow.cancel", - } - return self.event_type in flow_events - class HermesStreamProcessor: """Hermes 流响应处理器""" @@ -151,23 +146,22 @@ class HermesStreamProcessor: def format_mcp_status(self, event: HermesStreamEvent) -> str | None: """格式化 MCP 状态信息为可读文本""" - # 忽略 flow 事件 - if event.is_flow_event(): - return None - - # 只处理 step 事件 if not event.is_mcp_step_event(): return None + event_type = event.event_type + step_status = event.get_step_status() + executor_status = event.get_executor_status() + + # 优先检查状态字段:后端通过 stepStatus/executorStatus=error 表示失败 + if step_status == "error" or executor_status == "error": + return self._format_error_status(event) + step_name = event.get_step_name() step_id = event.get_step_id() - event_type = event.event_type content = event.get_content() - - # 检查是否应该替换之前的进度消息 should_replace = self._should_replace_progress(event, step_id) - # 处理特殊的等待状态事件 if event_type == MCPEventTypes.STEP_WAITING_FOR_START: base_message = self._format_waiting_for_start(content, step_name) return self._handle_progress_message( @@ -188,8 +182,28 @@ class HermesStreamProcessor: should_replace=should_replace, ) - # 处理其他事件类型 - return self._format_standard_status(event_type, step_name, step_id, should_replace=should_replace) + return self._format_standard_status( + event_type, + step_name, + step_id, + step_status, + should_replace=should_replace, + ) + + def _format_error_status(self, event: HermesStreamEvent) -> str: + """格式化错误状态消息""" + flow_info = event.get_flow_info() + step_name = event.get_step_name() or flow_info.get("stepName", "未知工具") + step_id = flow_info.get("stepId", "") + + base_message = MCPMessageTemplates.error_message(step_name) + return self._handle_progress_message( + MCPEventTypes.STEP_ERROR, + step_name, + step_id, + base_message, + should_replace=True, + ) def _format_waiting_for_start( self, @@ -216,11 +230,14 @@ class HermesStreamProcessor: event_type: str, step_name: str, step_id: str, + step_status: str, *, should_replace: bool, ) -> str | None: - """格式化标准状态消息""" - # 定义事件类型到状态消息的映射 + """格式化标准步骤状态消息""" + if step_status == "error": + event_type = MCPEventTypes.STEP_ERROR + status_messages = { MCPEventTypes.STEP_INIT: MCPMessageTemplates.init_message(step_name), MCPEventTypes.STEP_INPUT: MCPMessageTemplates.input_message(step_name), @@ -233,11 +250,7 @@ class HermesStreamProcessor: if not base_message: return None - # 定义进度消息类型 - progress_message_types = MCPEventTypes.PROGRESS_MESSAGE_EVENTS - - # 对于所有步骤相关的消息,都检查是否需要替换之前的进度 - if event_type in progress_message_types and step_id: + if event_type in MCPEventTypes.PROGRESS_MESSAGE_EVENTS and step_id: base_message = self._handle_progress_message( event_type, step_name, @@ -257,37 +270,26 @@ class HermesStreamProcessor: *, should_replace: bool, ) -> str: - """处理进度消息的替换逻辑""" - # 检查是否为最终状态消息 + """处理进度消息的 MCP 标记和替换逻辑""" is_final_state = event_type in MCPEventTypes.FINAL_STATE_EVENTS - - # 关键修复:使用工具名称而不是step_id来跟踪,确保同一工具的后续状态更新能够替换之前的进度 - # 策略:如果是同一个工具名称的后续消息,就应该替换之前的消息 has_previous_progress = step_name in self._current_tool_progress - # 这是一个进度消息,记录到跟踪字典中(使用工具名称作为key) + # 非最终状态:记录进度到跟踪字典(使用工具名称作为 key) if not is_final_state: self._current_tool_progress[step_name] = { "message": base_message, "should_replace": should_replace, "is_progress": True, - "step_id": step_id, # 保留step_id用于调试 + "step_id": step_id, } - # 使用工具名称作为标识,确保TUI层面能正确识别为MCP消息 + # 添加 MCP 标记:如果存在之前的进度则标记为替换,否则为新消息 if has_previous_progress: - # 如果有之前的进度,说明这是一个状态更新,需要替换 base_message = f"{create_mcp_tag(step_name, is_replace=True)}{base_message}" if is_final_state: - self.logger.debug("添加替换标记给最终状态消息,工具 %s: %s", step_name, event_type) - # 清理对应的进度信息 self._current_tool_progress.pop(step_name, None) - else: - self.logger.debug("添加替换标记给工具 %s: %s", step_name, event_type) else: - # 如果是第一个进度消息,添加MCP标记但不替换 base_message = f"{create_mcp_tag(step_name, is_replace=False)}{base_message}" - self.logger.debug("添加MCP标记给首次进度消息,工具 %s: %s", step_name, event_type) return base_message @@ -297,21 +299,11 @@ class HermesStreamProcessor: if not step_name: return False - # 定义进度消息类型 - progress_message_types = MCPEventTypes.PROGRESS_MESSAGE_EVENTS - event_type = event.event_type - # 对于进度消息类型,只要存在同一个工具名称的之前记录,就应该替换 - if event_type in progress_message_types and step_name in self._current_tool_progress: + # 进度消息类型 + 存在之前的记录 → 需要替换 + if event_type in MCPEventTypes.PROGRESS_MESSAGE_EVENTS and step_name in self._current_tool_progress: prev_info = self._current_tool_progress[step_name] - if prev_info.get("is_progress", False): - self.logger.debug( - "工具 %s 的进度消息将被替换: %s -> %s", - step_name, - prev_info.get("message", "").strip()[:50], - event_type, - ) - return True + return prev_info.get("is_progress", False) return False diff --git a/src/backend/models.py b/src/backend/models.py new file mode 100644 index 0000000000000000000000000000000000000000..f8c8eb0fa65522d1541d29a86418d1e9b0ba33dc --- /dev/null +++ b/src/backend/models.py @@ -0,0 +1,85 @@ +"""后端模型数据结构定义""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum + + +class LLMType(str, Enum): + """ + LLM 类型枚举 + + 定义了 Hermes 后端支持的 LLM 能力类型。 + """ + + CHAT = "chat" + """模型支持 Chat(聊天对话)""" + + FUNCTION = "function" + """模型支持 Function Call(函数调用)""" + + EMBEDDING = "embedding" + """模型支持 Embedding(向量嵌入)""" + + VISION = "vision" + """模型支持图片理解(视觉能力)""" + + THINKING = "thinking" + """模型支持思考推理(推理能力)""" + + +@dataclass +class ModelInfo: + """ + 模型信息数据类 + + 该类用于统一表示不同后端(OpenAI、Hermes)返回的模型信息。 + + 注意: + - model_name: 仅用于后端调用大模型 API 时使用,CLI 前端不需要关心 + - llm_id: CLI 前端使用的模型标识符,用于显示和配置保存 + """ + + # 通用字段(所有后端都支持) + model_name: str + """模型名称,仅用于后端调用大模型 API""" + + # Hermes 特有字段 + llm_id: str | None = None + """LLM ID,CLI 前端使用的模型唯一标识符(用于显示和配置)""" + + llm_description: str | None = None + """LLM 描述,Hermes 后端的模型说明""" + + llm_type: list[LLMType] = field(default_factory=list) + """LLM 类型列表,如 [LLMType.CHAT, LLMType.FUNCTION],Hermes 后端特有""" + + max_tokens: int | None = None + """模型支持的最大 token 数,Hermes 后端提供""" + + def __str__(self) -> str: + """返回模型的字符串表示(优先使用 llm_id)""" + return self.llm_id or self.model_name + + def __repr__(self) -> str: + """返回模型的详细表示""" + return f"ModelInfo(model_name={self.model_name!r}, llm_id={self.llm_id!r})" + + @staticmethod + def parse_llm_types(llm_types: list[str] | None) -> list[LLMType]: + """ + 解析 LLM 类型字符串列表,过滤掉不合法的值 + + Args: + llm_types: LLM 类型字符串列表 + + Returns: + list[LLMType]: 合法的 LLM 类型枚举列表 + + """ + if not llm_types: + return [] + + valid_values = {t.value for t in LLMType} + return [LLMType(llm_type_str) for llm_type_str in llm_types if llm_type_str in valid_values] diff --git a/src/backend/openai.py b/src/backend/openai.py index ba9a8871403e6c1e206d915616c7283a93ee3a10..bd8a0611cfa8aa0ed2c3faddbebccadae001a8ae 100644 --- a/src/backend/openai.py +++ b/src/backend/openai.py @@ -10,6 +10,7 @@ import httpx from openai import AsyncOpenAI, OpenAIError from backend.base import LLMClientBase +from backend.models import ModelInfo from log.manager import get_logger, log_api_request, log_exception if TYPE_CHECKING: @@ -47,7 +48,7 @@ class OpenAIClient(LLMClientBase): self.logger.debug("OpenAIClient SSL 验证状态: %s", self.verify_ssl) # 添加历史记录管理 - self._conversation_history: list[ChatCompletionMessageParam] = [] + self.conversation_history: list[ChatCompletionMessageParam] = [] # 用于中断的任务跟踪 self._current_task: asyncio.Task | None = None @@ -66,13 +67,13 @@ class OpenAIClient(LLMClientBase): # 添加用户消息到历史记录 user_message: ChatCompletionMessageParam = {"role": "user", "content": prompt} - self._conversation_history.append(user_message) + self.conversation_history.append(user_message) try: # 使用完整的对话历史记录 response = await self.client.chat.completions.create( model=self.model, - messages=self._conversation_history, + messages=self.conversation_history, stream=True, ) @@ -86,7 +87,7 @@ class OpenAIClient(LLMClientBase): duration, model=self.model, stream=True, - history_length=len(self._conversation_history), + history_length=len(self.conversation_history), ) # 收集助手的完整回复 @@ -101,11 +102,11 @@ class OpenAIClient(LLMClientBase): self.logger.info("OpenAI 流式响应被中断") # 如果被中断,移除刚添加的用户消息 if ( - self._conversation_history - and len(self._conversation_history) > 0 - and self._conversation_history[-1].get("content") == prompt + self.conversation_history + and len(self.conversation_history) > 0 + and self.conversation_history[-1].get("content") == prompt ): - self._conversation_history.pop() + self.conversation_history.pop() raise # 将助手回复添加到历史记录 @@ -114,8 +115,8 @@ class OpenAIClient(LLMClientBase): "role": "assistant", "content": assistant_response, } - self._conversation_history.append(assistant_message) - self.logger.info("对话历史记录已更新,当前消息数: %d", len(self._conversation_history)) + self.conversation_history.append(assistant_message) + self.logger.info("对话历史记录已更新,当前消息数: %d", len(self.conversation_history)) except asyncio.CancelledError: # 重新抛出取消异常 @@ -123,11 +124,11 @@ class OpenAIClient(LLMClientBase): except OpenAIError as e: # 如果请求失败,移除刚添加的用户消息 if ( - self._conversation_history - and len(self._conversation_history) > 0 - and self._conversation_history[-1].get("content") == prompt + self.conversation_history + and len(self.conversation_history) > 0 + and self.conversation_history[-1].get("content") == prompt ): - self._conversation_history.pop() + self.conversation_history.pop() duration = time.time() - start_time log_exception(self.logger, "OpenAI 流式聊天 API 请求失败", e) @@ -173,22 +174,26 @@ class OpenAIClient(LLMClientBase): 清空历史记录,开始新的对话会话。 """ - self._conversation_history.clear() + self.conversation_history.clear() self.logger.info("OpenAI 客户端对话历史记录已重置") - async def get_available_models(self) -> list[str]: + async def get_available_models(self) -> list[ModelInfo]: """ - 获取当前 LLM 服务中可用的模型,返回名称列表 + 获取当前 LLM 服务中可用的模型,返回模型信息列表 调用 LLM 服务的模型列表接口,并解析返回结果提取模型名称。 如果服务不支持模型列表接口,返回空列表。 + + 对于 OpenAI 后端,只返回基础的 model_name 字段,其他 Hermes 特有字段为 None。 """ start_time = time.time() self.logger.info("开始请求 OpenAI 模型列表 API") try: models_response = await self.client.models.list() - models = [model.id async for model in models_response] + model_names = [model.id async for model in models_response] + # 将模型名称转换为 ModelInfo 对象 + models = [ModelInfo(model_name=name) for name in model_names] # 记录成功的API请求 duration = time.time() - start_time log_api_request( diff --git a/src/config/manager.py b/src/config/manager.py index ba45454038c50b2fcfd091f4964fce348700e0d2..1d8d02604d93e365f6a2aa29269f69cbccdd6c36 100644 --- a/src/config/manager.py +++ b/src/config/manager.py @@ -204,6 +204,15 @@ class ConfigManager: self.data.eulerintelli.default_app = app_id self._save_settings() + def get_llm_chat_model(self) -> str: + """获取 Chat 模型的 llmId""" + return self.data.eulerintelli.llm_chat + + def set_llm_chat_model(self, llm_id: str) -> None: + """更新 Chat 模型的 llmId 并保存""" + self.data.eulerintelli.llm_chat = llm_id + self._save_settings() + def get_locale(self) -> str: """获取当前语言环境""" return self.data.locale diff --git a/src/config/model.py b/src/config/model.py index ab986c4ea5db06ac56ca773a05551be75bd3fcff..4f02f115200b713868b43ceb0d6cf0a9f208bfa8 100644 --- a/src/config/model.py +++ b/src/config/model.py @@ -57,6 +57,7 @@ class HermesConfig: base_url: str = field(default="http://127.0.0.1:8002") api_key: str = field(default="") default_app: str = field(default="") + llm_chat: str = field(default="") # Chat 模型的 llmId @classmethod def from_dict(cls, d: dict) -> "HermesConfig": @@ -65,6 +66,7 @@ class HermesConfig: base_url=d.get("base_url", cls.base_url), api_key=d.get("api_key", cls.api_key), default_app=d.get("default_app", cls.default_app), + llm_chat=d.get("llm_chat", cls.llm_chat), ) def to_dict(self) -> dict: @@ -73,6 +75,7 @@ class HermesConfig: "base_url": self.base_url, "api_key": self.api_key, "default_app": self.default_app, + "llm_chat": self.llm_chat, } diff --git a/src/i18n/locales/en_US/LC_MESSAGES/messages.po b/src/i18n/locales/en_US/LC_MESSAGES/messages.po index 8e5ecdf708c6a62c5869239e4ed18bf90a6769f5..6b4cab1dccd0326275746829151534f2ef925cbe 100644 --- a/src/i18n/locales/en_US/LC_MESSAGES/messages.po +++ b/src/i18n/locales/en_US/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: oi-cli\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-21 10:54+0800\n" +"POT-Creation-Date: 2025-11-04 17:40+0800\n" "PO-Revision-Date: 2025-10-20 19:28+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: English\n" @@ -17,233 +17,1565 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.8\n" -#: src/app/mcp_widgets.py:47 -msgid "需要用户确认是否执行此工具" -msgstr "User confirmation required to execute this tool" +#: src/app/mcp_widgets.py:50 src/backend/hermes/mcp_helpers.py:286 +msgid "低风险" +msgstr "Low risk" + +#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:287 +msgid "中等风险" +msgstr "Medium risk" + +#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:288 +msgid "高风险" +msgstr "High risk" + +#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:289 +msgid "未知风险" +msgstr "Unknown risk" + +#: src/app/mcp_widgets.py:66 +msgid "✓ 确认" +msgstr "✓ Confirm" + +#: src/app/mcp_widgets.py:67 src/app/mcp_widgets.py:170 +msgid "✗ 取消" +msgstr "✗ Cancel" + +#: src/app/mcp_widgets.py:153 +msgid "📝 参数输入" +msgstr "📝 Parameter Input" + +#: src/app/mcp_widgets.py:160 +#, python-brace-format +msgid "请输入 {param_name}" +msgstr "Please enter {param_name}" + +#: src/app/mcp_widgets.py:169 +msgid "✓ 提交" +msgstr "✓ Submit" + +#: src/app/deployment/service.py:66 +#, python-brace-format +msgid "无法读取模板文件: {path}" +msgstr "Unable to read template file: {path}" + +#: src/app/deployment/service.py:138 +#, python-brace-format +msgid "TOML 格式错误: {error}" +msgstr "TOML format error: {error}" + +#: src/app/deployment/service.py:142 +#, python-brace-format +msgid "更新 TOML 配置失败: {error}" +msgstr "Failed to update TOML configuration: {error}" + +#: src/app/deployment/service.py:188 +msgid "检查部署依赖" +msgstr "Checking deployment dependencies" + +#: src/app/deployment/service.py:189 +msgid "正在检查部署环境依赖..." +msgstr "Checking deployment environment dependencies..." + +#: src/app/deployment/service.py:194 +#: src/app/deployment/components/env_check.py:141 +msgid "仅支持 openEuler 操作系统" +msgstr "Only openEuler operating system is supported" + +#: src/app/deployment/service.py:201 +#, python-brace-format +msgid "⚠ 检测到 Python {version},低于 3.10 版本将不支持全量部署模式" +msgstr "" +"⚠ Detected Python {version}, versions below 3.10 do not support full " +"deployment mode" + +#: src/app/deployment/service.py:210 +msgid "缺少 openeuler-intelligence-installer 包,正在尝试安装..." +msgstr "" +"Missing openeuler-intelligence-installer package, attempting to install..." + +#: src/app/deployment/service.py:220 +#: src/app/deployment/components/env_check.py:164 +msgid "需要管理员权限,请确保可以使用 sudo" +msgstr "Administrator privileges required, please ensure sudo is available" + +#: src/app/deployment/service.py:224 +msgid "✓ 部署环境依赖检查完成" +msgstr "✓ Deployment environment dependency check completed" + +#: src/app/deployment/service.py:271 +msgid "" +"当前 openEuler 版本低于 24.03 LTS,不支持全量部署模式。请使用轻量部署模式或升" +"级到 openEuler 24.03+ 版本" +msgstr "" +"Current openEuler version is below 24.03 LTS and does not support full " +"deployment mode. Please use lightweight deployment mode or upgrade to " +"openEuler 24.03+" + +#: src/app/deployment/service.py:277 +#, python-brace-format +msgid "无法检查 Python 环境: {error}" +msgstr "Unable to check Python environment: {error}" + +#: src/app/deployment/service.py:280 +#, python-brace-format +msgid "Python 环境版本 {version} 符合要求" +msgstr "Python environment version {version} meets requirements" + +#: src/app/deployment/service.py:336 +msgid "部署过程中发生异常" +msgstr "Exception occurred during deployment" + +#: src/app/deployment/service.py:337 +msgid "✗ 部署失败" +msgstr "✗ Deployment failed" + +#: src/app/deployment/service.py:347 +msgid "✓ openEuler Intelligence 后端部署完成!" +msgstr "✓ openEuler Intelligence backend deployment completed!" + +#: src/app/deployment/service.py:385 +msgid "正在安装 openeuler-intelligence-installer..." +msgstr "Installing openeuler-intelligence-installer..." + +#: src/app/deployment/service.py:396 +msgid "✓ openeuler-intelligence-installer 安装成功" +msgstr "✓ openeuler-intelligence-installer installed successfully" + +#: src/app/deployment/service.py:400 +msgid "openeuler-intelligence-installer 安装后资源文件仍然缺失" +msgstr "" +"Resource files still missing after openeuler-intelligence-installer " +"installation" + +#: src/app/deployment/service.py:403 +msgid "安装 openeuler-intelligence-installer 失败" +msgstr "Failed to install openeuler-intelligence-installer" + +#: src/app/deployment/service.py:406 +msgid "安装输出:" +msgstr "Installation output:" + +#: src/app/deployment/service.py:410 +#, python-brace-format +msgid "安装过程中发生异常: {error}" +msgstr "Exception occurred during installation: {error}" + +#: src/app/deployment/service.py:446 +msgid "✓ 基础服务部署完成" +msgstr "✓ Basic service deployment completed" + +#: src/app/deployment/service.py:447 +msgid "请访问网页管理界面完成 Agent 服务配置" +msgstr "" +"Please visit the web management interface to complete Agent service " +"configuration" + +#: src/app/deployment/service.py:448 +#, python-brace-format +msgid "管理界面地址: http://{ip}:8080" +msgstr "Management interface address: http://{ip}:8080" + +#: src/app/deployment/service.py:485 src/app/deployment/service.py:575 +msgid "检查系统环境" +msgstr "Checking system environment" + +#: src/app/deployment/service.py:486 +msgid "正在检查系统环境..." +msgstr "Checking system environment..." + +#: src/app/deployment/service.py:493 +msgid "✗ 错误: 仅支持 openEuler 操作系统" +msgstr "✗ Error: Only openEuler operating system is supported" + +#: src/app/deployment/service.py:495 +msgid "✓ 检测到 openEuler 操作系统" +msgstr "✓ openEuler operating system detected" + +#: src/app/deployment/service.py:500 +#, python-brace-format +msgid "✗ 错误: {msg}" +msgstr "✗ Error: {msg}" + +#: src/app/deployment/service.py:505 +msgid "✗ 错误: openeuler-intelligence-installer 包未安装或资源缺失" +msgstr "" +"✗ Error: openeuler-intelligence-installer package is not installed or " +"resources are missing" + +#: src/app/deployment/service.py:506 +msgid "请先安装: sudo dnf install -y openeuler-intelligence-installer" +msgstr "" +"Please install first: sudo dnf install -y openeuler-intelligence-installer" + +#: src/app/deployment/service.py:508 +msgid "✓ openeuler-intelligence-installer 资源可用" +msgstr "✓ openeuler-intelligence-installer resources available" + +#: src/app/deployment/service.py:512 +msgid "✗ 错误: 需要管理员权限" +msgstr "✗ Error: Administrator privileges required" + +#: src/app/deployment/service.py:514 +msgid "✓ 具有管理员权限" +msgstr "✓ Administrator privileges available" + +#: src/app/deployment/service.py:525 +msgid "初始化部署配置" +msgstr "Initializing deployment configuration" + +#: src/app/deployment/service.py:526 +msgid "正在设置部署模式..." +msgstr "Setting deployment mode..." + +#: src/app/deployment/service.py:553 src/app/deployment/service.py:562 +#, python-brace-format +msgid "✗ 设置部署模式失败: {error}" +msgstr "✗ Failed to set deployment mode: {error}" + +#: src/app/deployment/service.py:556 src/app/deployment/service.py:557 +msgid "启用" +msgstr "Enabled" + +#: src/app/deployment/service.py:556 src/app/deployment/service.py:557 +msgid "禁用" +msgstr "Disabled" + +#: src/app/deployment/service.py:558 +#, python-brace-format +msgid "✓ 部署模式设置完成 (Web界面: {web}, RAG: {rag})" +msgstr "" +"✓ Deployment mode configuration completed (Web interface: {web}, RAG: {rag})" + +#: src/app/deployment/service.py:576 +msgid "正在执行系统环境检查..." +msgstr "Performing system environment check..." + +#: src/app/deployment/service.py:583 +msgid "环境检查脚本" +msgstr "Environment check script" + +#: src/app/deployment/service.py:585 +#, python-brace-format +msgid "✗ 环境检查失败: {error}" +msgstr "✗ Environment check failed: {error}" + +#: src/app/deployment/service.py:596 +msgid "安装依赖组件" +msgstr "Installing dependency components" + +#: src/app/deployment/service.py:597 +msgid "正在安装 openEuler Intelligence 依赖组件..." +msgstr "Installing openEuler Intelligence dependency components..." + +#: src/app/deployment/service.py:606 +msgid "依赖安装脚本" +msgstr "Dependency installation script" + +#: src/app/deployment/service.py:608 +#, python-brace-format +msgid "✗ 依赖安装失败: {error}" +msgstr "✗ Dependency installation failed: {error}" + +#: src/app/deployment/service.py:619 +msgid "初始化配置和服务" +msgstr "Initializing configuration and services" + +#: src/app/deployment/service.py:620 +msgid "正在初始化配置和启动服务..." +msgstr "Initializing configuration and starting services..." + +#: src/app/deployment/service.py:627 +msgid "配置初始化脚本" +msgstr "Configuration initialization script" + +#: src/app/deployment/service.py:629 +#, python-brace-format +msgid "✗ 配置初始化失败: {error}" +msgstr "✗ Configuration initialization failed: {error}" + +#: src/app/deployment/service.py:641 +#, python-brace-format +msgid "✗ 脚本文件不存在: {path}" +msgstr "✗ Script file does not exist: {path}" + +#: src/app/deployment/service.py:679 +#, python-brace-format +msgid "✓ {name}执行成功" +msgstr "✓ {name} executed successfully" + +#: src/app/deployment/service.py:683 +#, python-brace-format +msgid "✗ 运行{name}时发生错误: {error}" +msgstr "✗ Error occurred while running {name}: {error}" + +#: src/app/deployment/service.py:688 +#, python-brace-format +msgid "✗ {name}执行失败,返回码: {code}" +msgstr "✗ {name} execution failed, return code: {code}" + +#: src/app/deployment/service.py:709 +msgid "更新配置文件" +msgstr "Updating configuration files" + +#: src/app/deployment/service.py:710 +msgid "正在更新配置文件..." +msgstr "Updating configuration files..." + +#: src/app/deployment/service.py:718 +msgid "✓ 更新 env 配置文件" +msgstr "✓ Updated env configuration file" + +#: src/app/deployment/service.py:722 +msgid "✓ 更新 config.toml 配置文件" +msgstr "✓ Updated config.toml configuration file" + +#: src/app/deployment/service.py:725 +#, python-brace-format +msgid "✗ 更新配置文件失败: {error}" +msgstr "✗ Failed to update configuration files: {error}" + +#: src/app/deployment/service.py:758 +#, python-brace-format +msgid "备份 env 文件失败: {error}" +msgstr "Failed to backup env file: {error}" + +#: src/app/deployment/service.py:773 +#, python-brace-format +msgid "写入 env 文件失败: {error}" +msgstr "Failed to write env file: {error}" + +#: src/app/deployment/service.py:803 +#, python-brace-format +msgid "备份 config.toml 文件失败: {error}" +msgstr "Failed to backup config.toml file: {error}" + +#: src/app/deployment/service.py:818 +#, python-brace-format +msgid "写入 config.toml 文件失败: {error}" +msgstr "Failed to write config.toml file: {error}" + +#: src/app/deployment/service.py:866 +#, python-brace-format +msgid "检查 oi-runtime 服务状态 ({current}/{total})..." +msgstr "Checking oi-runtime service status ({current}/{total})..." + +#: src/app/deployment/service.py:888 +msgid "✓ Framework 服务状态正常" +msgstr "✓ Framework service status is normal" + +#: src/app/deployment/service.py:891 +#, python-brace-format +msgid "Framework 服务状态: {status}" +msgstr "Framework service status: {status}" + +#: src/app/deployment/service.py:894 +#, python-brace-format +msgid "等待 {seconds} 秒后重试..." +msgstr "Retrying in {seconds} seconds..." + +#: src/app/deployment/service.py:898 +#, python-brace-format +msgid "检查服务状态时发生错误: {error}" +msgstr "Error occurred while checking service status: {error}" + +#: src/app/deployment/service.py:902 +msgid "✗ Framework 服务状态检查超时失败" +msgstr "✗ Framework service status check timed out" + +#: src/app/deployment/service.py:917 +msgid "等待 openEuler Intelligence 服务就绪" +msgstr "Waiting for openEuler Intelligence service to be ready" + +#: src/app/deployment/service.py:929 +msgid "✓ openEuler Intelligence 服务已就绪" +msgstr "✓ openEuler Intelligence service is ready" + +#: src/app/deployment/service.py:935 +#, python-brace-format +msgid "连接 {url} 超时" +msgstr "Connection to {url} timed out" + +#: src/app/deployment/service.py:937 +#, python-brace-format +msgid "API 连通性检查时发生错误: {error}" +msgstr "Error occurred during API connectivity check: {error}" + +#: src/app/deployment/service.py:942 +msgid "✗ openEuler Intelligence API 服务检查超时失败" +msgstr "✗ openEuler Intelligence API service check timed out" + +#: src/app/deployment/service.py:952 +msgid "初始化 Agent 服务" +msgstr "Initializing Agent service" + +#: src/app/deployment/service.py:953 +msgid "正在检查 openEuler Intelligence 后端服务状态..." +msgstr "Checking openEuler Intelligence backend service status..." + +#: src/app/deployment/service.py:964 +msgid "✗ openEuler Intelligence 服务检查失败" +msgstr "✗ openEuler Intelligence service check failed" + +#: src/app/deployment/service.py:967 +msgid "✓ openEuler Intelligence 服务检查通过,开始初始化 Agent..." +msgstr "" +"✓ openEuler Intelligence service check passed, starting Agent " +"initialization..." + +#: src/app/deployment/service.py:977 +msgid "✓ Agent 初始化完成" +msgstr "✓ Agent initialization completed" + +#: src/app/deployment/service.py:981 +msgid "⚠ Agent 初始化已跳过(RPM 包不可用),但部署将继续进行" +msgstr "" +"⚠ Agent initialization skipped (RPM package unavailable), but deployment " +"will continue" + +#: src/app/deployment/service.py:1020 +msgid "✓ 全局配置模板创建成功,其他用户可正常使用" +msgstr "" +"✓ Global configuration template created successfully, other users can use it " +"normally" + +#: src/app/deployment/service.py:1023 +msgid "⚠ 全局配置模板创建失败,可能影响其他用户使用" +msgstr "" +"⚠ Failed to create global configuration template, may affect other users" + +#: src/app/deployment/service.py:1028 +msgid "⚠ 配置模板创建异常,可能影响其他用户使用" +msgstr "⚠ Configuration template creation exception, may affect other users" + +#: src/app/deployment/models.py:121 src/app/deployment/models.py:179 +msgid "LLM API 端点不能为空" +msgstr "LLM API endpoint cannot be empty" + +#: src/app/deployment/models.py:150 src/app/deployment/models.py:199 +#: src/app/deployment/models.py:202 +msgid "Embedding API 端点不能为空" +msgstr "Embedding API endpoint cannot be empty" + +#: src/app/deployment/models.py:172 +msgid "服务器 IP 地址不能为空" +msgstr "Server IP address cannot be empty" + +#: src/app/deployment/models.py:210 +msgid "LLM max_tokens 必须大于 0" +msgstr "LLM max_tokens must be greater than 0" + +#: src/app/deployment/models.py:213 +#, python-brace-format +msgid "LLM temperature 必须在 {min} 到 {max} 之间" +msgstr "LLM temperature must be between {min} and {max}" + +#: src/app/deployment/models.py:219 +msgid "LLM 请求超时时间必须大于 0" +msgstr "LLM request timeout must be greater than 0" + +#: src/app/deployment/ui.py:141 src/app/deployment/ui.py:199 +msgid "基础配置" +msgstr "Basic configuration" + +#: src/app/deployment/ui.py:144 +msgid "LLM 配置" +msgstr "LLM configuration" + +#: src/app/deployment/ui.py:147 +msgid "Embedding 配置" +msgstr "Embedding configuration" + +#: src/app/deployment/ui.py:151 +msgid "开始部署" +msgstr "Start deployment" + +#: src/app/deployment/ui.py:152 src/app/settings.py:65 +#: src/app/dialogs/user.py:180 src/app/dialogs/common.py:27 +msgid "取消" +msgstr "Cancel" + +#: src/app/deployment/ui.py:169 +msgid "全量部署" +msgstr "Full deployment" + +#: src/app/deployment/ui.py:170 +msgid "全量部署:部署框架服务 + Web 界面 + RAG 组件,自动初始化 Agent。" +msgstr "" +"Full deployment: Deploy framework service + Web interface + RAG components, " +"automatically initialize Agent." + +#: src/app/deployment/ui.py:172 src/app/deployment/ui.py:213 +msgid "轻量部署" +msgstr "Lightweight deployment" + +#: src/app/deployment/ui.py:173 src/app/deployment/ui.py:218 +msgid "轻量部署:仅部署框架服务,自动初始化 Agent。" +msgstr "" +"Lightweight deployment: Deploy only framework service, automatically " +"initialize Agent." + +#: src/app/deployment/ui.py:189 +msgid "[dim]不需要验证[/dim]" +msgstr "[dim]No validation required[/dim]" + +#: src/app/deployment/ui.py:202 +msgid "服务器 IP 地址:" +msgstr "Server IP address:" + +#: src/app/deployment/ui.py:205 +msgid "例如:127.0.0.1" +msgstr "For example: 127.0.0.1" + +#: src/app/deployment/ui.py:211 +msgid "部署模式:" +msgstr "Deployment mode:" + +#: src/app/deployment/ui.py:226 +msgid "大语言模型配置" +msgstr "LLM configuration" + +#: src/app/deployment/ui.py:229 src/app/deployment/ui.py:294 +msgid "API 端点:" +msgstr "API endpoint:" + +#: src/app/deployment/ui.py:231 src/app/deployment/ui.py:296 +msgid "例如:http://localhost:11434/v1" +msgstr "For example: http://localhost:11434/v1" + +#: src/app/deployment/ui.py:237 src/app/deployment/ui.py:302 +msgid "API 密钥:" +msgstr "API key:" + +#: src/app/deployment/ui.py:246 src/app/deployment/ui.py:311 +msgid "模型名称:" +msgstr "Model name:" + +#: src/app/deployment/ui.py:248 +msgid "例如:deepseek-llm-7b-chat" +msgstr "For example: deepseek-llm-7b-chat" + +#: src/app/deployment/ui.py:254 src/app/deployment/ui.py:319 +msgid "验证状态:" +msgstr "Validation status:" + +#: src/app/deployment/ui.py:255 src/app/deployment/ui.py:320 +#: src/app/deployment/components/modes.py:204 +msgid "未验证" +msgstr "Not validated" + +#: src/app/deployment/ui.py:258 +msgid "最大输出令牌数:" +msgstr "Max output tokens:" + +#: src/app/deployment/ui.py:266 +msgid "Temperature:" +msgstr "Temperature:" + +#: src/app/deployment/ui.py:274 +msgid "请求超时 (秒):" +msgstr "Request timeout (seconds):" + +#: src/app/deployment/ui.py:284 +msgid "嵌入模型配置" +msgstr "Embedding model configuration" + +#: src/app/deployment/ui.py:288 +msgid "[dim]轻量部署模式下,Embedding 配置为可选项。[/dim]" +msgstr "" +"[dim]In lightweight deployment mode, Embedding configuration is optional.[/" +"dim]" + +#: src/app/deployment/ui.py:313 +msgid "例如:bge-m3" +msgstr "For example: bge-m3" + +#: src/app/deployment/ui.py:330 +msgid "配置验证失败" +msgstr "Configuration validation failed" + +#: src/app/deployment/ui.py:369 +msgid "" +"[dim]轻量部署模式下,Embedding 配置为可选项。如果不填写,将跳过 RAG 功能。[/" +"dim]" +msgstr "" +"[dim]In lightweight deployment mode, Embedding configuration is optional. If " +"not filled, RAG features will be skipped.[/dim]" + +#: src/app/deployment/ui.py:373 +msgid "[dim]全量部署模式下,Embedding 配置为必填项,用于支持 RAG 功能。[/dim]" +msgstr "" +"[dim]In full deployment mode, Embedding configuration is required to support " +"RAG features.[/dim]" + +#: src/app/deployment/ui.py:518 src/app/deployment/components/modes.py:390 +#, python-brace-format +msgid "[green]✓ {message}[/green]" +msgstr "[green]✓ {message}[/green]" + +#: src/app/deployment/ui.py:521 +msgid "[red]✗ 不支持工具调用[/red]" +msgstr "[red]✗ Tool calling not supported[/red]" + +#: src/app/deployment/ui.py:523 +msgid "" +"LLM 验证失败:模型不支持工具调用功能,无法用于部署。请选择支持工具调用的模" +"型。" +msgstr "" +"LLM validation failed: Model does not support tool calling feature and " +"cannot be used for deployment. Please select a model that supports tool " +"calling." + +#: src/app/deployment/ui.py:528 src/app/deployment/ui.py:562 +#: src/app/deployment/components/modes.py:394 +#, python-brace-format +msgid "[red]✗ {message}[/red]" +msgstr "[red]✗ {message}[/red]" + +#: src/app/deployment/ui.py:532 src/app/deployment/ui.py:566 +#: src/app/deployment/components/modes.py:399 +#, python-brace-format +msgid "[red]✗ 验证异常: {error}[/red]" +msgstr "[red]✗ Validation exception: {error}[/red]" + +#: src/app/deployment/ui.py:556 +#, python-brace-format +msgid "[green]✓ {message} (维度: {dimension})[/green]" +msgstr "[green]✓ {message} (dimension: {dimension})[/green]" + +#: src/app/deployment/ui.py:710 +msgid "部署进度:" +msgstr "Deployment progress:" + +#: src/app/deployment/ui.py:711 +msgid "准备开始部署..." +msgstr "Ready to start deployment..." + +#: src/app/deployment/ui.py:717 +msgid "完成" +msgstr "Complete" + +#: src/app/deployment/ui.py:718 +msgid "重试" +msgstr "Retry" + +#: src/app/deployment/ui.py:719 +msgid "重新配置" +msgstr "Reconfigure" + +#: src/app/deployment/ui.py:720 +msgid "取消部署" +msgstr "Cancel deployment" + +#: src/app/deployment/ui.py:755 src/app/deployment/ui.py:836 +msgid "部署已取消" +msgstr "Deployment cancelled" + +#: src/app/deployment/ui.py:756 +msgid "部署已被用户取消" +msgstr "Deployment was cancelled by user" + +#: src/app/deployment/ui.py:819 +msgid "部署启动失败" +msgstr "Deployment startup failed" + +#: src/app/deployment/ui.py:820 +#, python-brace-format +msgid "部署启动失败: {error}" +msgstr "Deployment startup failed: {error}" + +#: src/app/deployment/ui.py:837 +msgid "部署被取消" +msgstr "Deployment was cancelled" + +#: src/app/deployment/ui.py:840 src/app/deployment/ui.py:884 +msgid "部署异常" +msgstr "Deployment exception" + +#: src/app/deployment/ui.py:841 +#, python-brace-format +msgid "部署异常: {error}" +msgstr "Deployment exception: {error}" + +#: src/app/deployment/ui.py:848 +msgid "正在检查部署环境..." +msgstr "Checking deployment environment..." + +#: src/app/deployment/ui.py:852 +msgid "环境检查失败" +msgstr "Environment check failed" + +#: src/app/deployment/ui.py:860 +msgid "正在执行部署..." +msgstr "Executing deployment..." + +#: src/app/deployment/ui.py:867 +msgid "部署完成!" +msgstr "Deployment completed!" + +#: src/app/deployment/ui.py:869 +msgid "[bold green]部署成功完成![/bold green]" +msgstr "[bold green]Deployment completed successfully![/bold green]" + +#: src/app/deployment/ui.py:872 +msgid "部署成功完成!" +msgstr "Deployment completed successfully!" + +#: src/app/deployment/ui.py:874 +msgid "部署失败" +msgstr "Deployment failed" + +#: src/app/deployment/ui.py:876 +msgid "[bold red]部署失败,请查看上面的错误信息[/bold red]" +msgstr "" +"[bold red]Deployment failed, please check error messages above[/bold red]" + +#: src/app/deployment/ui.py:878 +msgid "部署执行失败" +msgstr "Deployment execution failed" + +#: src/app/deployment/ui.py:880 +msgid "部署失败,可以重试或重新配置参数" +msgstr "Deployment failed, you can retry or reconfigure parameters" + +#: src/app/deployment/ui.py:883 +#, python-brace-format +msgid "部署过程中发生异常: {error}" +msgstr "Exception occurred during deployment: {error}" + +#: src/app/deployment/ui.py:901 +#, python-brace-format +msgid "步骤 {current}/{total}: {name}" +msgstr "Step {current}/{total}: {name}" + +#: src/app/deployment/ui.py:974 +msgid "错误" +msgstr "Error" + +#: src/app/deployment/ui.py:980 +msgid "确定" +msgstr "Confirm" + +#: src/app/deployment/components/modes.py:58 +#: src/app/deployment/components/modes.py:92 +#: src/app/deployment/components/modes.py:158 +#: src/app/deployment/components/modes.py:230 +#: src/app/deployment/components/env_check.py:105 +#: src/tool/oi_select_agent.py:29 +msgid "退出" +msgstr "Exit" + +#: src/app/deployment/components/modes.py:68 +msgid "openEuler Intelligence 初始化" +msgstr "openEuler Intelligence initialization" + +#: src/app/deployment/components/modes.py:70 +msgid "请选择您的初始化方式:" +msgstr "Please select your initialization method:" + +#: src/app/deployment/components/modes.py:77 +msgid "" +"连接现有服务\n" +"\n" +"输入现有服务的 URL 和 Token 即可连接使用" +msgstr "" +"Connect to existing service\n" +"\n" +"Enter the URL and Token of an existing service to connect" + +#: src/app/deployment/components/modes.py:85 +msgid "" +"部署新服务\n" +"\n" +"在本机部署全新的服务环境和配置" +msgstr "" +"Deploy new service\n" +"\n" +"Deploy a new service environment and configuration on this machine" + +#: src/app/deployment/components/modes.py:157 +#: src/app/deployment/components/modes.py:229 +#: src/app/deployment/components/env_check.py:104 +msgid "返回" +msgstr "Back" + +#: src/app/deployment/components/modes.py:174 +msgid "连接现有 openEuler Intelligence 服务" +msgstr "Connect to existing openEuler Intelligence service" + +#: src/app/deployment/components/modes.py:176 +msgid "请输入您的 openEuler Intelligence 服务连接信息:" +msgstr "" +"Please enter your openEuler Intelligence service connection information:" + +#: src/app/deployment/components/modes.py:181 +msgid "服务 URL:" +msgstr "Service URL:" + +#: src/app/deployment/components/modes.py:183 +msgid "例如:http://your-server:8002" +msgstr "For example: http://your-server:8002" + +#: src/app/deployment/components/modes.py:189 +msgid "访问令牌:" +msgstr "Access token:" + +#: src/app/deployment/components/modes.py:191 +msgid "可选,您的访问令牌" +msgstr "Optional, your access token" + +#: src/app/deployment/components/modes.py:197 +msgid "获取" +msgstr "Get" + +#: src/app/deployment/components/modes.py:209 +msgid "" +"提示:\n" +"• 服务 URL 通常以 http:// 或 https:// 开头\n" +"• 访问令牌为可选项,如果服务无需认证可留空\n" +"• 输入服务 URL 后,可点击 '获取' 按钮通过浏览器获取访问令牌\n" +"• 也可以从 openEuler Intelligence Web 界面手动获取并填入\n" +"• 系统会自动验证连接并保存配置" +msgstr "" +"Tips:\n" +"• Service URL usually starts with http:// or https://\n" +"• Access token is optional, can be left empty if service requires no " +"authentication\n" +"• After entering service URL, click 'Get' button to obtain access token " +"through browser\n" +"• You can also manually obtain from openEuler Intelligence Web interface and " +"fill in\n" +"• System will automatically validate connection and save configuration" + +#: src/app/deployment/components/modes.py:218 +msgid "" +"提示:\n" +"• 服务 URL 通常以 http:// 或 https:// 开头\n" +"• 访问令牌为可选项,如果服务无需认证可留空\n" +"• [yellow]当前环境不支持浏览器,请从 Web 界面手动获取访问令牌[/yellow]\n" +"• 系统会自动验证连接并保存配置" +msgstr "" +"Tips:\n" +"• Service URL usually starts with http:// or https://\n" +"• Access token is optional, can be left empty if service requires no " +"authentication\n" +"• [yellow]Browser is not available in current environment, please manually " +"obtain access token from web interface[/yellow]\n" +"• System will automatically validate connection and save configuration" + +#: src/app/deployment/components/modes.py:228 +msgid "连接并保存" +msgstr "Connect and save" + +#: src/app/deployment/components/modes.py:256 +msgid "当前环境不支持打开浏览器,请手动从 Web 界面获取访问令牌" +msgstr "Browser is not available in current environment, please manually obtain access token from web interface" + +#: src/app/deployment/components/modes.py:264 +msgid "请先输入服务 URL" +msgstr "Please enter service URL first" + +#: src/app/deployment/components/modes.py:273 +msgid "[yellow]正在获取授权 URL...[/yellow]" +msgstr "[yellow]Obtaining authorization URL...[/yellow]" + +#: src/app/deployment/components/modes.py:278 +msgid "[red]✗ 获取授权 URL 失败[/red]" +msgstr "[red]✗ Failed to obtain authorization URL[/red]" + +#: src/app/deployment/components/modes.py:287 +msgid "[yellow]正在打开浏览器登录...[/yellow]" +msgstr "[yellow]Opening browser for login...[/yellow]" + +#: src/app/deployment/components/modes.py:289 +msgid "已打开浏览器,请完成登录" +msgstr "Browser opened, please complete login" + +#: src/app/deployment/components/modes.py:296 +#, python-brace-format +msgid "获取 API Key 失败: {error}" +msgstr "Failed to obtain API Key: {error}" + +#: src/app/deployment/components/modes.py:307 +msgid "请等待连接验证完成" +msgstr "Please wait for connection validation to complete" + +#: src/app/deployment/components/modes.py:319 +msgid "配置已保存,初始化完成!" +msgstr "Configuration saved, initialization completed!" + +#: src/app/deployment/components/modes.py:323 +#, python-brace-format +msgid "保存配置时发生错误: {error}" +msgstr "Error occurred while saving configuration: {error}" + +#: src/app/deployment/components/modes.py:377 +msgid "[yellow]验证连接中...[/yellow]" +msgstr "[yellow]Validating connection...[/yellow]" + +#: src/app/deployment/components/modes.py:415 +msgid "[yellow]等待登录完成...[/yellow]" +msgstr "[yellow]Waiting for login to complete...[/yellow]" + +#: src/app/deployment/components/modes.py:431 +msgid "[green]✓ 登录成功,已获取 API Key[/green]" +msgstr "[green]✓ Login successful, API Key obtained[/green]" + +#: src/app/deployment/components/modes.py:437 +msgid "[red]✗ 登录失败:未收到 session ID[/red]" +msgstr "[red]✗ Login failed: session ID not received[/red]" + +#: src/app/deployment/components/modes.py:442 +#, python-brace-format +msgid "[red]✗ 登录失败: {error}[/red]" +msgstr "[red]✗ Login failed: {error}[/red]" + +#: src/app/deployment/components/modes.py:446 +msgid "[red]✗ 登录失败:未知结果[/red]" +msgstr "[red]✗ Login failed: unknown result[/red]" + +#: src/app/deployment/components/modes.py:451 +msgid "[yellow]登录已取消[/yellow]" +msgstr "[yellow]Login cancelled[/yellow]" + +#: src/app/deployment/components/modes.py:455 +#, python-brace-format +msgid "[red]✗ 登录异常: {error}[/red]" +msgstr "[red]✗ Login exception: {error}[/red]" + +#: src/app/deployment/components/env_check.py:92 +msgid "环境检查" +msgstr "Environment check" + +#: src/app/deployment/components/env_check.py:96 +msgid "检查操作系统类型..." +msgstr "Checking operating system type..." + +#: src/app/deployment/components/env_check.py:100 +msgid "检查管理员权限..." +msgstr "Checking administrator privileges..." + +#: src/app/deployment/components/env_check.py:103 +msgid "继续配置" +msgstr "Continue configuration" + +#: src/app/deployment/components/env_check.py:124 +#, python-brace-format +msgid "环境检查过程中发生异常: {error}" +msgstr "Exception occurred during environment check: {error}" + +#: src/app/deployment/components/env_check.py:137 +msgid "操作系统: openEuler (支持)" +msgstr "Operating System: openEuler (Supported)" + +#: src/app/deployment/components/env_check.py:140 +msgid "操作系统: 非 openEuler (不支持)" +msgstr "Operating System: Non-openEuler (Not supported)" + +#: src/app/deployment/components/env_check.py:146 +#, python-brace-format +msgid "操作系统检查失败: {error}" +msgstr "Operating system check failed: {error}" + +#: src/app/deployment/components/env_check.py:147 +#, python-brace-format +msgid "操作系统检查异常: {error}" +msgstr "Operating system check exception: {error}" + +#: src/app/deployment/components/env_check.py:160 +msgid "管理员权限: 可用" +msgstr "Administrator privileges: Available" + +#: src/app/deployment/components/env_check.py:163 +msgid "管理员权限: 不可用 (需要 sudo)" +msgstr "Administrator privileges: Unavailable (sudo required)" + +#: src/app/deployment/components/env_check.py:169 +#, python-brace-format +msgid "权限检查失败: {error}" +msgstr "Permission check failed: {error}" + +#: src/app/deployment/components/env_check.py:170 +#, python-brace-format +msgid "权限检查异常: {error}" +msgstr "Permission check exception: {error}" + +#: src/app/deployment/agent.py:409 +msgid "[bold blue]开始初始化智能体...[/bold blue]" +msgstr "[bold blue]Starting agent initialization...[/bold blue]" + +#: src/app/deployment/agent.py:416 +msgid "智能体初始化失败" +msgstr "Agent initialization failed" + +#: src/app/deployment/agent.py:454 +#, python-brace-format +msgid "[bold green]智能体初始化完成! 默认 App ID: {app_id}[/bold green]" +msgstr "" +"[bold green]Agent initialization completed! Default App ID: {app_id}[/bold " +"green]" + +#: src/app/deployment/agent.py:461 +msgid "[yellow]未能创建任何智能体[/yellow]" +msgstr "[yellow]Failed to create any agent[/yellow]" + +#: src/app/deployment/agent.py:491 +#, python-brace-format +msgid "[yellow]服务配置目录不存在: {dir},跳过{operation}[/yellow]" +msgstr "" +"[yellow]Service configuration directory does not exist: {dir}, skipping " +"{operation}[/yellow]" + +#: src/app/deployment/agent.py:505 +#, python-brace-format +msgid "[yellow]未找到服务配置文件,跳过{operation}[/yellow]" +msgstr "" +"[yellow]Service configuration file not found, skipping {operation}[/yellow]" + +#: src/app/deployment/agent.py:549 +#, python-brace-format +msgid " [red]处理 {file} 时发生异常[/red]" +msgstr " [red]Exception occurred while processing {file}[/red]" + +#: src/app/deployment/agent.py:563 +msgid "[cyan]安装 systemd 服务文件...[/cyan]" +msgstr "[cyan]Installing systemd service files...[/cyan]" + +#: src/app/deployment/agent.py:566 +msgid "服务文件安装" +msgstr "Service file installation" + +#: src/app/deployment/agent.py:585 +#, python-brace-format +msgid "[green]成功安装 {count} 个服务文件[/green]" +msgstr "[green]Successfully installed {count} service files[/green]" + +#: src/app/deployment/agent.py:604 +#, python-brace-format +msgid " [blue]复制服务文件: {name}[/blue]" +msgstr " [blue]Copying service file: {name}[/blue]" + +#: src/app/deployment/agent.py:622 +#, python-brace-format +msgid " [red]复制 {name} 时发生异常[/red]" +msgstr " [red]Exception occurred while copying {name}[/red]" + +#: src/app/deployment/agent.py:631 +#, python-brace-format +msgid " [green]{name} 复制成功[/green]" +msgstr " [green]{name} copied successfully[/green]" + +#: src/app/deployment/agent.py:640 +#, python-brace-format +msgid " [red]{name} 复制失败: {error}[/red]" +msgstr " [red]{name} copy failed: {error}[/red]" + +#: src/app/deployment/agent.py:652 +msgid "[cyan]重新加载 systemd 配置...[/cyan]" +msgstr "[cyan]Reloading systemd configuration...[/cyan]" + +#: src/app/deployment/agent.py:667 +msgid "[red]重新加载 systemd 配置时发生异常[/red]" +msgstr "[red]Exception occurred while reloading systemd configuration[/red]" + +#: src/app/deployment/agent.py:676 +msgid "[green]systemd 配置重新加载成功[/green]" +msgstr "[green]systemd configuration reloaded successfully[/green]" + +#: src/app/deployment/agent.py:685 +#, python-brace-format +msgid "[red]systemd 配置重新加载失败: {error}[/red]" +msgstr "[red]systemd configuration reload failed: {error}[/red]" + +#: src/app/deployment/agent.py:697 +msgid "[cyan]启动 MCP Server 进程...[/cyan]" +msgstr "[cyan]Starting MCP Server process...[/cyan]" + +#: src/app/deployment/agent.py:702 +#, python-brace-format +msgid "[red]MCP 启动脚本不存在: {path}[/red]" +msgstr "[red]MCP startup script does not exist: {path}[/red]" -#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:286 -msgid "低风险" -msgstr "Low risk" +#: src/app/deployment/agent.py:713 +msgid "[yellow]清理旧进程时遇到问题,但继续执行启动脚本[/yellow]" +msgstr "" +"[yellow]Problem encountered while cleaning old processes, but continuing " +"with startup script[/yellow]" -#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:287 -msgid "中等风险" -msgstr "Medium risk" +#: src/app/deployment/agent.py:721 +#, python-brace-format +msgid " [blue]执行命令: {cmd}[/blue]" +msgstr " [blue]Executing command: {cmd}[/blue]" -#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:288 -msgid "高风险" -msgstr "High risk" +#: src/app/deployment/agent.py:736 +msgid "[green]MCP Server 启动脚本执行成功[/green]" +msgstr "[green]MCP Server startup script executed successfully[/green]" -#: src/app/mcp_widgets.py:54 src/backend/hermes/mcp_helpers.py:289 -msgid "未知风险" -msgstr "Unknown risk" +#: src/app/deployment/agent.py:743 +msgid "执行 MCP Server 启动脚本失败" +msgstr "Failed to execute MCP Server startup script" -#: src/app/mcp_widgets.py:81 -msgid "✓ 确认" -msgstr "✓ Confirm" +#: src/app/deployment/agent.py:750 +#, python-brace-format +msgid "[red]MCP Server 启动脚本执行失败 (返回码: {code})[/red]" +msgstr "" +"[red]MCP Server startup script execution failed (return code: {code})[/red]" -#: src/app/mcp_widgets.py:82 src/app/mcp_widgets.py:201 -msgid "✗ 取消" -msgstr "✗ Cancel" +#: src/app/deployment/agent.py:808 +msgid "[cyan]验证 MCP Server 服务状态...[/cyan]" +msgstr "[cyan]Validating MCP Server service status...[/cyan]" -#: src/app/mcp_widgets.py:164 -msgid "需要补充参数" -msgstr "Parameters required" +#: src/app/deployment/agent.py:811 +msgid "服务验证" +msgstr "Service validation" -#: src/app/mcp_widgets.py:169 -msgid "📝 参数输入" -msgstr "📝 Parameter Input" +#: src/app/deployment/agent.py:826 +#, python-brace-format +msgid "[red]关键服务状态异常: {services},停止初始化[/red]" +msgstr "" +"[red]Critical service status abnormal: {services}, stopping initialization[/" +"red]" -#: src/app/mcp_widgets.py:181 +#: src/app/deployment/agent.py:832 +msgid "[green]MCP Server 服务验证完成[/green]" +msgstr "[green]MCP Server service validation completed[/green]" + +#: src/app/deployment/agent.py:852 #, python-brace-format -msgid "请输入 {param_name}" -msgstr "Please enter {param_name}" +msgid " [red]{name} 启动超时 (30秒)[/red]" +msgstr " [red]{name} startup timed out (30 seconds)[/red]" -#: src/app/mcp_widgets.py:191 -msgid "补充说明(可选)" -msgstr "Additional notes (optional)" +#: src/app/deployment/agent.py:861 +#, python-brace-format +msgid " [magenta]检查服务状态: {name}[/magenta]" +msgstr " [magenta]Checking service status: {name}[/magenta]" -#: src/app/mcp_widgets.py:200 -msgid "✓ 提交" -msgstr "✓ Submit" +#: src/app/deployment/agent.py:867 +#, python-brace-format +msgid " [dim]{name} 重新检查状态... (第 {count} 次)[/dim]" +msgstr " [dim]{name} rechecking status... (attempt {count})[/dim]" + +#: src/app/deployment/agent.py:889 +#, python-brace-format +msgid " [red]检查 {name} 状态失败[/red]" +msgstr " [red]Failed to check {name} status[/red]" + +#: src/app/deployment/agent.py:900 +#, python-brace-format +msgid " [green]{service_name} 状态正常 (active running)[/green]" +msgstr " [green]{service_name} status normal (active running)[/green]" + +#: src/app/deployment/agent.py:910 +#, python-brace-format +msgid " [red]{service_name} 服务启动失败[/red]" +msgstr " [red]{service_name} service startup failed[/red]" + +#: src/app/deployment/agent.py:921 +#, python-brace-format +msgid " [yellow]{service_name} 正在启动中,等待启动完成...[/yellow]" +msgstr "" +" [yellow]{service_name} is starting, waiting for completion...[/yellow]" + +#: src/app/deployment/agent.py:935 +#, python-brace-format +msgid " [red]{service_name} 状态异常 (返回码: {returncode})[/red]" +msgstr "" +" [red]{service_name} status abnormal (return code: {returncode})[/red]" + +#: src/app/deployment/agent.py:956 +msgid "[cyan]注册 MCP 服务...[/cyan]" +msgstr "[cyan]Registering MCP services...[/cyan]" + +#: src/app/deployment/agent.py:973 +#, python-brace-format +msgid " [green]{name} 注册成功: {mcp_path} -> {service_id}[/green]" +msgstr "" +" [green]{name} registered successfully: {mcp_path} -> {service_id}[/green]" + +#: src/app/deployment/agent.py:983 +#, python-brace-format +msgid " [red]MCP 服务 {name} 注册失败[/red]" +msgstr " [red]MCP service {name} registration failed[/red]" + +#: src/app/deployment/agent.py:989 +#, python-brace-format +msgid "[green]MCP 服务注册完成,成功 {count} 个[/green]" +msgstr "[green]MCP service registration completed, {count} successful[/green]" + +#: src/app/deployment/agent.py:1001 +msgid "[cyan]读取应用配置并创建智能体...[/cyan]" +msgstr "[cyan]Reading application configuration and creating agents...[/cyan]" + +#: src/app/deployment/agent.py:1027 +#, python-brace-format +msgid " [dim]设置默认智能体: {name}[/dim]" +msgstr " [dim]Setting default agent: {name}[/dim]" + +#: src/app/deployment/agent.py:1035 +#, python-brace-format +msgid "[green]成功创建 {count} 个智能体[/green]" +msgstr "[green]Successfully created {count} agents[/green]" + +#: src/app/deployment/agent.py:1040 +msgid "[red]未能创建任何智能体[/red]" +msgstr "[red]Failed to create any agents[/red]" + +#: src/app/deployment/agent.py:1053 +#, python-brace-format +msgid "[magenta]创建智能体: {name}[/magenta]" +msgstr "[magenta]Creating agent: {name}[/magenta]" + +#: src/app/deployment/agent.py:1066 +#, python-brace-format +msgid " [yellow]缺少 MCP 服务: {services},跳过[/yellow]" +msgstr " [yellow]Missing MCP services: {services}, skipping[/yellow]" + +#: src/app/deployment/agent.py:1075 +#, python-brace-format +msgid " [yellow]智能体 {name} 没有可用的 MCP 服务,跳过[/yellow]" +msgstr "" +" [yellow]Agent {name} has no available MCP services, skipping[/yellow]" + +#: src/app/deployment/agent.py:1095 +#, python-brace-format +msgid " [red]创建智能体 {name} 失败[/red]" +msgstr " [red]Failed to create agent {name}[/red]" -#: src/app/settings.py:63 +#: src/app/deployment/agent.py:1103 +#, python-brace-format +msgid " [green]智能体 {name} 创建成功: {app_id}[/green]" +msgstr " [green]Agent {name} created successfully: {app_id}[/green]" + +#: src/app/deployment/agent.py:1134 +msgid "[cyan]加载应用配置文件...[/cyan]" +msgstr "[cyan]Loading application configuration file...[/cyan]" + +#: src/app/deployment/agent.py:1139 +#, python-brace-format +msgid "[red]应用配置文件不存在: {path}[/red]" +msgstr "[red]Application configuration file does not exist: {path}[/red]" + +#: src/app/deployment/agent.py:1153 +msgid "[yellow]配置文件中没有找到应用定义[/yellow]" +msgstr "" +"[yellow]No application definitions found in configuration file[/yellow]" + +#: src/app/deployment/agent.py:1173 +#, python-brace-format +msgid " [red]应用配置缺少必需字段: {field}[/red]" +msgstr " [red]Application configuration missing required field: {field}[/red]" + +#: src/app/deployment/agent.py:1180 +#, python-brace-format +msgid "加载应用配置文件失败: {path}" +msgstr "Failed to load application configuration file: {path}" + +#: src/app/deployment/agent.py:1187 +#, python-brace-format +msgid "[green]成功加载 {count} 个应用配置[/green]" +msgstr "[green]Successfully loaded {count} application configurations[/green]" + +#: src/app/deployment/agent.py:1198 +msgid "[cyan]加载 MCP 配置文件...[/cyan]" +msgstr "[cyan]Loading MCP configuration file...[/cyan]" + +#: src/app/deployment/agent.py:1204 +msgid "[yellow]未找到 MCP 配置文件[/yellow]" +msgstr "[yellow]MCP configuration file not found[/yellow]" + +#: src/app/deployment/agent.py:1209 +#, python-brace-format +msgid "[green]成功加载 {count} 个 MCP 配置[/green]" +msgstr "[green]Successfully loaded {count} MCP configurations[/green]" + +#: src/app/deployment/agent.py:1227 +#, python-brace-format +msgid " [red]MCP 服务 {name} SSE Endpoint 验证失败[/red]" +msgstr " [red]MCP service {name} SSE Endpoint validation failed[/red]" + +#: src/app/deployment/agent.py:1245 +#, python-brace-format +msgid " [red]{name} 处理失败: {error}[/red]" +msgstr " [red]{name} processing failed: {error}[/red]" + +#: src/app/deployment/agent.py:1260 +#, python-brace-format +msgid " [blue]注册 {name}...[/blue]" +msgstr " [blue]Registering {name}...[/blue]" + +#: src/app/deployment/agent.py:1273 +#, python-brace-format +msgid " [cyan]安装 {name} (ID: {service_id})...[/cyan]" +msgstr " [cyan]Installing {name} (ID: {service_id})...[/cyan]" + +#: src/app/deployment/agent.py:1278 +#, python-brace-format +msgid " [dim]等待 {name} 安装完成...[/dim]" +msgstr " [dim]Waiting for {name} installation to complete...[/dim]" + +#: src/app/deployment/agent.py:1280 +#, python-brace-format +msgid " [red]{name} 安装超时[/red]" +msgstr " [red]{name} installation timed out[/red]" + +#: src/app/deployment/agent.py:1293 +#, python-brace-format +msgid " [yellow]激活 {name}...[/yellow]" +msgstr " [yellow]Activating {name}...[/yellow]" + +#: src/app/deployment/agent.py:1295 +#, python-brace-format +msgid " [green]{name} 处理完成[/green]" +msgstr " [green]{name} processing completed[/green]" + +#: src/app/deployment/agent.py:1307 +#, python-brace-format +msgid "[magenta]验证 SSE Endpoint: {name} -> {url}[/magenta]" +msgstr "[magenta]Validating SSE Endpoint: {name} -> {url}[/magenta]" + +#: src/app/deployment/agent.py:1320 src/app/deployment/agent.py:1330 +#, python-brace-format +msgid " [green]{name} SSE Endpoint 验证通过[/green]" +msgstr " [green]{name} SSE Endpoint validation passed[/green]" + +#: src/app/deployment/agent.py:1343 +#, python-brace-format +msgid " [red]{name} SSE Endpoint 验证失败: 30秒内无法连接[/red]" +msgstr "" +" [red]{name} SSE Endpoint validation failed: unable to connect within 30 " +"seconds[/red]" + +#: src/app/settings.py:57 msgid "设置" msgstr "Settings" -#: src/app/settings.py:66 +#: src/app/settings.py:64 src/app/dialogs/user.py:179 +msgid "保存" +msgstr "Save" + +#: src/app/settings.py:174 msgid "后端:" msgstr "Backend:" -#: src/app/settings.py:76 +#: src/app/settings.py:184 msgid "Base URL:" msgstr "Base URL:" -#: src/app/settings.py:88 +#: src/app/settings.py:196 msgid "API Key:" msgstr "API Key:" -#: src/app/settings.py:95 +#: src/app/settings.py:203 msgid "API 访问密钥,可选" msgstr "API access key, optional" -#: src/app/settings.py:103 src/app/settings.py:239 +#: src/app/settings.py:213 msgid "模型:" msgstr "Model:" -#: src/app/settings.py:108 src/app/settings.py:244 +#: src/app/settings.py:218 msgid "模型名称,可选" msgstr "Model name, optional" -#: src/app/settings.py:117 src/app/settings.py:272 -msgid "MCP 工具授权:" -msgstr "MCP Tool Authorization:" - -#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:274 -#: src/app/settings.py:549 src/app/settings.py:555 -msgid "自动执行" -msgstr "Auto Execute" - -#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:181 -#: src/app/settings.py:274 src/app/settings.py:549 src/app/settings.py:555 -msgid "手动确认" -msgstr "Manual Confirm" - -#: src/app/settings.py:133 -msgid "保存" -msgstr "Save" +#: src/app/settings.py:228 +msgid "用户设置:" +msgstr "User Settings:" -#: src/app/settings.py:134 src/app/dialogs/common.py:27 -msgid "取消" -msgstr "Cancel" +#: src/app/settings.py:230 +msgid "更改用户设置" +msgstr "Change User Settings" -#: src/app/settings.py:437 +#: src/app/settings.py:407 msgid "Base URL 不能为空" msgstr "Base URL cannot be empty" -#: src/app/settings.py:534 -msgid "切换中..." -msgstr "Switching..." - -#: src/app/tui.py:217 +#: src/app/tui.py:216 msgid "Enter command or question..." msgstr "Enter question or command..." -#: src/app/tui.py:226 +#: src/app/tui.py:225 msgid "Quit" msgstr "Quit" -#: src/app/tui.py:227 +#: src/app/tui.py:226 msgid "Settings" msgstr "Settings" -#: src/app/tui.py:228 +#: src/app/tui.py:227 msgid "Reset" msgstr "Reset Conversation" -#: src/app/tui.py:229 +#: src/app/tui.py:228 msgid "Agent" msgstr "Select Agent" -#: src/app/tui.py:230 +#: src/app/tui.py:229 msgid "Cancel" msgstr "Cancel" -#: src/app/tui.py:231 +#: src/app/tui.py:230 msgid "Focus" msgstr "Toggle Focus" -#: src/app/tui.py:255 +#: src/app/tui.py:254 #, python-brace-format msgid "Intelligent CLI Assistant {version}" msgstr "Command Line Tool {version}" -#: src/app/tui.py:371 +#: src/app/tui.py:372 msgid "[Cancelled]" msgstr "[Cancelled]" -#: src/app/tui.py:576 +#: src/app/tui.py:588 msgid "" "No response received, please check network connection or try again later" msgstr "" "No response received, please check network connection or try again later" -#: src/app/tui.py:684 +#: src/app/tui.py:696 msgid "Request timeout, processing stopped" msgstr "Request timeout, processing stopped" -#: src/app/tui.py:691 +#: src/app/tui.py:703 msgid "No response for a long time, processing stopped" msgstr "No response for a long time, processing stopped" -#: src/app/tui.py:744 src/app/tui.py:902 +#: src/app/tui.py:756 src/app/tui.py:914 msgid "Request timeout, please try again later" msgstr "Request timeout, please try again later" -#: src/app/tui.py:888 +#: src/app/tui.py:900 #, python-brace-format msgid "Server error: {message}" msgstr "Server error: {message}" -#: src/app/tui.py:890 +#: src/app/tui.py:902 #, python-brace-format msgid "Request failed: {message}" msgstr "Request failed: {message}" -#: src/app/tui.py:894 +#: src/app/tui.py:906 msgid "Network connection interrupted, please check network and try again" msgstr "Network connection interrupted, please check network and try again" -#: src/app/tui.py:906 +#: src/app/tui.py:918 msgid "Network connection error, please check network and try again" msgstr "Network connection error, please check network and try again" -#: src/app/tui.py:915 src/app/tui.py:949 +#: src/app/tui.py:927 src/app/tui.py:961 msgid "Server response error, please try again later" msgstr "Server response error, please try again later" -#: src/app/tui.py:920 +#: src/app/tui.py:932 msgid "Data format error, please try again later" msgstr "Data format error, please try again later" -#: src/app/tui.py:927 +#: src/app/tui.py:939 msgid "Authentication failed, please check configuration" msgstr "Authentication failed, please check configuration" -#: src/app/tui.py:951 +#: src/app/tui.py:963 #, python-brace-format msgid "Error processing command: {error}" msgstr "Processing command failed with error: {error}" -#: src/app/tui.py:1170 +#: src/app/tui.py:1088 src/app/tui.py:1114 src/app/tui.py:1296 +#: src/app/tui.py:1457 src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 +#: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 +#: src/app/dialogs/agent.py:208 src/tool/oi_select_agent.py:67 +#: src/tool/oi_select_agent.py:94 +msgid "智能问答" +msgstr "Smart Chat" + +#: src/app/tui.py:1214 msgid "💡 MCP response sent" msgstr "💡 MCP response sent" -#: src/app/tui.py:1173 src/app/tui.py:1202 +#: src/app/tui.py:1217 src/app/tui.py:1246 msgid "❌ Current client does not support MCP response" msgstr "❌ Current client does not support MCP response" -#: src/app/tui.py:1182 +#: src/app/tui.py:1226 #, python-brace-format msgid "❌ Failed to send MCP response: {error}" msgstr "❌ Failed to send MCP response: {error}" -#: src/app/tui.py:1249 -#, python-brace-format -msgid "⏱️ MCP response timeout ({seconds} seconds)" -msgstr "⏱️ MCP response timeout ({seconds} seconds)" - -#: src/app/tui.py:1253 +#: src/app/tui.py:1285 msgid "🚫 MCP response cancelled" msgstr "🚫 MCP response cancelled" -#: src/app/tui.py:1336 +#: src/app/tui.py:1367 msgid "Backend configuration validation failed, please check and modify" msgstr "Backend configuration validation failed, please check and modify" -#: src/app/tui.py:1337 +#: src/app/tui.py:1368 msgid "Configuration Error" msgstr "Configuration Error" +#: src/app/dialogs/user.py:90 +msgid "常规设置" +msgstr "General Settings" + +#: src/app/dialogs/user.py:91 +msgid "大模型设置" +msgstr "LLM Settings" + +#: src/app/dialogs/user.py:133 +msgid "用户名:" +msgstr "Username:" + +#: src/app/dialogs/user.py:142 +msgid "未登录" +msgstr "Not logged in" + +#: src/app/dialogs/user.py:150 +msgid "管理员" +msgstr "Administrator" + +#: src/app/dialogs/user.py:150 +msgid "普通用户" +msgstr "Regular User" + +#: src/app/dialogs/user.py:164 +msgid "MCP 工具授权:" +msgstr "MCP Tool Authorization:" + +#: src/app/dialogs/user.py:166 src/app/dialogs/user.py:374 +msgid "自动执行" +msgstr "Auto Execute" + +#: src/app/dialogs/user.py:166 src/app/dialogs/user.py:374 +msgid "手动确认" +msgstr "Manual Confirm" + +#: src/app/dialogs/user.py:214 +msgid "加载模型失败" +msgstr "Failed to load models" + +#: src/app/dialogs/user.py:216 +msgid "加载中..." +msgstr "Loading..." + +#: src/app/dialogs/user.py:219 +msgid "暂无可用模型" +msgstr "No models available" + +#: src/app/dialogs/user.py:255 +msgid "↑↓: 选择模型 空格: 激活 回车: 保存 ESC: 取消" +msgstr "↑↓: Select model Space: Activate Enter: Save ESC: Cancel" + +#: src/app/dialogs/user.py:273 +msgid "描述: " +msgstr "Description: " + +#: src/app/dialogs/user.py:284 +msgid "类型: " +msgstr "Type: " + +#: src/app/dialogs/user.py:294 +msgid "最大 Token: " +msgstr "Max Tokens: " + #: src/app/dialogs/common.py:25 msgid "确认退出吗?" msgstr "Are you sure you want to exit?" @@ -264,13 +1596,6 @@ msgstr "Please select openEuler Intelligence backend to use agent features" msgid "按任意键关闭" msgstr "Press any key to close" -#: src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 -#: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 -#: src/app/dialogs/agent.py:208 src/tool/oi_select_agent.py:67 -#: src/tool/oi_select_agent.py:94 -msgid "智能问答" -msgstr "Smart Chat" - #: src/app/dialogs/agent.py:113 msgid "OS 智能助手" msgstr "OS Smart Assistant" @@ -281,6 +1606,34 @@ msgstr "" "Use arrow keys to select, Enter to confirm, ESC to cancel | ✓ indicates " "current selection" +#: src/backend/hermes/client.py:395 +msgid "未配置 Chat 模型" +msgstr "Chat model not configured" + +#: src/backend/hermes/client.py:396 +msgid "配置步骤" +msgstr "Configuration steps" + +#: src/backend/hermes/client.py:397 +msgid "按 Ctrl+S 打开设置" +msgstr "Press Ctrl+S to open settings" + +#: src/backend/hermes/client.py:398 +msgid "确认后端为 openEuler Intelligence" +msgstr "Confirm backend is openEuler Intelligence" + +#: src/backend/hermes/client.py:399 +msgid "点击 \"更改用户设置\" 按钮" +msgstr "Click \"Change User Settings\" button" + +#: src/backend/hermes/client.py:400 +msgid "切换到 \"大模型设置\" 标签页" +msgstr "Switch to \"LLM Settings\" tab" + +#: src/backend/hermes/client.py:401 +msgid "使用 ↑↓ 键选择模型,空格激活,回车保存" +msgstr "Use ↑↓ to select model, space to activate, enter to save" + #: src/backend/hermes/mcp_helpers.py:44 msgid "正在初始化工具" msgstr "Initializing tool" @@ -321,11 +1674,11 @@ msgstr "Name" msgid "说明" msgstr "Description" -#: src/main.py:28 +#: src/main.py:27 msgid "openEuler Intelligence - Intelligent command-line tool" msgstr "openEuler Intelligence - Command-line Client" -#: src/main.py:29 +#: src/main.py:28 msgid "" "\n" "For more information and documentation, please visit:\n" @@ -337,33 +1690,33 @@ msgstr "" " https://gitee.com/openeuler/euler-copilot-shell/tree/master/docs\n" " " -#: src/main.py:39 +#: src/main.py:38 msgid "General Options" msgstr "General Options" -#: src/main.py:40 +#: src/main.py:39 msgid "Show help and version information" msgstr "Show help and version information" -#: src/main.py:46 +#: src/main.py:45 msgid "Show this help message and exit" msgstr "Show this help message and exit" -#: src/main.py:53 +#: src/main.py:52 msgid "Show program version number and exit" msgstr "Show program version number and exit" -#: src/main.py:58 +#: src/main.py:57 msgid "Backend Configuration Options" msgstr "Backend Configuration Options" -#: src/main.py:59 +#: src/main.py:58 msgid "" "For initializing and configuring openEuler Intelligence backend services" msgstr "" "For initializing and configuring openEuler Intelligence backend services" -#: src/main.py:65 +#: src/main.py:64 msgid "" "Initialize openEuler Intelligence backend\n" " * Initialization requires administrator privileges and network connection" @@ -371,7 +1724,7 @@ msgstr "" "Initialize openEuler Intelligence backend\n" " * Initialization requires administrator privileges and network connection" -#: src/main.py:73 +#: src/main.py:72 msgid "" "Change openEuler Intelligence LLM settings (requires valid local backend " "service)\n" @@ -381,105 +1734,113 @@ msgstr "" "service)\n" " * Configuration editing requires administrator privileges" -#: src/main.py:80 +#: src/main.py:79 msgid "Application Configuration Options" msgstr "Application Configuration Options" -#: src/main.py:81 +#: src/main.py:80 msgid "For configuring application frontend behavior and preferences" msgstr "For configuring application frontend behavior and preferences" -#: src/main.py:86 +#: src/main.py:85 msgid "Select default agent" msgstr "Select default agent" +#: src/main.py:90 +msgid "Authentication Management Options" +msgstr "Authentication Management Options" + #: src/main.py:91 +msgid "For managing login and authentication" +msgstr "For managing login and authentication" + +#: src/main.py:96 +msgid "Login via browser to obtain API key" +msgstr "Login via browser to obtain API key" + +#: src/main.py:101 msgid "Language Settings" msgstr "Language Settings" -#: src/main.py:92 +#: src/main.py:102 msgid "For configuring application display language" msgstr "For configuring application display language" -#: src/main.py:100 +#: src/main.py:110 #, python-brace-format msgid "Set display language (available: {locales})" msgstr "Set display language (available: {locales})" -#: src/main.py:105 +#: src/main.py:115 msgid "Log Management Options" msgstr "Log Management Options" -#: src/main.py:106 +#: src/main.py:116 msgid "For viewing and configuring log output" msgstr "For viewing and configuring log output" -#: src/main.py:111 +#: src/main.py:121 msgid "Show latest log content (up to 1000 lines)" msgstr "Show latest log content (up to 1000 lines)" -#: src/main.py:117 +#: src/main.py:127 msgid "Set log level (available: DEBUG, INFO, WARNING, ERROR)" msgstr "Set log level (available: DEBUG, INFO, WARNING, ERROR)" -#: src/main.py:140 +#: src/main.py:150 #, python-brace-format msgid "Failed to retrieve logs: {error}\n" msgstr "Failed to retrieve logs: {error}\n" -#: src/main.py:147 +#: src/main.py:157 #, python-brace-format msgid "Invalid log level: {level}\n" msgstr "Invalid log level: {level}\n" -#: src/main.py:156 +#: src/main.py:166 #, python-format msgid "Log level has been set to: %s" msgstr "Log level has been set to: %s" -#: src/main.py:157 +#: src/main.py:167 msgid "This is a DEBUG level test message" msgstr "This is a DEBUG level test message" -#: src/main.py:158 +#: src/main.py:168 msgid "This is an INFO level test message" msgstr "This is an INFO level test message" -#: src/main.py:159 +#: src/main.py:169 msgid "This is a WARNING level test message" msgstr "This is a WARNING level test message" -#: src/main.py:160 +#: src/main.py:170 msgid "This is an ERROR level test message" msgstr "This is an ERROR level test message" -#: src/main.py:162 +#: src/main.py:172 #, python-brace-format msgid "✓ Log level successfully set to: {level}\n" msgstr "✓ Log level successfully set to: {level}\n" -#: src/main.py:163 +#: src/main.py:173 msgid "✓ Logging system initialized\n" msgstr "✓ Logging system initialized\n" -#: src/main.py:191 +#: src/main.py:201 #, python-brace-format msgid "✓ Language set to: {locale}\n" msgstr "✓ Language set to: {locale}\n" -#: src/main.py:193 +#: src/main.py:203 #, python-brace-format msgid "✗ Unsupported language: {locale}\n" msgstr "✗ Unsupported language: {locale}\n" -#: src/main.py:228 +#: src/main.py:246 msgid "Fatal error in Intelligent Shell application" msgstr "Fatal error in Intelligent Shell application" -#: src/tool/oi_select_agent.py:29 -msgid "退出" -msgstr "Exit" - #: src/tool/oi_select_agent.py:123 #, python-brace-format msgid "✓ 默认智能体已设置为: {name}\n" @@ -520,7 +1881,9 @@ msgstr "Error: {error}\n" #: src/tool/oi_llm_config.py:77 msgid "需要管理员权限才能修改 openEuler Intelligence 配置文件" -msgstr "Administrator privileges are required to modify openEuler Intelligence configuration files" +msgstr "" +"Administrator privileges are required to modify openEuler Intelligence " +"configuration files" #: src/tool/oi_llm_config.py:84 #, python-brace-format @@ -549,200 +1912,209 @@ msgstr "Error occurred when accessing configuration file: {error}" #: src/tool/oi_llm_config.py:127 #, python-brace-format msgid "权限不足:无法访问配置文件 {filename},请以管理员身份运行" -msgstr "Insufficient permissions: Cannot access configuration file {filename}, please run as administrator" +msgstr "" +"Insufficient permissions: Cannot access configuration file {filename}, " +"please run as administrator" + +#: src/tool/oi_backend_init.py:39 +msgid "openEuler Intelligence 部署助手" +msgstr "openEuler Intelligence Deployment Assistant" -#: src/tool/validators.py:135 src/tool/validators.py:584 -#: src/tool/validators.py:647 +#: src/tool/validators.py:165 src/tool/validators.py:614 +#: src/tool/validators.py:677 #, python-brace-format msgid "连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}" -msgstr "Connection timeout - Unable to connect to {endpoint} within {timeout} seconds" +msgstr "" +"Connection timeout - Unable to connect to {endpoint} within {timeout} seconds" -#: src/tool/validators.py:140 +#: src/tool/validators.py:170 #, python-brace-format msgid "LLM 配置验证失败: {error}" msgstr "LLM configuration validation failed: {error}" -#: src/tool/validators.py:144 +#: src/tool/validators.py:174 msgid "LLM 配置验证成功" msgstr "LLM configuration validation successful" -#: src/tool/validators.py:146 +#: src/tool/validators.py:176 #, python-brace-format msgid " - 支持工具调用,类型: {func_type}" msgstr " - Tool calling supported, type: {func_type}" -#: src/tool/validators.py:148 +#: src/tool/validators.py:178 msgid " - 不支持工具调用" msgstr " - Tool calling not supported" -#: src/tool/validators.py:201 +#: src/tool/validators.py:231 msgid "无法连接到 Embedding 模型服务。" msgstr "Unable to connect to Embedding model service." -#: src/tool/validators.py:241 +#: src/tool/validators.py:271 msgid "基本对话测试失败" msgstr "Basic chat test failed" -#: src/tool/validators.py:244 +#: src/tool/validators.py:274 msgid "基本对话功能正常" msgstr "Basic chat functionality is working" -#: src/tool/validators.py:246 +#: src/tool/validators.py:276 msgid "对话响应为空" msgstr "Chat response is empty" -#: src/tool/validators.py:318 +#: src/tool/validators.py:348 msgid "不支持任何 function_call 格式" msgstr "No function_call format is supported" -#: src/tool/validators.py:353 +#: src/tool/validators.py:383 #, python-brace-format msgid "tools 格式测试失败: {error}" msgstr "tools format test failed: {error}" -#: src/tool/validators.py:358 +#: src/tool/validators.py:388 msgid "支持 tools 格式的 function_call" msgstr "tools format function_call is supported" -#: src/tool/validators.py:360 +#: src/tool/validators.py:390 msgid "不支持工具调用功能" msgstr "Tool calling functionality is not supported" -#: src/tool/validators.py:401 +#: src/tool/validators.py:431 #, python-brace-format msgid "structured_output 格式测试失败: {error}" msgstr "structured_output format test failed: {error}" -#: src/tool/validators.py:409 +#: src/tool/validators.py:439 msgid "structured_output 响应不是有效 JSON" msgstr "structured_output response is not valid JSON" -#: src/tool/validators.py:411 +#: src/tool/validators.py:441 msgid "支持 structured_output 格式" msgstr "structured_output format is supported" -#: src/tool/validators.py:413 +#: src/tool/validators.py:443 msgid "structured_output 响应为空" msgstr "structured_output response is empty" -#: src/tool/validators.py:439 +#: src/tool/validators.py:469 #, python-brace-format msgid "json_mode 格式测试失败: {error}" msgstr "json_mode format test failed: {error}" -#: src/tool/validators.py:447 +#: src/tool/validators.py:477 msgid "json_mode 响应不是有效 JSON" msgstr "json_mode response is not valid JSON" -#: src/tool/validators.py:449 +#: src/tool/validators.py:479 msgid "支持 json_mode 格式" msgstr "json_mode format is supported" -#: src/tool/validators.py:451 +#: src/tool/validators.py:481 msgid "json_mode 响应为空" msgstr "json_mode response is empty" -#: src/tool/validators.py:499 +#: src/tool/validators.py:529 msgid "支持 vLLM 结构化输出(部分支持)" msgstr "vLLM structured output is supported (partial support)" -#: src/tool/validators.py:504 +#: src/tool/validators.py:534 #, python-brace-format msgid "不支持 vLLM guided_json 格式: {error}" msgstr "vLLM guided_json format is not supported: {error}" -#: src/tool/validators.py:508 +#: src/tool/validators.py:538 msgid "vLLM guided_json 响应无效" msgstr "vLLM guided_json response is invalid" -#: src/tool/validators.py:555 +#: src/tool/validators.py:585 msgid "支持 Ollama function_call 格式" msgstr "Ollama function_call format is supported" -#: src/tool/validators.py:558 +#: src/tool/validators.py:588 #, python-brace-format msgid "不支持 Ollama function_call 格式: {error}" msgstr "Ollama function_call format is not supported: {error}" -#: src/tool/validators.py:561 +#: src/tool/validators.py:591 msgid "Ollama function_call 响应无效" msgstr "Ollama function_call response is invalid" -#: src/tool/validators.py:589 +#: src/tool/validators.py:619 #, python-brace-format msgid "OpenAI Embedding 配置验证失败: {error}" msgstr "OpenAI Embedding configuration validation failed: {error}" -#: src/tool/validators.py:598 +#: src/tool/validators.py:628 #, python-brace-format msgid "OpenAI Embedding 配置验证成功 - 维度: {dimension}" -msgstr "OpenAI Embedding configuration validation successful - Dimension: {dimension}" +msgstr "" +"OpenAI Embedding configuration validation successful - Dimension: {dimension}" -#: src/tool/validators.py:606 +#: src/tool/validators.py:636 msgid "OpenAI Embedding 响应为空" msgstr "OpenAI Embedding response is empty" -#: src/tool/validators.py:634 +#: src/tool/validators.py:664 #, python-brace-format msgid "MindIE Embedding 配置验证成功 - 维度: {dimension}" -msgstr "MindIE Embedding configuration validation successful - Dimension: {dimension}" +msgstr "" +"MindIE Embedding configuration validation successful - Dimension: {dimension}" -#: src/tool/validators.py:644 +#: src/tool/validators.py:674 msgid "MindIE Embedding 响应格式不正确" msgstr "MindIE Embedding response format is incorrect" -#: src/tool/validators.py:652 +#: src/tool/validators.py:682 #, python-brace-format msgid "MindIE Embedding 配置验证失败: {error}" msgstr "MindIE Embedding configuration validation failed: {error}" -#: src/tool/validators.py:674 +#: src/tool/validators.py:704 msgid "服务 URL 必须以 http:// 或 https:// 开头" msgstr "Service URL must start with http:// or https://" -#: src/tool/validators.py:685 +#: src/tool/validators.py:715 msgid "访问令牌格式无效" msgstr "Access token format is invalid" -#: src/tool/validators.py:710 +#: src/tool/validators.py:740 msgid "服务返回的数据格式不正确" msgstr "Service returned data in incorrect format" -#: src/tool/validators.py:716 +#: src/tool/validators.py:746 msgid "连接成功" msgstr "Connection successful" -#: src/tool/validators.py:718 +#: src/tool/validators.py:748 #, python-brace-format msgid "服务返回错误代码: {code}" msgstr "Service returned error code: {code}" -#: src/tool/validators.py:721 +#: src/tool/validators.py:751 msgid "无法连接到服务,请检查 URL 和网络连接" msgstr "Unable to connect to service, please check URL and network connection" -#: src/tool/validators.py:723 +#: src/tool/validators.py:753 msgid "连接超时,请检查网络连接或服务状态" msgstr "Connection timeout, please check network connection or service status" -#: src/tool/validators.py:726 +#: src/tool/validators.py:756 #, python-brace-format msgid "连接验证失败: {error}" msgstr "Connection validation failed: {error}" -#: src/tool/validators.py:732 +#: src/tool/validators.py:762 msgid "访问令牌无效或已过期" msgstr "Access token is invalid or expired" -#: src/tool/validators.py:733 +#: src/tool/validators.py:763 msgid "访问权限不足" msgstr "Insufficient access permissions" -#: src/tool/validators.py:734 +#: src/tool/validators.py:764 msgid "API 接口不存在,请检查服务版本" msgstr "API interface does not exist, please check service version" -#: src/tool/validators.py:737 +#: src/tool/validators.py:767 #, python-brace-format msgid "服务响应异常,状态码: {status_code}" msgstr "Service response error, status code: {status_code}" @@ -762,7 +2134,9 @@ msgstr "[Command start failed] Unable to create subprocess" #: src/tool/command_processor.py:136 #, python-brace-format msgid "无法启动命令 '{command}',请分析可能原因并给出解决建议。" -msgstr "Unable to start command '{command}', please analyze possible causes and provide solutions." +msgstr "" +"Unable to start command '{command}', please analyze possible causes and " +"provide solutions." #: src/tool/command_processor.py:165 #, python-brace-format @@ -794,3 +2168,118 @@ msgstr "" #: src/tool/command_processor.py:206 msgid "读取 stderr 失败" msgstr "Failed to read stderr" + +#: src/tool/oi_login.py:67 +#, python-brace-format +msgid "✗ Failed to get authorization URL: {error}\n" +msgstr "✗ Failed to get authorization URL: {error}\n" + +#: src/tool/oi_login.py:136 +msgid "✗ Error: Browser is not available in current environment\n" +msgstr "✗ Error: Browser is not available in current environment\n" + +#: src/tool/oi_login.py:137 +msgid "This feature requires a graphical environment with browser support.\n" +msgstr "This feature requires a graphical environment with browser support.\n" + +#: src/tool/oi_login.py:138 +msgid "If you are using SSH, please run this command on the server directly\n" +msgstr "If you are using SSH, please run this command on the server directly\n" + +#: src/tool/oi_login.py:139 +msgid "or use X11 forwarding / VNC to enable graphical access.\n" +msgstr "or use X11 forwarding / VNC to enable graphical access.\n" + +#: src/tool/oi_login.py:151 +msgid "" +"\n" +"\n" +"✗ Login cancelled by user\n" +msgstr "" +"\n" +"\n" +"✗ Login cancelled by user\n" + +#: src/tool/oi_login.py:155 +#, python-brace-format +msgid "" +"\n" +"✗ An error occurred during login: {error}\n" +msgstr "" +"\n" +"✗ An error occurred during login: {error}\n" + +#: src/tool/oi_login.py:169 +msgid "✗ Error: openEuler Intelligence URL not configured\n" +msgstr "✗ Error: openEuler Intelligence URL not configured\n" + +#: src/tool/oi_login.py:170 +msgid "Please run deployment initialization first: oi --init\n" +msgstr "Please run deployment initialization first: oi --init\n" + +#: src/tool/oi_login.py:181 +msgid "Getting authorization URL from server...\n" +msgstr "Getting authorization URL from server...\n" + +#: src/tool/oi_login.py:185 +msgid "✗ Failed to get authorization URL\n" +msgstr "✗ Failed to get authorization URL\n" + +#: src/tool/oi_login.py:194 +msgid "Opening browser for login...\n" +msgstr "Opening browser for login...\n" + +#: src/tool/oi_login.py:195 +msgid "If the browser doesn't open automatically, please visit:\n" +msgstr "If the browser doesn't open automatically, please visit:\n" + +#: src/tool/oi_login.py:202 +msgid "Waiting for login to complete...\n" +msgstr "Waiting for login to complete...\n" + +#: src/tool/oi_login.py:215 +msgid "" +"\n" +"✓ Login successful!\n" +msgstr "" +"\n" +"✓ Login successful!\n" + +#: src/tool/oi_login.py:216 +msgid "✓ API Key has been saved to configuration\n" +msgstr "✓ API Key has been saved to configuration\n" + +#: src/tool/oi_login.py:219 +msgid "" +"\n" +"✗ Login failed: No session ID received\n" +msgstr "" +"\n" +"✗ Login failed: No session ID received\n" + +#: src/tool/oi_login.py:223 +#, python-brace-format +msgid "" +"\n" +"✗ Login failed: {error}\n" +msgstr "" +"\n" +"✗ Login failed: {error}\n" + +#: src/tool/oi_login.py:226 +msgid "" +"\n" +"✗ Login failed: Unknown result\n" +msgstr "" +"\n" +"✗ Login failed: Unknown result\n" + +#~ msgid "需要用户确认是否执行此工具" +#~ msgstr "User confirmation required to execute this tool" + +#~ msgid "需要补充参数" +#~ msgstr "Parameters required" + +#, python-brace-format +#~ msgid "⏱️ MCP response timeout ({seconds} seconds)" +#~ msgstr "⏱️ MCP response timeout ({seconds} seconds)" diff --git a/src/i18n/locales/messages.pot b/src/i18n/locales/messages.pot index c1109567f50c75bedae803427b824d588277939c..2a7bcef0a89f65422147d76015ad031b4b84b0c3 100644 --- a/src/i18n/locales/messages.pot +++ b/src/i18n/locales/messages.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: oi-cli 0.10.2\n" +"Project-Id-Version: oi-cli 2.0.0\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-21 10:54+0800\n" +"POT-Creation-Date: 2025-11-04 17:40+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,232 +17,1489 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/app/mcp_widgets.py:47 -msgid "需要用户确认是否执行此工具" +#: src/app/mcp_widgets.py:50 src/backend/hermes/mcp_helpers.py:286 +msgid "低风险" +msgstr "" + +#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:287 +msgid "中等风险" +msgstr "" + +#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:288 +msgid "高风险" +msgstr "" + +#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:289 +msgid "未知风险" +msgstr "" + +#: src/app/mcp_widgets.py:66 +msgid "✓ 确认" +msgstr "" + +#: src/app/mcp_widgets.py:67 src/app/mcp_widgets.py:170 +msgid "✗ 取消" +msgstr "" + +#: src/app/mcp_widgets.py:153 +msgid "📝 参数输入" +msgstr "" + +#: src/app/mcp_widgets.py:160 +#, python-brace-format +msgid "请输入 {param_name}" +msgstr "" + +#: src/app/mcp_widgets.py:169 +msgid "✓ 提交" +msgstr "" + +#: src/app/deployment/service.py:66 +#, python-brace-format +msgid "无法读取模板文件: {path}" +msgstr "" + +#: src/app/deployment/service.py:138 +#, python-brace-format +msgid "TOML 格式错误: {error}" +msgstr "" + +#: src/app/deployment/service.py:142 +#, python-brace-format +msgid "更新 TOML 配置失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:188 +msgid "检查部署依赖" +msgstr "" + +#: src/app/deployment/service.py:189 +msgid "正在检查部署环境依赖..." +msgstr "" + +#: src/app/deployment/service.py:194 +#: src/app/deployment/components/env_check.py:141 +msgid "仅支持 openEuler 操作系统" +msgstr "" + +#: src/app/deployment/service.py:201 +#, python-brace-format +msgid "⚠ 检测到 Python {version},低于 3.10 版本将不支持全量部署模式" +msgstr "" + +#: src/app/deployment/service.py:210 +msgid "缺少 openeuler-intelligence-installer 包,正在尝试安装..." +msgstr "" + +#: src/app/deployment/service.py:220 +#: src/app/deployment/components/env_check.py:164 +msgid "需要管理员权限,请确保可以使用 sudo" +msgstr "" + +#: src/app/deployment/service.py:224 +msgid "✓ 部署环境依赖检查完成" +msgstr "" + +#: src/app/deployment/service.py:271 +msgid "" +"当前 openEuler 版本低于 24.03 LTS,不支持全量部署模式。请使用轻量部署模式或升" +"级到 openEuler 24.03+ 版本" +msgstr "" + +#: src/app/deployment/service.py:277 +#, python-brace-format +msgid "无法检查 Python 环境: {error}" +msgstr "" + +#: src/app/deployment/service.py:280 +#, python-brace-format +msgid "Python 环境版本 {version} 符合要求" +msgstr "" + +#: src/app/deployment/service.py:336 +msgid "部署过程中发生异常" +msgstr "" + +#: src/app/deployment/service.py:337 +msgid "✗ 部署失败" +msgstr "" + +#: src/app/deployment/service.py:347 +msgid "✓ openEuler Intelligence 后端部署完成!" +msgstr "" + +#: src/app/deployment/service.py:385 +msgid "正在安装 openeuler-intelligence-installer..." +msgstr "" + +#: src/app/deployment/service.py:396 +msgid "✓ openeuler-intelligence-installer 安装成功" +msgstr "" + +#: src/app/deployment/service.py:400 +msgid "openeuler-intelligence-installer 安装后资源文件仍然缺失" +msgstr "" + +#: src/app/deployment/service.py:403 +msgid "安装 openeuler-intelligence-installer 失败" +msgstr "" + +#: src/app/deployment/service.py:406 +msgid "安装输出:" +msgstr "" + +#: src/app/deployment/service.py:410 +#, python-brace-format +msgid "安装过程中发生异常: {error}" +msgstr "" + +#: src/app/deployment/service.py:446 +msgid "✓ 基础服务部署完成" +msgstr "" + +#: src/app/deployment/service.py:447 +msgid "请访问网页管理界面完成 Agent 服务配置" +msgstr "" + +#: src/app/deployment/service.py:448 +#, python-brace-format +msgid "管理界面地址: http://{ip}:8080" +msgstr "" + +#: src/app/deployment/service.py:485 src/app/deployment/service.py:575 +msgid "检查系统环境" +msgstr "" + +#: src/app/deployment/service.py:486 +msgid "正在检查系统环境..." +msgstr "" + +#: src/app/deployment/service.py:493 +msgid "✗ 错误: 仅支持 openEuler 操作系统" +msgstr "" + +#: src/app/deployment/service.py:495 +msgid "✓ 检测到 openEuler 操作系统" +msgstr "" + +#: src/app/deployment/service.py:500 +#, python-brace-format +msgid "✗ 错误: {msg}" +msgstr "" + +#: src/app/deployment/service.py:505 +msgid "✗ 错误: openeuler-intelligence-installer 包未安装或资源缺失" +msgstr "" + +#: src/app/deployment/service.py:506 +msgid "请先安装: sudo dnf install -y openeuler-intelligence-installer" +msgstr "" + +#: src/app/deployment/service.py:508 +msgid "✓ openeuler-intelligence-installer 资源可用" +msgstr "" + +#: src/app/deployment/service.py:512 +msgid "✗ 错误: 需要管理员权限" +msgstr "" + +#: src/app/deployment/service.py:514 +msgid "✓ 具有管理员权限" +msgstr "" + +#: src/app/deployment/service.py:525 +msgid "初始化部署配置" +msgstr "" + +#: src/app/deployment/service.py:526 +msgid "正在设置部署模式..." +msgstr "" + +#: src/app/deployment/service.py:553 src/app/deployment/service.py:562 +#, python-brace-format +msgid "✗ 设置部署模式失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:556 src/app/deployment/service.py:557 +msgid "启用" +msgstr "" + +#: src/app/deployment/service.py:556 src/app/deployment/service.py:557 +msgid "禁用" +msgstr "" + +#: src/app/deployment/service.py:558 +#, python-brace-format +msgid "✓ 部署模式设置完成 (Web界面: {web}, RAG: {rag})" +msgstr "" + +#: src/app/deployment/service.py:576 +msgid "正在执行系统环境检查..." +msgstr "" + +#: src/app/deployment/service.py:583 +msgid "环境检查脚本" +msgstr "" + +#: src/app/deployment/service.py:585 +#, python-brace-format +msgid "✗ 环境检查失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:596 +msgid "安装依赖组件" +msgstr "" + +#: src/app/deployment/service.py:597 +msgid "正在安装 openEuler Intelligence 依赖组件..." +msgstr "" + +#: src/app/deployment/service.py:606 +msgid "依赖安装脚本" +msgstr "" + +#: src/app/deployment/service.py:608 +#, python-brace-format +msgid "✗ 依赖安装失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:619 +msgid "初始化配置和服务" +msgstr "" + +#: src/app/deployment/service.py:620 +msgid "正在初始化配置和启动服务..." +msgstr "" + +#: src/app/deployment/service.py:627 +msgid "配置初始化脚本" +msgstr "" + +#: src/app/deployment/service.py:629 +#, python-brace-format +msgid "✗ 配置初始化失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:641 +#, python-brace-format +msgid "✗ 脚本文件不存在: {path}" +msgstr "" + +#: src/app/deployment/service.py:679 +#, python-brace-format +msgid "✓ {name}执行成功" +msgstr "" + +#: src/app/deployment/service.py:683 +#, python-brace-format +msgid "✗ 运行{name}时发生错误: {error}" +msgstr "" + +#: src/app/deployment/service.py:688 +#, python-brace-format +msgid "✗ {name}执行失败,返回码: {code}" +msgstr "" + +#: src/app/deployment/service.py:709 +msgid "更新配置文件" +msgstr "" + +#: src/app/deployment/service.py:710 +msgid "正在更新配置文件..." +msgstr "" + +#: src/app/deployment/service.py:718 +msgid "✓ 更新 env 配置文件" +msgstr "" + +#: src/app/deployment/service.py:722 +msgid "✓ 更新 config.toml 配置文件" +msgstr "" + +#: src/app/deployment/service.py:725 +#, python-brace-format +msgid "✗ 更新配置文件失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:758 +#, python-brace-format +msgid "备份 env 文件失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:773 +#, python-brace-format +msgid "写入 env 文件失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:803 +#, python-brace-format +msgid "备份 config.toml 文件失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:818 +#, python-brace-format +msgid "写入 config.toml 文件失败: {error}" +msgstr "" + +#: src/app/deployment/service.py:866 +#, python-brace-format +msgid "检查 oi-runtime 服务状态 ({current}/{total})..." +msgstr "" + +#: src/app/deployment/service.py:888 +msgid "✓ Framework 服务状态正常" +msgstr "" + +#: src/app/deployment/service.py:891 +#, python-brace-format +msgid "Framework 服务状态: {status}" +msgstr "" + +#: src/app/deployment/service.py:894 +#, python-brace-format +msgid "等待 {seconds} 秒后重试..." +msgstr "" + +#: src/app/deployment/service.py:898 +#, python-brace-format +msgid "检查服务状态时发生错误: {error}" +msgstr "" + +#: src/app/deployment/service.py:902 +msgid "✗ Framework 服务状态检查超时失败" +msgstr "" + +#: src/app/deployment/service.py:917 +msgid "等待 openEuler Intelligence 服务就绪" +msgstr "" + +#: src/app/deployment/service.py:929 +msgid "✓ openEuler Intelligence 服务已就绪" +msgstr "" + +#: src/app/deployment/service.py:935 +#, python-brace-format +msgid "连接 {url} 超时" +msgstr "" + +#: src/app/deployment/service.py:937 +#, python-brace-format +msgid "API 连通性检查时发生错误: {error}" +msgstr "" + +#: src/app/deployment/service.py:942 +msgid "✗ openEuler Intelligence API 服务检查超时失败" +msgstr "" + +#: src/app/deployment/service.py:952 +msgid "初始化 Agent 服务" +msgstr "" + +#: src/app/deployment/service.py:953 +msgid "正在检查 openEuler Intelligence 后端服务状态..." +msgstr "" + +#: src/app/deployment/service.py:964 +msgid "✗ openEuler Intelligence 服务检查失败" +msgstr "" + +#: src/app/deployment/service.py:967 +msgid "✓ openEuler Intelligence 服务检查通过,开始初始化 Agent..." +msgstr "" + +#: src/app/deployment/service.py:977 +msgid "✓ Agent 初始化完成" +msgstr "" + +#: src/app/deployment/service.py:981 +msgid "⚠ Agent 初始化已跳过(RPM 包不可用),但部署将继续进行" +msgstr "" + +#: src/app/deployment/service.py:1020 +msgid "✓ 全局配置模板创建成功,其他用户可正常使用" +msgstr "" + +#: src/app/deployment/service.py:1023 +msgid "⚠ 全局配置模板创建失败,可能影响其他用户使用" +msgstr "" + +#: src/app/deployment/service.py:1028 +msgid "⚠ 配置模板创建异常,可能影响其他用户使用" +msgstr "" + +#: src/app/deployment/models.py:121 src/app/deployment/models.py:179 +msgid "LLM API 端点不能为空" +msgstr "" + +#: src/app/deployment/models.py:150 src/app/deployment/models.py:199 +#: src/app/deployment/models.py:202 +msgid "Embedding API 端点不能为空" +msgstr "" + +#: src/app/deployment/models.py:172 +msgid "服务器 IP 地址不能为空" +msgstr "" + +#: src/app/deployment/models.py:210 +msgid "LLM max_tokens 必须大于 0" +msgstr "" + +#: src/app/deployment/models.py:213 +#, python-brace-format +msgid "LLM temperature 必须在 {min} 到 {max} 之间" +msgstr "" + +#: src/app/deployment/models.py:219 +msgid "LLM 请求超时时间必须大于 0" +msgstr "" + +#: src/app/deployment/ui.py:141 src/app/deployment/ui.py:199 +msgid "基础配置" +msgstr "" + +#: src/app/deployment/ui.py:144 +msgid "LLM 配置" +msgstr "" + +#: src/app/deployment/ui.py:147 +msgid "Embedding 配置" +msgstr "" + +#: src/app/deployment/ui.py:151 +msgid "开始部署" +msgstr "" + +#: src/app/deployment/ui.py:152 src/app/settings.py:65 +#: src/app/dialogs/user.py:180 src/app/dialogs/common.py:27 +msgid "取消" +msgstr "" + +#: src/app/deployment/ui.py:169 +msgid "全量部署" +msgstr "" + +#: src/app/deployment/ui.py:170 +msgid "全量部署:部署框架服务 + Web 界面 + RAG 组件,自动初始化 Agent。" +msgstr "" + +#: src/app/deployment/ui.py:172 src/app/deployment/ui.py:213 +msgid "轻量部署" +msgstr "" + +#: src/app/deployment/ui.py:173 src/app/deployment/ui.py:218 +msgid "轻量部署:仅部署框架服务,自动初始化 Agent。" +msgstr "" + +#: src/app/deployment/ui.py:189 +msgid "[dim]不需要验证[/dim]" +msgstr "" + +#: src/app/deployment/ui.py:202 +msgid "服务器 IP 地址:" +msgstr "" + +#: src/app/deployment/ui.py:205 +msgid "例如:127.0.0.1" +msgstr "" + +#: src/app/deployment/ui.py:211 +msgid "部署模式:" +msgstr "" + +#: src/app/deployment/ui.py:226 +msgid "大语言模型配置" +msgstr "" + +#: src/app/deployment/ui.py:229 src/app/deployment/ui.py:294 +msgid "API 端点:" +msgstr "" + +#: src/app/deployment/ui.py:231 src/app/deployment/ui.py:296 +msgid "例如:http://localhost:11434/v1" +msgstr "" + +#: src/app/deployment/ui.py:237 src/app/deployment/ui.py:302 +msgid "API 密钥:" +msgstr "" + +#: src/app/deployment/ui.py:246 src/app/deployment/ui.py:311 +msgid "模型名称:" +msgstr "" + +#: src/app/deployment/ui.py:248 +msgid "例如:deepseek-llm-7b-chat" +msgstr "" + +#: src/app/deployment/ui.py:254 src/app/deployment/ui.py:319 +msgid "验证状态:" +msgstr "" + +#: src/app/deployment/ui.py:255 src/app/deployment/ui.py:320 +#: src/app/deployment/components/modes.py:204 +msgid "未验证" +msgstr "" + +#: src/app/deployment/ui.py:258 +msgid "最大输出令牌数:" +msgstr "" + +#: src/app/deployment/ui.py:266 +msgid "Temperature:" +msgstr "" + +#: src/app/deployment/ui.py:274 +msgid "请求超时 (秒):" +msgstr "" + +#: src/app/deployment/ui.py:284 +msgid "嵌入模型配置" +msgstr "" + +#: src/app/deployment/ui.py:288 +msgid "[dim]轻量部署模式下,Embedding 配置为可选项。[/dim]" +msgstr "" + +#: src/app/deployment/ui.py:313 +msgid "例如:bge-m3" +msgstr "" + +#: src/app/deployment/ui.py:330 +msgid "配置验证失败" +msgstr "" + +#: src/app/deployment/ui.py:369 +msgid "" +"[dim]轻量部署模式下,Embedding 配置为可选项。如果不填写,将跳过 RAG 功能。[/" +"dim]" +msgstr "" + +#: src/app/deployment/ui.py:373 +msgid "[dim]全量部署模式下,Embedding 配置为必填项,用于支持 RAG 功能。[/dim]" +msgstr "" + +#: src/app/deployment/ui.py:518 src/app/deployment/components/modes.py:390 +#, python-brace-format +msgid "[green]✓ {message}[/green]" +msgstr "" + +#: src/app/deployment/ui.py:521 +msgid "[red]✗ 不支持工具调用[/red]" +msgstr "" + +#: src/app/deployment/ui.py:523 +msgid "" +"LLM 验证失败:模型不支持工具调用功能,无法用于部署。请选择支持工具调用的模" +"型。" +msgstr "" + +#: src/app/deployment/ui.py:528 src/app/deployment/ui.py:562 +#: src/app/deployment/components/modes.py:394 +#, python-brace-format +msgid "[red]✗ {message}[/red]" +msgstr "" + +#: src/app/deployment/ui.py:532 src/app/deployment/ui.py:566 +#: src/app/deployment/components/modes.py:399 +#, python-brace-format +msgid "[red]✗ 验证异常: {error}[/red]" +msgstr "" + +#: src/app/deployment/ui.py:556 +#, python-brace-format +msgid "[green]✓ {message} (维度: {dimension})[/green]" +msgstr "" + +#: src/app/deployment/ui.py:710 +msgid "部署进度:" +msgstr "" + +#: src/app/deployment/ui.py:711 +msgid "准备开始部署..." +msgstr "" + +#: src/app/deployment/ui.py:717 +msgid "完成" +msgstr "" + +#: src/app/deployment/ui.py:718 +msgid "重试" +msgstr "" + +#: src/app/deployment/ui.py:719 +msgid "重新配置" +msgstr "" + +#: src/app/deployment/ui.py:720 +msgid "取消部署" +msgstr "" + +#: src/app/deployment/ui.py:755 src/app/deployment/ui.py:836 +msgid "部署已取消" +msgstr "" + +#: src/app/deployment/ui.py:756 +msgid "部署已被用户取消" +msgstr "" + +#: src/app/deployment/ui.py:819 +msgid "部署启动失败" +msgstr "" + +#: src/app/deployment/ui.py:820 +#, python-brace-format +msgid "部署启动失败: {error}" +msgstr "" + +#: src/app/deployment/ui.py:837 +msgid "部署被取消" +msgstr "" + +#: src/app/deployment/ui.py:840 src/app/deployment/ui.py:884 +msgid "部署异常" +msgstr "" + +#: src/app/deployment/ui.py:841 +#, python-brace-format +msgid "部署异常: {error}" +msgstr "" + +#: src/app/deployment/ui.py:848 +msgid "正在检查部署环境..." +msgstr "" + +#: src/app/deployment/ui.py:852 +msgid "环境检查失败" +msgstr "" + +#: src/app/deployment/ui.py:860 +msgid "正在执行部署..." +msgstr "" + +#: src/app/deployment/ui.py:867 +msgid "部署完成!" +msgstr "" + +#: src/app/deployment/ui.py:869 +msgid "[bold green]部署成功完成![/bold green]" +msgstr "" + +#: src/app/deployment/ui.py:872 +msgid "部署成功完成!" +msgstr "" + +#: src/app/deployment/ui.py:874 +msgid "部署失败" +msgstr "" + +#: src/app/deployment/ui.py:876 +msgid "[bold red]部署失败,请查看上面的错误信息[/bold red]" +msgstr "" + +#: src/app/deployment/ui.py:878 +msgid "部署执行失败" +msgstr "" + +#: src/app/deployment/ui.py:880 +msgid "部署失败,可以重试或重新配置参数" +msgstr "" + +#: src/app/deployment/ui.py:883 +#, python-brace-format +msgid "部署过程中发生异常: {error}" +msgstr "" + +#: src/app/deployment/ui.py:901 +#, python-brace-format +msgid "步骤 {current}/{total}: {name}" +msgstr "" + +#: src/app/deployment/ui.py:974 +msgid "错误" +msgstr "" + +#: src/app/deployment/ui.py:980 +msgid "确定" +msgstr "" + +#: src/app/deployment/components/modes.py:58 +#: src/app/deployment/components/modes.py:92 +#: src/app/deployment/components/modes.py:158 +#: src/app/deployment/components/modes.py:230 +#: src/app/deployment/components/env_check.py:105 +#: src/tool/oi_select_agent.py:29 +msgid "退出" +msgstr "" + +#: src/app/deployment/components/modes.py:68 +msgid "openEuler Intelligence 初始化" +msgstr "" + +#: src/app/deployment/components/modes.py:70 +msgid "请选择您的初始化方式:" +msgstr "" + +#: src/app/deployment/components/modes.py:77 +msgid "" +"连接现有服务\n" +"\n" +"输入现有服务的 URL 和 Token 即可连接使用" +msgstr "" + +#: src/app/deployment/components/modes.py:85 +msgid "" +"部署新服务\n" +"\n" +"在本机部署全新的服务环境和配置" +msgstr "" + +#: src/app/deployment/components/modes.py:157 +#: src/app/deployment/components/modes.py:229 +#: src/app/deployment/components/env_check.py:104 +msgid "返回" +msgstr "" + +#: src/app/deployment/components/modes.py:174 +msgid "连接现有 openEuler Intelligence 服务" +msgstr "" + +#: src/app/deployment/components/modes.py:176 +msgid "请输入您的 openEuler Intelligence 服务连接信息:" +msgstr "" + +#: src/app/deployment/components/modes.py:181 +msgid "服务 URL:" +msgstr "" + +#: src/app/deployment/components/modes.py:183 +msgid "例如:http://your-server:8002" +msgstr "" + +#: src/app/deployment/components/modes.py:189 +msgid "访问令牌:" +msgstr "" + +#: src/app/deployment/components/modes.py:191 +msgid "可选,您的访问令牌" +msgstr "" + +#: src/app/deployment/components/modes.py:197 +msgid "获取" +msgstr "" + +#: src/app/deployment/components/modes.py:209 +msgid "" +"提示:\n" +"• 服务 URL 通常以 http:// 或 https:// 开头\n" +"• 访问令牌为可选项,如果服务无需认证可留空\n" +"• 输入服务 URL 后,可点击 '获取' 按钮通过浏览器获取访问令牌\n" +"• 也可以从 openEuler Intelligence Web 界面手动获取并填入\n" +"• 系统会自动验证连接并保存配置" +msgstr "" + +#: src/app/deployment/components/modes.py:218 +msgid "" +"提示:\n" +"• 服务 URL 通常以 http:// 或 https:// 开头\n" +"• 访问令牌为可选项,如果服务无需认证可留空\n" +"• [yellow]当前环境不支持浏览器,请从 Web 界面手动获取访问令牌[/yellow]\n" +"• 系统会自动验证连接并保存配置" +msgstr "" + +#: src/app/deployment/components/modes.py:228 +msgid "连接并保存" +msgstr "" + +#: src/app/deployment/components/modes.py:256 +msgid "当前环境不支持打开浏览器,请手动从 Web 界面获取访问令牌" +msgstr "" + +#: src/app/deployment/components/modes.py:264 +msgid "请先输入服务 URL" +msgstr "" + +#: src/app/deployment/components/modes.py:273 +msgid "[yellow]正在获取授权 URL...[/yellow]" +msgstr "" + +#: src/app/deployment/components/modes.py:278 +msgid "[red]✗ 获取授权 URL 失败[/red]" +msgstr "" + +#: src/app/deployment/components/modes.py:287 +msgid "[yellow]正在打开浏览器登录...[/yellow]" +msgstr "" + +#: src/app/deployment/components/modes.py:289 +msgid "已打开浏览器,请完成登录" +msgstr "" + +#: src/app/deployment/components/modes.py:296 +#, python-brace-format +msgid "获取 API Key 失败: {error}" +msgstr "" + +#: src/app/deployment/components/modes.py:307 +msgid "请等待连接验证完成" +msgstr "" + +#: src/app/deployment/components/modes.py:319 +msgid "配置已保存,初始化完成!" +msgstr "" + +#: src/app/deployment/components/modes.py:323 +#, python-brace-format +msgid "保存配置时发生错误: {error}" +msgstr "" + +#: src/app/deployment/components/modes.py:377 +msgid "[yellow]验证连接中...[/yellow]" +msgstr "" + +#: src/app/deployment/components/modes.py:415 +msgid "[yellow]等待登录完成...[/yellow]" +msgstr "" + +#: src/app/deployment/components/modes.py:431 +msgid "[green]✓ 登录成功,已获取 API Key[/green]" +msgstr "" + +#: src/app/deployment/components/modes.py:437 +msgid "[red]✗ 登录失败:未收到 session ID[/red]" +msgstr "" + +#: src/app/deployment/components/modes.py:442 +#, python-brace-format +msgid "[red]✗ 登录失败: {error}[/red]" +msgstr "" + +#: src/app/deployment/components/modes.py:446 +msgid "[red]✗ 登录失败:未知结果[/red]" +msgstr "" + +#: src/app/deployment/components/modes.py:451 +msgid "[yellow]登录已取消[/yellow]" +msgstr "" + +#: src/app/deployment/components/modes.py:455 +#, python-brace-format +msgid "[red]✗ 登录异常: {error}[/red]" +msgstr "" + +#: src/app/deployment/components/env_check.py:92 +msgid "环境检查" +msgstr "" + +#: src/app/deployment/components/env_check.py:96 +msgid "检查操作系统类型..." +msgstr "" + +#: src/app/deployment/components/env_check.py:100 +msgid "检查管理员权限..." +msgstr "" + +#: src/app/deployment/components/env_check.py:103 +msgid "继续配置" +msgstr "" + +#: src/app/deployment/components/env_check.py:124 +#, python-brace-format +msgid "环境检查过程中发生异常: {error}" +msgstr "" + +#: src/app/deployment/components/env_check.py:137 +msgid "操作系统: openEuler (支持)" +msgstr "" + +#: src/app/deployment/components/env_check.py:140 +msgid "操作系统: 非 openEuler (不支持)" +msgstr "" + +#: src/app/deployment/components/env_check.py:146 +#, python-brace-format +msgid "操作系统检查失败: {error}" +msgstr "" + +#: src/app/deployment/components/env_check.py:147 +#, python-brace-format +msgid "操作系统检查异常: {error}" +msgstr "" + +#: src/app/deployment/components/env_check.py:160 +msgid "管理员权限: 可用" +msgstr "" + +#: src/app/deployment/components/env_check.py:163 +msgid "管理员权限: 不可用 (需要 sudo)" +msgstr "" + +#: src/app/deployment/components/env_check.py:169 +#, python-brace-format +msgid "权限检查失败: {error}" +msgstr "" + +#: src/app/deployment/components/env_check.py:170 +#, python-brace-format +msgid "权限检查异常: {error}" +msgstr "" + +#: src/app/deployment/agent.py:409 +msgid "[bold blue]开始初始化智能体...[/bold blue]" +msgstr "" + +#: src/app/deployment/agent.py:416 +msgid "智能体初始化失败" +msgstr "" + +#: src/app/deployment/agent.py:454 +#, python-brace-format +msgid "[bold green]智能体初始化完成! 默认 App ID: {app_id}[/bold green]" +msgstr "" + +#: src/app/deployment/agent.py:461 +msgid "[yellow]未能创建任何智能体[/yellow]" +msgstr "" + +#: src/app/deployment/agent.py:491 +#, python-brace-format +msgid "[yellow]服务配置目录不存在: {dir},跳过{operation}[/yellow]" +msgstr "" + +#: src/app/deployment/agent.py:505 +#, python-brace-format +msgid "[yellow]未找到服务配置文件,跳过{operation}[/yellow]" +msgstr "" + +#: src/app/deployment/agent.py:549 +#, python-brace-format +msgid " [red]处理 {file} 时发生异常[/red]" +msgstr "" + +#: src/app/deployment/agent.py:563 +msgid "[cyan]安装 systemd 服务文件...[/cyan]" +msgstr "" + +#: src/app/deployment/agent.py:566 +msgid "服务文件安装" +msgstr "" + +#: src/app/deployment/agent.py:585 +#, python-brace-format +msgid "[green]成功安装 {count} 个服务文件[/green]" +msgstr "" + +#: src/app/deployment/agent.py:604 +#, python-brace-format +msgid " [blue]复制服务文件: {name}[/blue]" +msgstr "" + +#: src/app/deployment/agent.py:622 +#, python-brace-format +msgid " [red]复制 {name} 时发生异常[/red]" +msgstr "" + +#: src/app/deployment/agent.py:631 +#, python-brace-format +msgid " [green]{name} 复制成功[/green]" +msgstr "" + +#: src/app/deployment/agent.py:640 +#, python-brace-format +msgid " [red]{name} 复制失败: {error}[/red]" +msgstr "" + +#: src/app/deployment/agent.py:652 +msgid "[cyan]重新加载 systemd 配置...[/cyan]" +msgstr "" + +#: src/app/deployment/agent.py:667 +msgid "[red]重新加载 systemd 配置时发生异常[/red]" +msgstr "" + +#: src/app/deployment/agent.py:676 +msgid "[green]systemd 配置重新加载成功[/green]" +msgstr "" + +#: src/app/deployment/agent.py:685 +#, python-brace-format +msgid "[red]systemd 配置重新加载失败: {error}[/red]" +msgstr "" + +#: src/app/deployment/agent.py:697 +msgid "[cyan]启动 MCP Server 进程...[/cyan]" +msgstr "" + +#: src/app/deployment/agent.py:702 +#, python-brace-format +msgid "[red]MCP 启动脚本不存在: {path}[/red]" +msgstr "" + +#: src/app/deployment/agent.py:713 +msgid "[yellow]清理旧进程时遇到问题,但继续执行启动脚本[/yellow]" +msgstr "" + +#: src/app/deployment/agent.py:721 +#, python-brace-format +msgid " [blue]执行命令: {cmd}[/blue]" +msgstr "" + +#: src/app/deployment/agent.py:736 +msgid "[green]MCP Server 启动脚本执行成功[/green]" +msgstr "" + +#: src/app/deployment/agent.py:743 +msgid "执行 MCP Server 启动脚本失败" +msgstr "" + +#: src/app/deployment/agent.py:750 +#, python-brace-format +msgid "[red]MCP Server 启动脚本执行失败 (返回码: {code})[/red]" +msgstr "" + +#: src/app/deployment/agent.py:808 +msgid "[cyan]验证 MCP Server 服务状态...[/cyan]" +msgstr "" + +#: src/app/deployment/agent.py:811 +msgid "服务验证" +msgstr "" + +#: src/app/deployment/agent.py:826 +#, python-brace-format +msgid "[red]关键服务状态异常: {services},停止初始化[/red]" +msgstr "" + +#: src/app/deployment/agent.py:832 +msgid "[green]MCP Server 服务验证完成[/green]" +msgstr "" + +#: src/app/deployment/agent.py:852 +#, python-brace-format +msgid " [red]{name} 启动超时 (30秒)[/red]" +msgstr "" + +#: src/app/deployment/agent.py:861 +#, python-brace-format +msgid " [magenta]检查服务状态: {name}[/magenta]" +msgstr "" + +#: src/app/deployment/agent.py:867 +#, python-brace-format +msgid " [dim]{name} 重新检查状态... (第 {count} 次)[/dim]" +msgstr "" + +#: src/app/deployment/agent.py:889 +#, python-brace-format +msgid " [red]检查 {name} 状态失败[/red]" +msgstr "" + +#: src/app/deployment/agent.py:900 +#, python-brace-format +msgid " [green]{service_name} 状态正常 (active running)[/green]" +msgstr "" + +#: src/app/deployment/agent.py:910 +#, python-brace-format +msgid " [red]{service_name} 服务启动失败[/red]" msgstr "" -#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:286 -msgid "低风险" +#: src/app/deployment/agent.py:921 +#, python-brace-format +msgid " [yellow]{service_name} 正在启动中,等待启动完成...[/yellow]" msgstr "" -#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:287 -msgid "中等风险" +#: src/app/deployment/agent.py:935 +#, python-brace-format +msgid " [red]{service_name} 状态异常 (返回码: {returncode})[/red]" msgstr "" -#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:288 -msgid "高风险" +#: src/app/deployment/agent.py:956 +msgid "[cyan]注册 MCP 服务...[/cyan]" msgstr "" -#: src/app/mcp_widgets.py:54 src/backend/hermes/mcp_helpers.py:289 -msgid "未知风险" +#: src/app/deployment/agent.py:973 +#, python-brace-format +msgid " [green]{name} 注册成功: {mcp_path} -> {service_id}[/green]" msgstr "" -#: src/app/mcp_widgets.py:81 -msgid "✓ 确认" +#: src/app/deployment/agent.py:983 +#, python-brace-format +msgid " [red]MCP 服务 {name} 注册失败[/red]" msgstr "" -#: src/app/mcp_widgets.py:82 src/app/mcp_widgets.py:201 -msgid "✗ 取消" +#: src/app/deployment/agent.py:989 +#, python-brace-format +msgid "[green]MCP 服务注册完成,成功 {count} 个[/green]" msgstr "" -#: src/app/mcp_widgets.py:164 -msgid "需要补充参数" +#: src/app/deployment/agent.py:1001 +msgid "[cyan]读取应用配置并创建智能体...[/cyan]" msgstr "" -#: src/app/mcp_widgets.py:169 -msgid "📝 参数输入" +#: src/app/deployment/agent.py:1027 +#, python-brace-format +msgid " [dim]设置默认智能体: {name}[/dim]" msgstr "" -#: src/app/mcp_widgets.py:181 +#: src/app/deployment/agent.py:1035 #, python-brace-format -msgid "请输入 {param_name}" +msgid "[green]成功创建 {count} 个智能体[/green]" msgstr "" -#: src/app/mcp_widgets.py:191 -msgid "补充说明(可选)" +#: src/app/deployment/agent.py:1040 +msgid "[red]未能创建任何智能体[/red]" msgstr "" -#: src/app/mcp_widgets.py:200 -msgid "✓ 提交" +#: src/app/deployment/agent.py:1053 +#, python-brace-format +msgid "[magenta]创建智能体: {name}[/magenta]" msgstr "" -#: src/app/settings.py:63 -msgid "设置" +#: src/app/deployment/agent.py:1066 +#, python-brace-format +msgid " [yellow]缺少 MCP 服务: {services},跳过[/yellow]" msgstr "" -#: src/app/settings.py:66 -msgid "后端:" +#: src/app/deployment/agent.py:1075 +#, python-brace-format +msgid " [yellow]智能体 {name} 没有可用的 MCP 服务,跳过[/yellow]" msgstr "" -#: src/app/settings.py:76 -msgid "Base URL:" +#: src/app/deployment/agent.py:1095 +#, python-brace-format +msgid " [red]创建智能体 {name} 失败[/red]" msgstr "" -#: src/app/settings.py:88 -msgid "API Key:" +#: src/app/deployment/agent.py:1103 +#, python-brace-format +msgid " [green]智能体 {name} 创建成功: {app_id}[/green]" msgstr "" -#: src/app/settings.py:95 -msgid "API 访问密钥,可选" +#: src/app/deployment/agent.py:1134 +msgid "[cyan]加载应用配置文件...[/cyan]" msgstr "" -#: src/app/settings.py:103 src/app/settings.py:239 -msgid "模型:" +#: src/app/deployment/agent.py:1139 +#, python-brace-format +msgid "[red]应用配置文件不存在: {path}[/red]" msgstr "" -#: src/app/settings.py:108 src/app/settings.py:244 -msgid "模型名称,可选" +#: src/app/deployment/agent.py:1153 +msgid "[yellow]配置文件中没有找到应用定义[/yellow]" msgstr "" -#: src/app/settings.py:117 src/app/settings.py:272 -msgid "MCP 工具授权:" +#: src/app/deployment/agent.py:1173 +#, python-brace-format +msgid " [red]应用配置缺少必需字段: {field}[/red]" msgstr "" -#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:274 -#: src/app/settings.py:549 src/app/settings.py:555 -msgid "自动执行" +#: src/app/deployment/agent.py:1180 +#, python-brace-format +msgid "加载应用配置文件失败: {path}" msgstr "" -#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:181 -#: src/app/settings.py:274 src/app/settings.py:549 src/app/settings.py:555 -msgid "手动确认" +#: src/app/deployment/agent.py:1187 +#, python-brace-format +msgid "[green]成功加载 {count} 个应用配置[/green]" +msgstr "" + +#: src/app/deployment/agent.py:1198 +msgid "[cyan]加载 MCP 配置文件...[/cyan]" +msgstr "" + +#: src/app/deployment/agent.py:1204 +msgid "[yellow]未找到 MCP 配置文件[/yellow]" +msgstr "" + +#: src/app/deployment/agent.py:1209 +#, python-brace-format +msgid "[green]成功加载 {count} 个 MCP 配置[/green]" +msgstr "" + +#: src/app/deployment/agent.py:1227 +#, python-brace-format +msgid " [red]MCP 服务 {name} SSE Endpoint 验证失败[/red]" +msgstr "" + +#: src/app/deployment/agent.py:1245 +#, python-brace-format +msgid " [red]{name} 处理失败: {error}[/red]" +msgstr "" + +#: src/app/deployment/agent.py:1260 +#, python-brace-format +msgid " [blue]注册 {name}...[/blue]" +msgstr "" + +#: src/app/deployment/agent.py:1273 +#, python-brace-format +msgid " [cyan]安装 {name} (ID: {service_id})...[/cyan]" +msgstr "" + +#: src/app/deployment/agent.py:1278 +#, python-brace-format +msgid " [dim]等待 {name} 安装完成...[/dim]" +msgstr "" + +#: src/app/deployment/agent.py:1280 +#, python-brace-format +msgid " [red]{name} 安装超时[/red]" +msgstr "" + +#: src/app/deployment/agent.py:1293 +#, python-brace-format +msgid " [yellow]激活 {name}...[/yellow]" msgstr "" -#: src/app/settings.py:133 +#: src/app/deployment/agent.py:1295 +#, python-brace-format +msgid " [green]{name} 处理完成[/green]" +msgstr "" + +#: src/app/deployment/agent.py:1307 +#, python-brace-format +msgid "[magenta]验证 SSE Endpoint: {name} -> {url}[/magenta]" +msgstr "" + +#: src/app/deployment/agent.py:1320 src/app/deployment/agent.py:1330 +#, python-brace-format +msgid " [green]{name} SSE Endpoint 验证通过[/green]" +msgstr "" + +#: src/app/deployment/agent.py:1343 +#, python-brace-format +msgid " [red]{name} SSE Endpoint 验证失败: 30秒内无法连接[/red]" +msgstr "" + +#: src/app/settings.py:57 +msgid "设置" +msgstr "" + +#: src/app/settings.py:64 src/app/dialogs/user.py:179 msgid "保存" msgstr "" -#: src/app/settings.py:134 src/app/dialogs/common.py:27 -msgid "取消" +#: src/app/settings.py:174 +msgid "后端:" msgstr "" -#: src/app/settings.py:437 -msgid "Base URL 不能为空" +#: src/app/settings.py:184 +msgid "Base URL:" +msgstr "" + +#: src/app/settings.py:196 +msgid "API Key:" +msgstr "" + +#: src/app/settings.py:203 +msgid "API 访问密钥,可选" +msgstr "" + +#: src/app/settings.py:213 +msgid "模型:" +msgstr "" + +#: src/app/settings.py:218 +msgid "模型名称,可选" +msgstr "" + +#: src/app/settings.py:228 +msgid "用户设置:" +msgstr "" + +#: src/app/settings.py:230 +msgid "更改用户设置" msgstr "" -#: src/app/settings.py:534 -msgid "切换中..." +#: src/app/settings.py:407 +msgid "Base URL 不能为空" msgstr "" -#: src/app/tui.py:217 +#: src/app/tui.py:216 msgid "Enter command or question..." msgstr "" -#: src/app/tui.py:226 +#: src/app/tui.py:225 msgid "Quit" msgstr "" -#: src/app/tui.py:227 +#: src/app/tui.py:226 msgid "Settings" msgstr "" -#: src/app/tui.py:228 +#: src/app/tui.py:227 msgid "Reset" msgstr "" -#: src/app/tui.py:229 +#: src/app/tui.py:228 msgid "Agent" msgstr "" -#: src/app/tui.py:230 +#: src/app/tui.py:229 msgid "Cancel" msgstr "" -#: src/app/tui.py:231 +#: src/app/tui.py:230 msgid "Focus" msgstr "" -#: src/app/tui.py:255 +#: src/app/tui.py:254 #, python-brace-format msgid "Intelligent CLI Assistant {version}" msgstr "" -#: src/app/tui.py:371 +#: src/app/tui.py:372 msgid "[Cancelled]" msgstr "" -#: src/app/tui.py:576 +#: src/app/tui.py:588 msgid "" "No response received, please check network connection or try again later" msgstr "" -#: src/app/tui.py:684 +#: src/app/tui.py:696 msgid "Request timeout, processing stopped" msgstr "" -#: src/app/tui.py:691 +#: src/app/tui.py:703 msgid "No response for a long time, processing stopped" msgstr "" -#: src/app/tui.py:744 src/app/tui.py:902 +#: src/app/tui.py:756 src/app/tui.py:914 msgid "Request timeout, please try again later" msgstr "" -#: src/app/tui.py:888 +#: src/app/tui.py:900 #, python-brace-format msgid "Server error: {message}" msgstr "" -#: src/app/tui.py:890 +#: src/app/tui.py:902 #, python-brace-format msgid "Request failed: {message}" msgstr "" -#: src/app/tui.py:894 +#: src/app/tui.py:906 msgid "Network connection interrupted, please check network and try again" msgstr "" -#: src/app/tui.py:906 +#: src/app/tui.py:918 msgid "Network connection error, please check network and try again" msgstr "" -#: src/app/tui.py:915 src/app/tui.py:949 +#: src/app/tui.py:927 src/app/tui.py:961 msgid "Server response error, please try again later" msgstr "" -#: src/app/tui.py:920 +#: src/app/tui.py:932 msgid "Data format error, please try again later" msgstr "" -#: src/app/tui.py:927 +#: src/app/tui.py:939 msgid "Authentication failed, please check configuration" msgstr "" -#: src/app/tui.py:951 +#: src/app/tui.py:963 #, python-brace-format msgid "Error processing command: {error}" msgstr "" -#: src/app/tui.py:1170 +#: src/app/tui.py:1088 src/app/tui.py:1114 src/app/tui.py:1296 +#: src/app/tui.py:1457 src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 +#: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 +#: src/app/dialogs/agent.py:208 src/tool/oi_select_agent.py:67 +#: src/tool/oi_select_agent.py:94 +msgid "智能问答" +msgstr "" + +#: src/app/tui.py:1214 msgid "💡 MCP response sent" msgstr "" -#: src/app/tui.py:1173 src/app/tui.py:1202 +#: src/app/tui.py:1217 src/app/tui.py:1246 msgid "❌ Current client does not support MCP response" msgstr "" -#: src/app/tui.py:1182 +#: src/app/tui.py:1226 #, python-brace-format msgid "❌ Failed to send MCP response: {error}" msgstr "" -#: src/app/tui.py:1249 -#, python-brace-format -msgid "⏱️ MCP response timeout ({seconds} seconds)" -msgstr "" - -#: src/app/tui.py:1253 +#: src/app/tui.py:1285 msgid "🚫 MCP response cancelled" msgstr "" -#: src/app/tui.py:1336 +#: src/app/tui.py:1367 msgid "Backend configuration validation failed, please check and modify" msgstr "" -#: src/app/tui.py:1337 +#: src/app/tui.py:1368 msgid "Configuration Error" msgstr "" +#: src/app/dialogs/user.py:90 +msgid "常规设置" +msgstr "" + +#: src/app/dialogs/user.py:91 +msgid "大模型设置" +msgstr "" + +#: src/app/dialogs/user.py:133 +msgid "用户名:" +msgstr "" + +#: src/app/dialogs/user.py:142 +msgid "未登录" +msgstr "" + +#: src/app/dialogs/user.py:150 +msgid "管理员" +msgstr "" + +#: src/app/dialogs/user.py:150 +msgid "普通用户" +msgstr "" + +#: src/app/dialogs/user.py:164 +msgid "MCP 工具授权:" +msgstr "" + +#: src/app/dialogs/user.py:166 src/app/dialogs/user.py:374 +msgid "自动执行" +msgstr "" + +#: src/app/dialogs/user.py:166 src/app/dialogs/user.py:374 +msgid "手动确认" +msgstr "" + +#: src/app/dialogs/user.py:214 +msgid "加载模型失败" +msgstr "" + +#: src/app/dialogs/user.py:216 +msgid "加载中..." +msgstr "" + +#: src/app/dialogs/user.py:219 +msgid "暂无可用模型" +msgstr "" + +#: src/app/dialogs/user.py:255 +msgid "↑↓: 选择模型 空格: 激活 回车: 保存 ESC: 取消" +msgstr "" + +#: src/app/dialogs/user.py:273 +msgid "描述: " +msgstr "" + +#: src/app/dialogs/user.py:284 +msgid "类型: " +msgstr "" + +#: src/app/dialogs/user.py:294 +msgid "最大 Token: " +msgstr "" + #: src/app/dialogs/common.py:25 msgid "确认退出吗?" msgstr "" @@ -263,13 +1520,6 @@ msgstr "" msgid "按任意键关闭" msgstr "" -#: src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 -#: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 -#: src/app/dialogs/agent.py:208 src/tool/oi_select_agent.py:67 -#: src/tool/oi_select_agent.py:94 -msgid "智能问答" -msgstr "" - #: src/app/dialogs/agent.py:113 msgid "OS 智能助手" msgstr "" @@ -278,6 +1528,34 @@ msgstr "" msgid "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" msgstr "" +#: src/backend/hermes/client.py:395 +msgid "未配置 Chat 模型" +msgstr "" + +#: src/backend/hermes/client.py:396 +msgid "配置步骤" +msgstr "" + +#: src/backend/hermes/client.py:397 +msgid "按 Ctrl+S 打开设置" +msgstr "" + +#: src/backend/hermes/client.py:398 +msgid "确认后端为 openEuler Intelligence" +msgstr "" + +#: src/backend/hermes/client.py:399 +msgid "点击 \"更改用户设置\" 按钮" +msgstr "" + +#: src/backend/hermes/client.py:400 +msgid "切换到 \"大模型设置\" 标签页" +msgstr "" + +#: src/backend/hermes/client.py:401 +msgid "使用 ↑↓ 键选择模型,空格激活,回车保存" +msgstr "" + #: src/backend/hermes/mcp_helpers.py:44 msgid "正在初始化工具" msgstr "" @@ -318,11 +1596,11 @@ msgstr "" msgid "说明" msgstr "" -#: src/main.py:28 +#: src/main.py:27 msgid "openEuler Intelligence - Intelligent command-line tool" msgstr "" -#: src/main.py:29 +#: src/main.py:28 msgid "" "\n" "For more information and documentation, please visit:\n" @@ -330,143 +1608,151 @@ msgid "" " " msgstr "" -#: src/main.py:39 +#: src/main.py:38 msgid "General Options" msgstr "" -#: src/main.py:40 +#: src/main.py:39 msgid "Show help and version information" msgstr "" -#: src/main.py:46 +#: src/main.py:45 msgid "Show this help message and exit" msgstr "" -#: src/main.py:53 +#: src/main.py:52 msgid "Show program version number and exit" msgstr "" -#: src/main.py:58 +#: src/main.py:57 msgid "Backend Configuration Options" msgstr "" -#: src/main.py:59 +#: src/main.py:58 msgid "" "For initializing and configuring openEuler Intelligence backend services" msgstr "" -#: src/main.py:65 +#: src/main.py:64 msgid "" "Initialize openEuler Intelligence backend\n" " * Initialization requires administrator privileges and network connection" msgstr "" -#: src/main.py:73 +#: src/main.py:72 msgid "" "Change openEuler Intelligence LLM settings (requires valid local backend " "service)\n" " * Configuration editing requires administrator privileges" msgstr "" -#: src/main.py:80 +#: src/main.py:79 msgid "Application Configuration Options" msgstr "" -#: src/main.py:81 +#: src/main.py:80 msgid "For configuring application frontend behavior and preferences" msgstr "" -#: src/main.py:86 +#: src/main.py:85 msgid "Select default agent" msgstr "" +#: src/main.py:90 +msgid "Authentication Management Options" +msgstr "" + #: src/main.py:91 +msgid "For managing login and authentication" +msgstr "" + +#: src/main.py:96 +msgid "Login via browser to obtain API key" +msgstr "" + +#: src/main.py:101 msgid "Language Settings" msgstr "" -#: src/main.py:92 +#: src/main.py:102 msgid "For configuring application display language" msgstr "" -#: src/main.py:100 +#: src/main.py:110 #, python-brace-format msgid "Set display language (available: {locales})" msgstr "" -#: src/main.py:105 +#: src/main.py:115 msgid "Log Management Options" msgstr "" -#: src/main.py:106 +#: src/main.py:116 msgid "For viewing and configuring log output" msgstr "" -#: src/main.py:111 +#: src/main.py:121 msgid "Show latest log content (up to 1000 lines)" msgstr "" -#: src/main.py:117 +#: src/main.py:127 msgid "Set log level (available: DEBUG, INFO, WARNING, ERROR)" msgstr "" -#: src/main.py:140 +#: src/main.py:150 #, python-brace-format msgid "Failed to retrieve logs: {error}\n" msgstr "" -#: src/main.py:147 +#: src/main.py:157 #, python-brace-format msgid "Invalid log level: {level}\n" msgstr "" -#: src/main.py:156 +#: src/main.py:166 #, python-format msgid "Log level has been set to: %s" msgstr "" -#: src/main.py:157 +#: src/main.py:167 msgid "This is a DEBUG level test message" msgstr "" -#: src/main.py:158 +#: src/main.py:168 msgid "This is an INFO level test message" msgstr "" -#: src/main.py:159 +#: src/main.py:169 msgid "This is a WARNING level test message" msgstr "" -#: src/main.py:160 +#: src/main.py:170 msgid "This is an ERROR level test message" msgstr "" -#: src/main.py:162 +#: src/main.py:172 #, python-brace-format msgid "✓ Log level successfully set to: {level}\n" msgstr "" -#: src/main.py:163 +#: src/main.py:173 msgid "✓ Logging system initialized\n" msgstr "" -#: src/main.py:191 +#: src/main.py:201 #, python-brace-format msgid "✓ Language set to: {locale}\n" msgstr "" -#: src/main.py:193 +#: src/main.py:203 #, python-brace-format msgid "✗ Unsupported language: {locale}\n" msgstr "" -#: src/main.py:228 +#: src/main.py:246 msgid "Fatal error in Intelligent Shell application" msgstr "" -#: src/tool/oi_select_agent.py:29 -msgid "退出" -msgstr "" - #: src/tool/oi_select_agent.py:123 #, python-brace-format msgid "✓ 默认智能体已设置为: {name}\n" @@ -536,198 +1822,202 @@ msgstr "" msgid "权限不足:无法访问配置文件 {filename},请以管理员身份运行" msgstr "" -#: src/tool/validators.py:135 src/tool/validators.py:584 -#: src/tool/validators.py:647 +#: src/tool/oi_backend_init.py:39 +msgid "openEuler Intelligence 部署助手" +msgstr "" + +#: src/tool/validators.py:165 src/tool/validators.py:614 +#: src/tool/validators.py:677 #, python-brace-format msgid "连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}" msgstr "" -#: src/tool/validators.py:140 +#: src/tool/validators.py:170 #, python-brace-format msgid "LLM 配置验证失败: {error}" msgstr "" -#: src/tool/validators.py:144 +#: src/tool/validators.py:174 msgid "LLM 配置验证成功" msgstr "" -#: src/tool/validators.py:146 +#: src/tool/validators.py:176 #, python-brace-format msgid " - 支持工具调用,类型: {func_type}" msgstr "" -#: src/tool/validators.py:148 +#: src/tool/validators.py:178 msgid " - 不支持工具调用" msgstr "" -#: src/tool/validators.py:201 +#: src/tool/validators.py:231 msgid "无法连接到 Embedding 模型服务。" msgstr "" -#: src/tool/validators.py:241 +#: src/tool/validators.py:271 msgid "基本对话测试失败" msgstr "" -#: src/tool/validators.py:244 +#: src/tool/validators.py:274 msgid "基本对话功能正常" msgstr "" -#: src/tool/validators.py:246 +#: src/tool/validators.py:276 msgid "对话响应为空" msgstr "" -#: src/tool/validators.py:318 +#: src/tool/validators.py:348 msgid "不支持任何 function_call 格式" msgstr "" -#: src/tool/validators.py:353 +#: src/tool/validators.py:383 #, python-brace-format msgid "tools 格式测试失败: {error}" msgstr "" -#: src/tool/validators.py:358 +#: src/tool/validators.py:388 msgid "支持 tools 格式的 function_call" msgstr "" -#: src/tool/validators.py:360 +#: src/tool/validators.py:390 msgid "不支持工具调用功能" msgstr "" -#: src/tool/validators.py:401 +#: src/tool/validators.py:431 #, python-brace-format msgid "structured_output 格式测试失败: {error}" msgstr "" -#: src/tool/validators.py:409 +#: src/tool/validators.py:439 msgid "structured_output 响应不是有效 JSON" msgstr "" -#: src/tool/validators.py:411 +#: src/tool/validators.py:441 msgid "支持 structured_output 格式" msgstr "" -#: src/tool/validators.py:413 +#: src/tool/validators.py:443 msgid "structured_output 响应为空" msgstr "" -#: src/tool/validators.py:439 +#: src/tool/validators.py:469 #, python-brace-format msgid "json_mode 格式测试失败: {error}" msgstr "" -#: src/tool/validators.py:447 +#: src/tool/validators.py:477 msgid "json_mode 响应不是有效 JSON" msgstr "" -#: src/tool/validators.py:449 +#: src/tool/validators.py:479 msgid "支持 json_mode 格式" msgstr "" -#: src/tool/validators.py:451 +#: src/tool/validators.py:481 msgid "json_mode 响应为空" msgstr "" -#: src/tool/validators.py:499 +#: src/tool/validators.py:529 msgid "支持 vLLM 结构化输出(部分支持)" msgstr "" -#: src/tool/validators.py:504 +#: src/tool/validators.py:534 #, python-brace-format msgid "不支持 vLLM guided_json 格式: {error}" msgstr "" -#: src/tool/validators.py:508 +#: src/tool/validators.py:538 msgid "vLLM guided_json 响应无效" msgstr "" -#: src/tool/validators.py:555 +#: src/tool/validators.py:585 msgid "支持 Ollama function_call 格式" msgstr "" -#: src/tool/validators.py:558 +#: src/tool/validators.py:588 #, python-brace-format msgid "不支持 Ollama function_call 格式: {error}" msgstr "" -#: src/tool/validators.py:561 +#: src/tool/validators.py:591 msgid "Ollama function_call 响应无效" msgstr "" -#: src/tool/validators.py:589 +#: src/tool/validators.py:619 #, python-brace-format msgid "OpenAI Embedding 配置验证失败: {error}" msgstr "" -#: src/tool/validators.py:598 +#: src/tool/validators.py:628 #, python-brace-format msgid "OpenAI Embedding 配置验证成功 - 维度: {dimension}" msgstr "" -#: src/tool/validators.py:606 +#: src/tool/validators.py:636 msgid "OpenAI Embedding 响应为空" msgstr "" -#: src/tool/validators.py:634 +#: src/tool/validators.py:664 #, python-brace-format msgid "MindIE Embedding 配置验证成功 - 维度: {dimension}" msgstr "" -#: src/tool/validators.py:644 +#: src/tool/validators.py:674 msgid "MindIE Embedding 响应格式不正确" msgstr "" -#: src/tool/validators.py:652 +#: src/tool/validators.py:682 #, python-brace-format msgid "MindIE Embedding 配置验证失败: {error}" msgstr "" -#: src/tool/validators.py:674 +#: src/tool/validators.py:704 msgid "服务 URL 必须以 http:// 或 https:// 开头" msgstr "" -#: src/tool/validators.py:685 +#: src/tool/validators.py:715 msgid "访问令牌格式无效" msgstr "" -#: src/tool/validators.py:710 +#: src/tool/validators.py:740 msgid "服务返回的数据格式不正确" msgstr "" -#: src/tool/validators.py:716 +#: src/tool/validators.py:746 msgid "连接成功" msgstr "" -#: src/tool/validators.py:718 +#: src/tool/validators.py:748 #, python-brace-format msgid "服务返回错误代码: {code}" msgstr "" -#: src/tool/validators.py:721 +#: src/tool/validators.py:751 msgid "无法连接到服务,请检查 URL 和网络连接" msgstr "" -#: src/tool/validators.py:723 +#: src/tool/validators.py:753 msgid "连接超时,请检查网络连接或服务状态" msgstr "" -#: src/tool/validators.py:726 +#: src/tool/validators.py:756 #, python-brace-format msgid "连接验证失败: {error}" msgstr "" -#: src/tool/validators.py:732 +#: src/tool/validators.py:762 msgid "访问令牌无效或已过期" msgstr "" -#: src/tool/validators.py:733 +#: src/tool/validators.py:763 msgid "访问权限不足" msgstr "" -#: src/tool/validators.py:734 +#: src/tool/validators.py:764 msgid "API 接口不存在,请检查服务版本" msgstr "" -#: src/tool/validators.py:737 +#: src/tool/validators.py:767 #, python-brace-format msgid "服务响应异常,状态码: {status_code}" msgstr "" @@ -773,3 +2063,95 @@ msgstr "" #: src/tool/command_processor.py:206 msgid "读取 stderr 失败" msgstr "" + +#: src/tool/oi_login.py:67 +#, python-brace-format +msgid "✗ Failed to get authorization URL: {error}\n" +msgstr "" + +#: src/tool/oi_login.py:136 +msgid "✗ Error: Browser is not available in current environment\n" +msgstr "" + +#: src/tool/oi_login.py:137 +msgid "This feature requires a graphical environment with browser support.\n" +msgstr "" + +#: src/tool/oi_login.py:138 +msgid "If you are using SSH, please run this command on the server directly\n" +msgstr "" + +#: src/tool/oi_login.py:139 +msgid "or use X11 forwarding / VNC to enable graphical access.\n" +msgstr "" + +#: src/tool/oi_login.py:151 +msgid "" +"\n" +"\n" +"✗ Login cancelled by user\n" +msgstr "" + +#: src/tool/oi_login.py:155 +#, python-brace-format +msgid "" +"\n" +"✗ An error occurred during login: {error}\n" +msgstr "" + +#: src/tool/oi_login.py:169 +msgid "✗ Error: openEuler Intelligence URL not configured\n" +msgstr "" + +#: src/tool/oi_login.py:170 +msgid "Please run deployment initialization first: oi --init\n" +msgstr "" + +#: src/tool/oi_login.py:181 +msgid "Getting authorization URL from server...\n" +msgstr "" + +#: src/tool/oi_login.py:185 +msgid "✗ Failed to get authorization URL\n" +msgstr "" + +#: src/tool/oi_login.py:194 +msgid "Opening browser for login...\n" +msgstr "" + +#: src/tool/oi_login.py:195 +msgid "If the browser doesn't open automatically, please visit:\n" +msgstr "" + +#: src/tool/oi_login.py:202 +msgid "Waiting for login to complete...\n" +msgstr "" + +#: src/tool/oi_login.py:215 +msgid "" +"\n" +"✓ Login successful!\n" +msgstr "" + +#: src/tool/oi_login.py:216 +msgid "✓ API Key has been saved to configuration\n" +msgstr "" + +#: src/tool/oi_login.py:219 +msgid "" +"\n" +"✗ Login failed: No session ID received\n" +msgstr "" + +#: src/tool/oi_login.py:223 +#, python-brace-format +msgid "" +"\n" +"✗ Login failed: {error}\n" +msgstr "" + +#: src/tool/oi_login.py:226 +msgid "" +"\n" +"✗ Login failed: Unknown result\n" +msgstr "" diff --git a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po index c0926a41829e5e31f074479285352f96dc871228..eb1814fd136fae5db56d0fd45566740d3e6803ca 100644 --- a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po +++ b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: oi-cli\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-21 10:54+0800\n" +"POT-Creation-Date: 2025-11-04 17:40+0800\n" "PO-Revision-Date: 2025-10-21 09:40+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: Chinese (Simplified)\n" @@ -17,232 +17,1512 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.8\n" -#: src/app/mcp_widgets.py:47 -msgid "需要用户确认是否执行此工具" -msgstr "需要用户确认是否执行此工具" +#: src/app/mcp_widgets.py:50 src/backend/hermes/mcp_helpers.py:286 +msgid "低风险" +msgstr "低风险" + +#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:287 +msgid "中等风险" +msgstr "中等风险" + +#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:288 +msgid "高风险" +msgstr "高风险" + +#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:289 +msgid "未知风险" +msgstr "未知风险" + +#: src/app/mcp_widgets.py:66 +msgid "✓ 确认" +msgstr "✓ 确认" + +#: src/app/mcp_widgets.py:67 src/app/mcp_widgets.py:170 +msgid "✗ 取消" +msgstr "✗ 取消" + +#: src/app/mcp_widgets.py:153 +msgid "📝 参数输入" +msgstr "📝 参数输入" + +#: src/app/mcp_widgets.py:160 +#, python-brace-format +msgid "请输入 {param_name}" +msgstr "请输入 {param_name}" + +#: src/app/mcp_widgets.py:169 +msgid "✓ 提交" +msgstr "✓ 提交" + +#: src/app/deployment/service.py:66 +#, python-brace-format +msgid "无法读取模板文件: {path}" +msgstr "无法读取模板文件: {path}" + +#: src/app/deployment/service.py:138 +#, python-brace-format +msgid "TOML 格式错误: {error}" +msgstr "TOML 格式错误: {error}" + +#: src/app/deployment/service.py:142 +#, python-brace-format +msgid "更新 TOML 配置失败: {error}" +msgstr "更新 TOML 配置失败: {error}" + +#: src/app/deployment/service.py:188 +msgid "检查部署依赖" +msgstr "检查部署依赖" + +#: src/app/deployment/service.py:189 +msgid "正在检查部署环境依赖..." +msgstr "正在检查部署环境依赖..." + +#: src/app/deployment/service.py:194 +#: src/app/deployment/components/env_check.py:141 +msgid "仅支持 openEuler 操作系统" +msgstr "仅支持 openEuler 操作系统" + +#: src/app/deployment/service.py:201 +#, python-brace-format +msgid "⚠ 检测到 Python {version},低于 3.10 版本将不支持全量部署模式" +msgstr "⚠ 检测到 Python {version},低于 3.10 版本将不支持全量部署模式" + +#: src/app/deployment/service.py:210 +msgid "缺少 openeuler-intelligence-installer 包,正在尝试安装..." +msgstr "缺少 openeuler-intelligence-installer 包,正在尝试安装..." + +#: src/app/deployment/service.py:220 +#: src/app/deployment/components/env_check.py:164 +msgid "需要管理员权限,请确保可以使用 sudo" +msgstr "需要管理员权限,请确保可以使用 sudo" + +#: src/app/deployment/service.py:224 +msgid "✓ 部署环境依赖检查完成" +msgstr "✓ 部署环境依赖检查完成" + +#: src/app/deployment/service.py:271 +msgid "" +"当前 openEuler 版本低于 24.03 LTS,不支持全量部署模式。请使用轻量部署模式或升" +"级到 openEuler 24.03+ 版本" +msgstr "" +"当前 openEuler 版本低于 24.03 LTS,不支持全量部署模式。请使用轻量部署模式或升" +"级到 openEuler 24.03+ 版本" + +#: src/app/deployment/service.py:277 +#, python-brace-format +msgid "无法检查 Python 环境: {error}" +msgstr "无法检查 Python 环境: {error}" + +#: src/app/deployment/service.py:280 +#, python-brace-format +msgid "Python 环境版本 {version} 符合要求" +msgstr "Python 环境版本 {version} 符合要求" + +#: src/app/deployment/service.py:336 +msgid "部署过程中发生异常" +msgstr "部署过程中发生异常" + +#: src/app/deployment/service.py:337 +msgid "✗ 部署失败" +msgstr "✗ 部署失败" + +#: src/app/deployment/service.py:347 +msgid "✓ openEuler Intelligence 后端部署完成!" +msgstr "✓ openEuler Intelligence 后端部署完成!" + +#: src/app/deployment/service.py:385 +msgid "正在安装 openeuler-intelligence-installer..." +msgstr "正在安装 openeuler-intelligence-installer..." + +#: src/app/deployment/service.py:396 +msgid "✓ openeuler-intelligence-installer 安装成功" +msgstr "✓ openeuler-intelligence-installer 安装成功" + +#: src/app/deployment/service.py:400 +msgid "openeuler-intelligence-installer 安装后资源文件仍然缺失" +msgstr "openeuler-intelligence-installer 安装后资源文件仍然缺失" + +#: src/app/deployment/service.py:403 +msgid "安装 openeuler-intelligence-installer 失败" +msgstr "安装 openeuler-intelligence-installer 失败" + +#: src/app/deployment/service.py:406 +msgid "安装输出:" +msgstr "安装输出:" + +#: src/app/deployment/service.py:410 +#, python-brace-format +msgid "安装过程中发生异常: {error}" +msgstr "安装过程中发生异常: {error}" + +#: src/app/deployment/service.py:446 +msgid "✓ 基础服务部署完成" +msgstr "✓ 基础服务部署完成" + +#: src/app/deployment/service.py:447 +msgid "请访问网页管理界面完成 Agent 服务配置" +msgstr "请访问网页管理界面完成 Agent 服务配置" + +#: src/app/deployment/service.py:448 +#, python-brace-format +msgid "管理界面地址: http://{ip}:8080" +msgstr "管理界面地址: http://{ip}:8080" + +#: src/app/deployment/service.py:485 src/app/deployment/service.py:575 +msgid "检查系统环境" +msgstr "检查系统环境" + +#: src/app/deployment/service.py:486 +msgid "正在检查系统环境..." +msgstr "正在检查系统环境..." + +#: src/app/deployment/service.py:493 +msgid "✗ 错误: 仅支持 openEuler 操作系统" +msgstr "✗ 错误: 仅支持 openEuler 操作系统" + +#: src/app/deployment/service.py:495 +msgid "✓ 检测到 openEuler 操作系统" +msgstr "✓ 检测到 openEuler 操作系统" + +#: src/app/deployment/service.py:500 +#, python-brace-format +msgid "✗ 错误: {msg}" +msgstr "✗ 错误: {msg}" + +#: src/app/deployment/service.py:505 +msgid "✗ 错误: openeuler-intelligence-installer 包未安装或资源缺失" +msgstr "✗ 错误: openeuler-intelligence-installer 包未安装或资源缺失" + +#: src/app/deployment/service.py:506 +msgid "请先安装: sudo dnf install -y openeuler-intelligence-installer" +msgstr "请先安装: sudo dnf install -y openeuler-intelligence-installer" + +#: src/app/deployment/service.py:508 +msgid "✓ openeuler-intelligence-installer 资源可用" +msgstr "✓ openeuler-intelligence-installer 资源可用" + +#: src/app/deployment/service.py:512 +msgid "✗ 错误: 需要管理员权限" +msgstr "✗ 错误: 需要管理员权限" + +#: src/app/deployment/service.py:514 +msgid "✓ 具有管理员权限" +msgstr "✓ 具有管理员权限" + +#: src/app/deployment/service.py:525 +msgid "初始化部署配置" +msgstr "初始化部署配置" + +#: src/app/deployment/service.py:526 +msgid "正在设置部署模式..." +msgstr "正在设置部署模式..." + +#: src/app/deployment/service.py:553 src/app/deployment/service.py:562 +#, python-brace-format +msgid "✗ 设置部署模式失败: {error}" +msgstr "✗ 设置部署模式失败: {error}" + +#: src/app/deployment/service.py:556 src/app/deployment/service.py:557 +msgid "启用" +msgstr "启用" + +#: src/app/deployment/service.py:556 src/app/deployment/service.py:557 +msgid "禁用" +msgstr "禁用" + +#: src/app/deployment/service.py:558 +#, python-brace-format +msgid "✓ 部署模式设置完成 (Web界面: {web}, RAG: {rag})" +msgstr "✓ 部署模式设置完成 (Web界面: {web}, RAG: {rag})" + +#: src/app/deployment/service.py:576 +msgid "正在执行系统环境检查..." +msgstr "正在执行系统环境检查..." + +#: src/app/deployment/service.py:583 +msgid "环境检查脚本" +msgstr "环境检查脚本" + +#: src/app/deployment/service.py:585 +#, python-brace-format +msgid "✗ 环境检查失败: {error}" +msgstr "✗ 环境检查失败: {error}" + +#: src/app/deployment/service.py:596 +msgid "安装依赖组件" +msgstr "安装依赖组件" + +#: src/app/deployment/service.py:597 +msgid "正在安装 openEuler Intelligence 依赖组件..." +msgstr "正在安装 openEuler Intelligence 依赖组件..." + +#: src/app/deployment/service.py:606 +msgid "依赖安装脚本" +msgstr "依赖安装脚本" + +#: src/app/deployment/service.py:608 +#, python-brace-format +msgid "✗ 依赖安装失败: {error}" +msgstr "✗ 依赖安装失败: {error}" + +#: src/app/deployment/service.py:619 +msgid "初始化配置和服务" +msgstr "初始化配置和服务" + +#: src/app/deployment/service.py:620 +msgid "正在初始化配置和启动服务..." +msgstr "正在初始化配置和启动服务..." + +#: src/app/deployment/service.py:627 +msgid "配置初始化脚本" +msgstr "配置初始化脚本" + +#: src/app/deployment/service.py:629 +#, python-brace-format +msgid "✗ 配置初始化失败: {error}" +msgstr "✗ 配置初始化失败: {error}" + +#: src/app/deployment/service.py:641 +#, python-brace-format +msgid "✗ 脚本文件不存在: {path}" +msgstr "✗ 脚本文件不存在: {path}" + +#: src/app/deployment/service.py:679 +#, python-brace-format +msgid "✓ {name}执行成功" +msgstr "✓ {name}执行成功" + +#: src/app/deployment/service.py:683 +#, python-brace-format +msgid "✗ 运行{name}时发生错误: {error}" +msgstr "✗ 运行{name}时发生错误: {error}" + +#: src/app/deployment/service.py:688 +#, python-brace-format +msgid "✗ {name}执行失败,返回码: {code}" +msgstr "✗ {name}执行失败,返回码: {code}" + +#: src/app/deployment/service.py:709 +msgid "更新配置文件" +msgstr "更新配置文件" + +#: src/app/deployment/service.py:710 +msgid "正在更新配置文件..." +msgstr "正在更新配置文件..." + +#: src/app/deployment/service.py:718 +msgid "✓ 更新 env 配置文件" +msgstr "✓ 更新 env 配置文件" + +#: src/app/deployment/service.py:722 +msgid "✓ 更新 config.toml 配置文件" +msgstr "✓ 更新 config.toml 配置文件" + +#: src/app/deployment/service.py:725 +#, python-brace-format +msgid "✗ 更新配置文件失败: {error}" +msgstr "✗ 更新配置文件失败: {error}" + +#: src/app/deployment/service.py:758 +#, python-brace-format +msgid "备份 env 文件失败: {error}" +msgstr "备份 env 文件失败: {error}" + +#: src/app/deployment/service.py:773 +#, python-brace-format +msgid "写入 env 文件失败: {error}" +msgstr "写入 env 文件失败: {error}" + +#: src/app/deployment/service.py:803 +#, python-brace-format +msgid "备份 config.toml 文件失败: {error}" +msgstr "备份 config.toml 文件失败: {error}" + +#: src/app/deployment/service.py:818 +#, python-brace-format +msgid "写入 config.toml 文件失败: {error}" +msgstr "写入 config.toml 文件失败: {error}" + +#: src/app/deployment/service.py:866 +#, python-brace-format +msgid "检查 oi-runtime 服务状态 ({current}/{total})..." +msgstr "检查 oi-runtime 服务状态 ({current}/{total})..." + +#: src/app/deployment/service.py:888 +msgid "✓ Framework 服务状态正常" +msgstr "✓ Framework 服务状态正常" + +#: src/app/deployment/service.py:891 +#, python-brace-format +msgid "Framework 服务状态: {status}" +msgstr "Framework 服务状态: {status}" + +#: src/app/deployment/service.py:894 +#, python-brace-format +msgid "等待 {seconds} 秒后重试..." +msgstr "等待 {seconds} 秒后重试..." + +#: src/app/deployment/service.py:898 +#, python-brace-format +msgid "检查服务状态时发生错误: {error}" +msgstr "检查服务状态时发生错误: {error}" + +#: src/app/deployment/service.py:902 +msgid "✗ Framework 服务状态检查超时失败" +msgstr "✗ Framework 服务状态检查超时失败" + +#: src/app/deployment/service.py:917 +msgid "等待 openEuler Intelligence 服务就绪" +msgstr "等待 openEuler Intelligence 服务就绪" + +#: src/app/deployment/service.py:929 +msgid "✓ openEuler Intelligence 服务已就绪" +msgstr "✓ openEuler Intelligence 服务已就绪" + +#: src/app/deployment/service.py:935 +#, python-brace-format +msgid "连接 {url} 超时" +msgstr "连接 {url} 超时" + +#: src/app/deployment/service.py:937 +#, python-brace-format +msgid "API 连通性检查时发生错误: {error}" +msgstr "API 连通性检查时发生错误: {error}" + +#: src/app/deployment/service.py:942 +msgid "✗ openEuler Intelligence API 服务检查超时失败" +msgstr "✗ openEuler Intelligence API 服务检查超时失败" + +#: src/app/deployment/service.py:952 +msgid "初始化 Agent 服务" +msgstr "初始化 Agent 服务" + +#: src/app/deployment/service.py:953 +msgid "正在检查 openEuler Intelligence 后端服务状态..." +msgstr "正在检查 openEuler Intelligence 后端服务状态..." + +#: src/app/deployment/service.py:964 +msgid "✗ openEuler Intelligence 服务检查失败" +msgstr "✗ openEuler Intelligence 服务检查失败" + +#: src/app/deployment/service.py:967 +msgid "✓ openEuler Intelligence 服务检查通过,开始初始化 Agent..." +msgstr "✓ openEuler Intelligence 服务检查通过,开始初始化 Agent..." + +#: src/app/deployment/service.py:977 +msgid "✓ Agent 初始化完成" +msgstr "✓ Agent 初始化完成" + +#: src/app/deployment/service.py:981 +msgid "⚠ Agent 初始化已跳过(RPM 包不可用),但部署将继续进行" +msgstr "⚠ Agent 初始化已跳过(RPM 包不可用),但部署将继续进行" + +#: src/app/deployment/service.py:1020 +msgid "✓ 全局配置模板创建成功,其他用户可正常使用" +msgstr "✓ 全局配置模板创建成功,其他用户可正常使用" + +#: src/app/deployment/service.py:1023 +msgid "⚠ 全局配置模板创建失败,可能影响其他用户使用" +msgstr "⚠ 全局配置模板创建失败,可能影响其他用户使用" + +#: src/app/deployment/service.py:1028 +msgid "⚠ 配置模板创建异常,可能影响其他用户使用" +msgstr "⚠ 配置模板创建异常,可能影响其他用户使用" + +#: src/app/deployment/models.py:121 src/app/deployment/models.py:179 +msgid "LLM API 端点不能为空" +msgstr "LLM API 端点不能为空" + +#: src/app/deployment/models.py:150 src/app/deployment/models.py:199 +#: src/app/deployment/models.py:202 +msgid "Embedding API 端点不能为空" +msgstr "Embedding API 端点不能为空" + +#: src/app/deployment/models.py:172 +msgid "服务器 IP 地址不能为空" +msgstr "服务器 IP 地址不能为空" + +#: src/app/deployment/models.py:210 +msgid "LLM max_tokens 必须大于 0" +msgstr "LLM max_tokens 必须大于 0" + +#: src/app/deployment/models.py:213 +#, python-brace-format +msgid "LLM temperature 必须在 {min} 到 {max} 之间" +msgstr "LLM temperature 必须在 {min} 到 {max} 之间" + +#: src/app/deployment/models.py:219 +msgid "LLM 请求超时时间必须大于 0" +msgstr "LLM 请求超时时间必须大于 0" + +#: src/app/deployment/ui.py:141 src/app/deployment/ui.py:199 +msgid "基础配置" +msgstr "基础配置" + +#: src/app/deployment/ui.py:144 +msgid "LLM 配置" +msgstr "LLM 配置" + +#: src/app/deployment/ui.py:147 +msgid "Embedding 配置" +msgstr "Embedding 配置" + +#: src/app/deployment/ui.py:151 +msgid "开始部署" +msgstr "开始部署" + +#: src/app/deployment/ui.py:152 src/app/settings.py:65 +#: src/app/dialogs/user.py:180 src/app/dialogs/common.py:27 +msgid "取消" +msgstr "取消" + +#: src/app/deployment/ui.py:169 +msgid "全量部署" +msgstr "全量部署" + +#: src/app/deployment/ui.py:170 +msgid "全量部署:部署框架服务 + Web 界面 + RAG 组件,自动初始化 Agent。" +msgstr "全量部署:部署框架服务 + Web 界面 + RAG 组件,自动初始化 Agent。" + +#: src/app/deployment/ui.py:172 src/app/deployment/ui.py:213 +msgid "轻量部署" +msgstr "轻量部署" + +#: src/app/deployment/ui.py:173 src/app/deployment/ui.py:218 +msgid "轻量部署:仅部署框架服务,自动初始化 Agent。" +msgstr "轻量部署:仅部署框架服务,自动初始化 Agent。" + +#: src/app/deployment/ui.py:189 +msgid "[dim]不需要验证[/dim]" +msgstr "[dim]不需要验证[/dim]" + +#: src/app/deployment/ui.py:202 +msgid "服务器 IP 地址:" +msgstr "服务器 IP 地址:" + +#: src/app/deployment/ui.py:205 +msgid "例如:127.0.0.1" +msgstr "例如:127.0.0.1" + +#: src/app/deployment/ui.py:211 +msgid "部署模式:" +msgstr "部署模式:" + +#: src/app/deployment/ui.py:226 +msgid "大语言模型配置" +msgstr "大语言模型配置" + +#: src/app/deployment/ui.py:229 src/app/deployment/ui.py:294 +msgid "API 端点:" +msgstr "API 端点:" + +#: src/app/deployment/ui.py:231 src/app/deployment/ui.py:296 +msgid "例如:http://localhost:11434/v1" +msgstr "例如:http://localhost:11434/v1" + +#: src/app/deployment/ui.py:237 src/app/deployment/ui.py:302 +msgid "API 密钥:" +msgstr "API 密钥:" + +#: src/app/deployment/ui.py:246 src/app/deployment/ui.py:311 +msgid "模型名称:" +msgstr "模型名称:" + +#: src/app/deployment/ui.py:248 +msgid "例如:deepseek-llm-7b-chat" +msgstr "例如:deepseek-llm-7b-chat" + +#: src/app/deployment/ui.py:254 src/app/deployment/ui.py:319 +msgid "验证状态:" +msgstr "验证状态:" + +#: src/app/deployment/ui.py:255 src/app/deployment/ui.py:320 +#: src/app/deployment/components/modes.py:204 +msgid "未验证" +msgstr "未验证" + +#: src/app/deployment/ui.py:258 +msgid "最大输出令牌数:" +msgstr "最大输出令牌数:" + +#: src/app/deployment/ui.py:266 +msgid "Temperature:" +msgstr "Temperature:" + +#: src/app/deployment/ui.py:274 +msgid "请求超时 (秒):" +msgstr "请求超时 (秒):" + +#: src/app/deployment/ui.py:284 +msgid "嵌入模型配置" +msgstr "嵌入模型配置" + +#: src/app/deployment/ui.py:288 +msgid "[dim]轻量部署模式下,Embedding 配置为可选项。[/dim]" +msgstr "[dim]轻量部署模式下,Embedding 配置为可选项。[/dim]" + +#: src/app/deployment/ui.py:313 +msgid "例如:bge-m3" +msgstr "例如:bge-m3" + +#: src/app/deployment/ui.py:330 +msgid "配置验证失败" +msgstr "配置验证失败" + +#: src/app/deployment/ui.py:369 +msgid "" +"[dim]轻量部署模式下,Embedding 配置为可选项。如果不填写,将跳过 RAG 功能。[/" +"dim]" +msgstr "" +"[dim]轻量部署模式下,Embedding 配置为可选项。如果不填写,将跳过 RAG 功能。[/" +"dim]" + +#: src/app/deployment/ui.py:373 +msgid "[dim]全量部署模式下,Embedding 配置为必填项,用于支持 RAG 功能。[/dim]" +msgstr "[dim]全量部署模式下,Embedding 配置为必填项,用于支持 RAG 功能。[/dim]" + +#: src/app/deployment/ui.py:518 src/app/deployment/components/modes.py:390 +#, python-brace-format +msgid "[green]✓ {message}[/green]" +msgstr "[green]✓ {message}[/green]" + +#: src/app/deployment/ui.py:521 +msgid "[red]✗ 不支持工具调用[/red]" +msgstr "[red]✗ 不支持工具调用[/red]" + +#: src/app/deployment/ui.py:523 +msgid "" +"LLM 验证失败:模型不支持工具调用功能,无法用于部署。请选择支持工具调用的模" +"型。" +msgstr "" +"LLM 验证失败:模型不支持工具调用功能,无法用于部署。请选择支持工具调用的模" +"型。" + +#: src/app/deployment/ui.py:528 src/app/deployment/ui.py:562 +#: src/app/deployment/components/modes.py:394 +#, python-brace-format +msgid "[red]✗ {message}[/red]" +msgstr "[red]✗ {message}[/red]" + +#: src/app/deployment/ui.py:532 src/app/deployment/ui.py:566 +#: src/app/deployment/components/modes.py:399 +#, python-brace-format +msgid "[red]✗ 验证异常: {error}[/red]" +msgstr "[red]✗ 验证异常: {error}[/red]" + +#: src/app/deployment/ui.py:556 +#, python-brace-format +msgid "[green]✓ {message} (维度: {dimension})[/green]" +msgstr "[green]✓ {message} (维度: {dimension})[/green]" + +#: src/app/deployment/ui.py:710 +msgid "部署进度:" +msgstr "部署进度:" + +#: src/app/deployment/ui.py:711 +msgid "准备开始部署..." +msgstr "准备开始部署..." + +#: src/app/deployment/ui.py:717 +msgid "完成" +msgstr "完成" + +#: src/app/deployment/ui.py:718 +msgid "重试" +msgstr "重试" + +#: src/app/deployment/ui.py:719 +msgid "重新配置" +msgstr "重新配置" + +#: src/app/deployment/ui.py:720 +msgid "取消部署" +msgstr "取消部署" + +#: src/app/deployment/ui.py:755 src/app/deployment/ui.py:836 +msgid "部署已取消" +msgstr "部署已取消" + +#: src/app/deployment/ui.py:756 +msgid "部署已被用户取消" +msgstr "部署已被用户取消" + +#: src/app/deployment/ui.py:819 +msgid "部署启动失败" +msgstr "部署启动失败" + +#: src/app/deployment/ui.py:820 +#, python-brace-format +msgid "部署启动失败: {error}" +msgstr "部署启动失败: {error}" + +#: src/app/deployment/ui.py:837 +msgid "部署被取消" +msgstr "部署被取消" + +#: src/app/deployment/ui.py:840 src/app/deployment/ui.py:884 +msgid "部署异常" +msgstr "部署异常" + +#: src/app/deployment/ui.py:841 +#, python-brace-format +msgid "部署异常: {error}" +msgstr "部署异常: {error}" + +#: src/app/deployment/ui.py:848 +msgid "正在检查部署环境..." +msgstr "正在检查部署环境..." + +#: src/app/deployment/ui.py:852 +msgid "环境检查失败" +msgstr "环境检查失败" + +#: src/app/deployment/ui.py:860 +msgid "正在执行部署..." +msgstr "正在执行部署..." + +#: src/app/deployment/ui.py:867 +msgid "部署完成!" +msgstr "部署完成!" + +#: src/app/deployment/ui.py:869 +msgid "[bold green]部署成功完成![/bold green]" +msgstr "[bold green]部署成功完成![/bold green]" + +#: src/app/deployment/ui.py:872 +msgid "部署成功完成!" +msgstr "部署成功完成!" + +#: src/app/deployment/ui.py:874 +msgid "部署失败" +msgstr "部署失败" + +#: src/app/deployment/ui.py:876 +msgid "[bold red]部署失败,请查看上面的错误信息[/bold red]" +msgstr "[bold red]部署失败,请查看上面的错误信息[/bold red]" + +#: src/app/deployment/ui.py:878 +msgid "部署执行失败" +msgstr "部署执行失败" + +#: src/app/deployment/ui.py:880 +msgid "部署失败,可以重试或重新配置参数" +msgstr "部署失败,可以重试或重新配置参数" + +#: src/app/deployment/ui.py:883 +#, python-brace-format +msgid "部署过程中发生异常: {error}" +msgstr "部署过程中发生异常: {error}" + +#: src/app/deployment/ui.py:901 +#, python-brace-format +msgid "步骤 {current}/{total}: {name}" +msgstr "步骤 {current}/{total}: {name}" + +#: src/app/deployment/ui.py:974 +msgid "错误" +msgstr "错误" + +#: src/app/deployment/ui.py:980 +msgid "确定" +msgstr "确定" + +#: src/app/deployment/components/modes.py:58 +#: src/app/deployment/components/modes.py:92 +#: src/app/deployment/components/modes.py:158 +#: src/app/deployment/components/modes.py:230 +#: src/app/deployment/components/env_check.py:105 +#: src/tool/oi_select_agent.py:29 +msgid "退出" +msgstr "退出" + +#: src/app/deployment/components/modes.py:68 +msgid "openEuler Intelligence 初始化" +msgstr "openEuler Intelligence 初始化" + +#: src/app/deployment/components/modes.py:70 +msgid "请选择您的初始化方式:" +msgstr "请选择您的初始化方式:" + +#: src/app/deployment/components/modes.py:77 +msgid "" +"连接现有服务\n" +"\n" +"输入现有服务的 URL 和 Token 即可连接使用" +msgstr "" +"连接现有服务\n" +"\n" +"输入现有服务的 URL 和 Token 即可连接使用" + +#: src/app/deployment/components/modes.py:85 +msgid "" +"部署新服务\n" +"\n" +"在本机部署全新的服务环境和配置" +msgstr "" +"部署新服务\n" +"\n" +"在本机部署全新的服务环境和配置" + +#: src/app/deployment/components/modes.py:157 +#: src/app/deployment/components/modes.py:229 +#: src/app/deployment/components/env_check.py:104 +msgid "返回" +msgstr "返回" + +#: src/app/deployment/components/modes.py:174 +msgid "连接现有 openEuler Intelligence 服务" +msgstr "连接现有 openEuler Intelligence 服务" + +#: src/app/deployment/components/modes.py:176 +msgid "请输入您的 openEuler Intelligence 服务连接信息:" +msgstr "请输入您的 openEuler Intelligence 服务连接信息:" + +#: src/app/deployment/components/modes.py:181 +msgid "服务 URL:" +msgstr "服务 URL:" + +#: src/app/deployment/components/modes.py:183 +msgid "例如:http://your-server:8002" +msgstr "例如:http://your-server:8002" + +#: src/app/deployment/components/modes.py:189 +msgid "访问令牌:" +msgstr "访问令牌:" + +#: src/app/deployment/components/modes.py:191 +msgid "可选,您的访问令牌" +msgstr "可选,您的访问令牌" + +#: src/app/deployment/components/modes.py:197 +msgid "获取" +msgstr "获取" + +#: src/app/deployment/components/modes.py:209 +msgid "" +"提示:\n" +"• 服务 URL 通常以 http:// 或 https:// 开头\n" +"• 访问令牌为可选项,如果服务无需认证可留空\n" +"• 输入服务 URL 后,可点击 '获取' 按钮通过浏览器获取访问令牌\n" +"• 也可以从 openEuler Intelligence Web 界面手动获取并填入\n" +"• 系统会自动验证连接并保存配置" +msgstr "" +"提示:\n" +"• 服务 URL 通常以 http:// 或 https:// 开头\n" +"• 访问令牌为可选项,如果服务无需认证可留空\n" +"• 输入服务 URL 后,可点击 '获取' 按钮通过浏览器获取访问令牌\n" +"• 也可以从 openEuler Intelligence Web 界面手动获取并填入\n" +"• 系统会自动验证连接并保存配置" + +#: src/app/deployment/components/modes.py:218 +msgid "" +"提示:\n" +"• 服务 URL 通常以 http:// 或 https:// 开头\n" +"• 访问令牌为可选项,如果服务无需认证可留空\n" +"• [yellow]当前环境不支持浏览器,请从 Web 界面手动获取访问令牌[/yellow]\n" +"• 系统会自动验证连接并保存配置" +msgstr "" +"提示:\n" +"• 服务 URL 通常以 http:// 或 https:// 开头\n" +"• 访问令牌为可选项,如果服务无需认证可留空\n" +"• [yellow]当前环境不支持浏览器,请从 Web 界面手动获取访问令牌[/yellow]\n" +"• 系统会自动验证连接并保存配置" + +#: src/app/deployment/components/modes.py:228 +msgid "连接并保存" +msgstr "连接并保存" + +#: src/app/deployment/components/modes.py:256 +msgid "当前环境不支持打开浏览器,请手动从 Web 界面获取访问令牌" +msgstr "当前环境不支持打开浏览器,请手动从 Web 界面获取访问令牌" + +#: src/app/deployment/components/modes.py:264 +msgid "请先输入服务 URL" +msgstr "请先输入服务 URL" + +#: src/app/deployment/components/modes.py:273 +msgid "[yellow]正在获取授权 URL...[/yellow]" +msgstr "[yellow]正在获取授权 URL...[/yellow]" + +#: src/app/deployment/components/modes.py:278 +msgid "[red]✗ 获取授权 URL 失败[/red]" +msgstr "[red]✗ 获取授权 URL 失败[/red]" + +#: src/app/deployment/components/modes.py:287 +msgid "[yellow]正在打开浏览器登录...[/yellow]" +msgstr "[yellow]正在打开浏览器登录...[/yellow]" + +#: src/app/deployment/components/modes.py:289 +msgid "已打开浏览器,请完成登录" +msgstr "已打开浏览器,请完成登录" + +#: src/app/deployment/components/modes.py:296 +#, python-brace-format +msgid "获取 API Key 失败: {error}" +msgstr "获取 API Key 失败: {error}" + +#: src/app/deployment/components/modes.py:307 +msgid "请等待连接验证完成" +msgstr "请等待连接验证完成" + +#: src/app/deployment/components/modes.py:319 +msgid "配置已保存,初始化完成!" +msgstr "配置已保存,初始化完成!" + +#: src/app/deployment/components/modes.py:323 +#, python-brace-format +msgid "保存配置时发生错误: {error}" +msgstr "保存配置时发生错误: {error}" + +#: src/app/deployment/components/modes.py:377 +msgid "[yellow]验证连接中...[/yellow]" +msgstr "[yellow]验证连接中...[/yellow]" + +#: src/app/deployment/components/modes.py:415 +msgid "[yellow]等待登录完成...[/yellow]" +msgstr "[yellow]等待登录完成...[/yellow]" + +#: src/app/deployment/components/modes.py:431 +msgid "[green]✓ 登录成功,已获取 API Key[/green]" +msgstr "[green]✓ 登录成功,已获取 API Key[/green]" + +#: src/app/deployment/components/modes.py:437 +msgid "[red]✗ 登录失败:未收到 session ID[/red]" +msgstr "[red]✗ 登录失败:未收到 session ID[/red]" + +#: src/app/deployment/components/modes.py:442 +#, python-brace-format +msgid "[red]✗ 登录失败: {error}[/red]" +msgstr "[red]✗ 登录失败: {error}[/red]" + +#: src/app/deployment/components/modes.py:446 +msgid "[red]✗ 登录失败:未知结果[/red]" +msgstr "[red]✗ 登录失败:未知结果[/red]" + +#: src/app/deployment/components/modes.py:451 +msgid "[yellow]登录已取消[/yellow]" +msgstr "[yellow]登录已取消[/yellow]" + +#: src/app/deployment/components/modes.py:455 +#, python-brace-format +msgid "[red]✗ 登录异常: {error}[/red]" +msgstr "[red]✗ 登录异常: {error}[/red]" + +#: src/app/deployment/components/env_check.py:92 +msgid "环境检查" +msgstr "环境检查" + +#: src/app/deployment/components/env_check.py:96 +msgid "检查操作系统类型..." +msgstr "检查操作系统类型..." + +#: src/app/deployment/components/env_check.py:100 +msgid "检查管理员权限..." +msgstr "检查管理员权限..." + +#: src/app/deployment/components/env_check.py:103 +msgid "继续配置" +msgstr "继续配置" + +#: src/app/deployment/components/env_check.py:124 +#, python-brace-format +msgid "环境检查过程中发生异常: {error}" +msgstr "环境检查过程中发生异常: {error}" + +#: src/app/deployment/components/env_check.py:137 +msgid "操作系统: openEuler (支持)" +msgstr "操作系统: openEuler (支持)" + +#: src/app/deployment/components/env_check.py:140 +msgid "操作系统: 非 openEuler (不支持)" +msgstr "操作系统: 非 openEuler (不支持)" + +#: src/app/deployment/components/env_check.py:146 +#, python-brace-format +msgid "操作系统检查失败: {error}" +msgstr "操作系统检查失败: {error}" + +#: src/app/deployment/components/env_check.py:147 +#, python-brace-format +msgid "操作系统检查异常: {error}" +msgstr "操作系统检查异常: {error}" + +#: src/app/deployment/components/env_check.py:160 +msgid "管理员权限: 可用" +msgstr "管理员权限: 可用" + +#: src/app/deployment/components/env_check.py:163 +msgid "管理员权限: 不可用 (需要 sudo)" +msgstr "管理员权限: 不可用 (需要 sudo)" + +#: src/app/deployment/components/env_check.py:169 +#, python-brace-format +msgid "权限检查失败: {error}" +msgstr "权限检查失败: {error}" + +#: src/app/deployment/components/env_check.py:170 +#, python-brace-format +msgid "权限检查异常: {error}" +msgstr "权限检查异常: {error}" + +#: src/app/deployment/agent.py:409 +msgid "[bold blue]开始初始化智能体...[/bold blue]" +msgstr "[bold blue]开始初始化智能体...[/bold blue]" + +#: src/app/deployment/agent.py:416 +msgid "智能体初始化失败" +msgstr "智能体初始化失败" + +#: src/app/deployment/agent.py:454 +#, python-brace-format +msgid "[bold green]智能体初始化完成! 默认 App ID: {app_id}[/bold green]" +msgstr "[bold green]智能体初始化完成! 默认 App ID: {app_id}[/bold green]" + +#: src/app/deployment/agent.py:461 +msgid "[yellow]未能创建任何智能体[/yellow]" +msgstr "[yellow]未能创建任何智能体[/yellow]" + +#: src/app/deployment/agent.py:491 +#, python-brace-format +msgid "[yellow]服务配置目录不存在: {dir},跳过{operation}[/yellow]" +msgstr "[yellow]服务配置目录不存在: {dir},跳过{operation}[/yellow]" + +#: src/app/deployment/agent.py:505 +#, python-brace-format +msgid "[yellow]未找到服务配置文件,跳过{operation}[/yellow]" +msgstr "[yellow]未找到服务配置文件,跳过{operation}[/yellow]" + +#: src/app/deployment/agent.py:549 +#, python-brace-format +msgid " [red]处理 {file} 时发生异常[/red]" +msgstr " [red]处理 {file} 时发生异常[/red]" + +#: src/app/deployment/agent.py:563 +msgid "[cyan]安装 systemd 服务文件...[/cyan]" +msgstr "[cyan]安装 systemd 服务文件...[/cyan]" + +#: src/app/deployment/agent.py:566 +msgid "服务文件安装" +msgstr "服务文件安装" + +#: src/app/deployment/agent.py:585 +#, python-brace-format +msgid "[green]成功安装 {count} 个服务文件[/green]" +msgstr "[green]成功安装 {count} 个服务文件[/green]" -#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:286 -msgid "低风险" -msgstr "低风险" +#: src/app/deployment/agent.py:604 +#, python-brace-format +msgid " [blue]复制服务文件: {name}[/blue]" +msgstr " [blue]复制服务文件: {name}[/blue]" -#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:287 -msgid "中等风险" -msgstr "中等风险" +#: src/app/deployment/agent.py:622 +#, python-brace-format +msgid " [red]复制 {name} 时发生异常[/red]" +msgstr " [red]复制 {name} 时发生异常[/red]" -#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:288 -msgid "高风险" -msgstr "高风险" +#: src/app/deployment/agent.py:631 +#, python-brace-format +msgid " [green]{name} 复制成功[/green]" +msgstr " [green]{name} 复制成功[/green]" -#: src/app/mcp_widgets.py:54 src/backend/hermes/mcp_helpers.py:289 -msgid "未知风险" -msgstr "未知风险" +#: src/app/deployment/agent.py:640 +#, python-brace-format +msgid " [red]{name} 复制失败: {error}[/red]" +msgstr " [red]{name} 复制失败: {error}[/red]" -#: src/app/mcp_widgets.py:81 -msgid "✓ 确认" -msgstr "✓ 确认" +#: src/app/deployment/agent.py:652 +msgid "[cyan]重新加载 systemd 配置...[/cyan]" +msgstr "[cyan]重新加载 systemd 配置...[/cyan]" -#: src/app/mcp_widgets.py:82 src/app/mcp_widgets.py:201 -msgid "✗ 取消" -msgstr "✗ 取消" +#: src/app/deployment/agent.py:667 +msgid "[red]重新加载 systemd 配置时发生异常[/red]" +msgstr "[red]重新加载 systemd 配置时发生异常[/red]" -#: src/app/mcp_widgets.py:164 -msgid "需要补充参数" -msgstr "需要补充参数" +#: src/app/deployment/agent.py:676 +msgid "[green]systemd 配置重新加载成功[/green]" +msgstr "[green]systemd 配置重新加载成功[/green]" -#: src/app/mcp_widgets.py:169 -msgid "📝 参数输入" -msgstr "📝 参数输入" +#: src/app/deployment/agent.py:685 +#, python-brace-format +msgid "[red]systemd 配置重新加载失败: {error}[/red]" +msgstr "[red]systemd 配置重新加载失败: {error}[/red]" -#: src/app/mcp_widgets.py:181 +#: src/app/deployment/agent.py:697 +msgid "[cyan]启动 MCP Server 进程...[/cyan]" +msgstr "[cyan]启动 MCP Server 进程...[/cyan]" + +#: src/app/deployment/agent.py:702 #, python-brace-format -msgid "请输入 {param_name}" -msgstr "请输入 {param_name}" +msgid "[red]MCP 启动脚本不存在: {path}[/red]" +msgstr "[red]MCP 启动脚本不存在: {path}[/red]" -#: src/app/mcp_widgets.py:191 -msgid "补充说明(可选)" -msgstr "补充说明(可选)" +#: src/app/deployment/agent.py:713 +msgid "[yellow]清理旧进程时遇到问题,但继续执行启动脚本[/yellow]" +msgstr "[yellow]清理旧进程时遇到问题,但继续执行启动脚本[/yellow]" -#: src/app/mcp_widgets.py:200 -msgid "✓ 提交" -msgstr "✓ 提交" +#: src/app/deployment/agent.py:721 +#, python-brace-format +msgid " [blue]执行命令: {cmd}[/blue]" +msgstr " [blue]执行命令: {cmd}[/blue]" + +#: src/app/deployment/agent.py:736 +msgid "[green]MCP Server 启动脚本执行成功[/green]" +msgstr "[green]MCP Server 启动脚本执行成功[/green]" + +#: src/app/deployment/agent.py:743 +msgid "执行 MCP Server 启动脚本失败" +msgstr "执行 MCP Server 启动脚本失败" + +#: src/app/deployment/agent.py:750 +#, python-brace-format +msgid "[red]MCP Server 启动脚本执行失败 (返回码: {code})[/red]" +msgstr "[red]MCP Server 启动脚本执行失败 (返回码: {code})[/red]" + +#: src/app/deployment/agent.py:808 +msgid "[cyan]验证 MCP Server 服务状态...[/cyan]" +msgstr "[cyan]验证 MCP Server 服务状态...[/cyan]" + +#: src/app/deployment/agent.py:811 +msgid "服务验证" +msgstr "服务验证" + +#: src/app/deployment/agent.py:826 +#, python-brace-format +msgid "[red]关键服务状态异常: {services},停止初始化[/red]" +msgstr "[red]关键服务状态异常: {services},停止初始化[/red]" + +#: src/app/deployment/agent.py:832 +msgid "[green]MCP Server 服务验证完成[/green]" +msgstr "[green]MCP Server 服务验证完成[/green]" + +#: src/app/deployment/agent.py:852 +#, python-brace-format +msgid " [red]{name} 启动超时 (30秒)[/red]" +msgstr " [red]{name} 启动超时 (30秒)[/red]" + +#: src/app/deployment/agent.py:861 +#, python-brace-format +msgid " [magenta]检查服务状态: {name}[/magenta]" +msgstr " [magenta]检查服务状态: {name}[/magenta]" + +#: src/app/deployment/agent.py:867 +#, python-brace-format +msgid " [dim]{name} 重新检查状态... (第 {count} 次)[/dim]" +msgstr " [dim]{name} 重新检查状态... (第 {count} 次)[/dim]" + +#: src/app/deployment/agent.py:889 +#, python-brace-format +msgid " [red]检查 {name} 状态失败[/red]" +msgstr " [red]检查 {name} 状态失败[/red]" + +#: src/app/deployment/agent.py:900 +#, python-brace-format +msgid " [green]{service_name} 状态正常 (active running)[/green]" +msgstr " [green]{service_name} 状态正常 (active running)[/green]" + +#: src/app/deployment/agent.py:910 +#, python-brace-format +msgid " [red]{service_name} 服务启动失败[/red]" +msgstr " [red]{service_name} 服务启动失败[/red]" + +#: src/app/deployment/agent.py:921 +#, python-brace-format +msgid " [yellow]{service_name} 正在启动中,等待启动完成...[/yellow]" +msgstr " [yellow]{service_name} 正在启动中,等待启动完成...[/yellow]" + +#: src/app/deployment/agent.py:935 +#, python-brace-format +msgid " [red]{service_name} 状态异常 (返回码: {returncode})[/red]" +msgstr " [red]{service_name} 状态异常 (返回码: {returncode})[/red]" + +#: src/app/deployment/agent.py:956 +msgid "[cyan]注册 MCP 服务...[/cyan]" +msgstr "[cyan]注册 MCP 服务...[/cyan]" + +#: src/app/deployment/agent.py:973 +#, python-brace-format +msgid " [green]{name} 注册成功: {mcp_path} -> {service_id}[/green]" +msgstr " [green]{name} 注册成功: {mcp_path} -> {service_id}[/green]" + +#: src/app/deployment/agent.py:983 +#, python-brace-format +msgid " [red]MCP 服务 {name} 注册失败[/red]" +msgstr " [red]MCP 服务 {name} 注册失败[/red]" + +#: src/app/deployment/agent.py:989 +#, python-brace-format +msgid "[green]MCP 服务注册完成,成功 {count} 个[/green]" +msgstr "[green]MCP 服务注册完成,成功 {count} 个[/green]" + +#: src/app/deployment/agent.py:1001 +msgid "[cyan]读取应用配置并创建智能体...[/cyan]" +msgstr "[cyan]读取应用配置并创建智能体...[/cyan]" + +#: src/app/deployment/agent.py:1027 +#, python-brace-format +msgid " [dim]设置默认智能体: {name}[/dim]" +msgstr " [dim]设置默认智能体: {name}[/dim]" + +#: src/app/deployment/agent.py:1035 +#, python-brace-format +msgid "[green]成功创建 {count} 个智能体[/green]" +msgstr "[green]成功创建 {count} 个智能体[/green]" + +#: src/app/deployment/agent.py:1040 +msgid "[red]未能创建任何智能体[/red]" +msgstr "[red]未能创建任何智能体[/red]" -#: src/app/settings.py:63 +#: src/app/deployment/agent.py:1053 +#, python-brace-format +msgid "[magenta]创建智能体: {name}[/magenta]" +msgstr "[magenta]创建智能体: {name}[/magenta]" + +#: src/app/deployment/agent.py:1066 +#, python-brace-format +msgid " [yellow]缺少 MCP 服务: {services},跳过[/yellow]" +msgstr " [yellow]缺少 MCP 服务: {services},跳过[/yellow]" + +#: src/app/deployment/agent.py:1075 +#, python-brace-format +msgid " [yellow]智能体 {name} 没有可用的 MCP 服务,跳过[/yellow]" +msgstr " [yellow]智能体 {name} 没有可用的 MCP 服务,跳过[/yellow]" + +#: src/app/deployment/agent.py:1095 +#, python-brace-format +msgid " [red]创建智能体 {name} 失败[/red]" +msgstr " [red]创建智能体 {name} 失败[/red]" + +#: src/app/deployment/agent.py:1103 +#, python-brace-format +msgid " [green]智能体 {name} 创建成功: {app_id}[/green]" +msgstr " [green]智能体 {name} 创建成功: {app_id}[/green]" + +#: src/app/deployment/agent.py:1134 +msgid "[cyan]加载应用配置文件...[/cyan]" +msgstr "[cyan]加载应用配置文件...[/cyan]" + +#: src/app/deployment/agent.py:1139 +#, python-brace-format +msgid "[red]应用配置文件不存在: {path}[/red]" +msgstr "[red]应用配置文件不存在: {path}[/red]" + +#: src/app/deployment/agent.py:1153 +msgid "[yellow]配置文件中没有找到应用定义[/yellow]" +msgstr "[yellow]配置文件中没有找到应用定义[/yellow]" + +#: src/app/deployment/agent.py:1173 +#, python-brace-format +msgid " [red]应用配置缺少必需字段: {field}[/red]" +msgstr " [red]应用配置缺少必需字段: {field}[/red]" + +#: src/app/deployment/agent.py:1180 +#, python-brace-format +msgid "加载应用配置文件失败: {path}" +msgstr "加载应用配置文件失败: {path}" + +#: src/app/deployment/agent.py:1187 +#, python-brace-format +msgid "[green]成功加载 {count} 个应用配置[/green]" +msgstr "[green]成功加载 {count} 个应用配置[/green]" + +#: src/app/deployment/agent.py:1198 +msgid "[cyan]加载 MCP 配置文件...[/cyan]" +msgstr "[cyan]加载 MCP 配置文件...[/cyan]" + +#: src/app/deployment/agent.py:1204 +msgid "[yellow]未找到 MCP 配置文件[/yellow]" +msgstr "[yellow]未找到 MCP 配置文件[/yellow]" + +#: src/app/deployment/agent.py:1209 +#, python-brace-format +msgid "[green]成功加载 {count} 个 MCP 配置[/green]" +msgstr "[green]成功加载 {count} 个 MCP 配置[/green]" + +#: src/app/deployment/agent.py:1227 +#, python-brace-format +msgid " [red]MCP 服务 {name} SSE Endpoint 验证失败[/red]" +msgstr " [red]MCP 服务 {name} SSE Endpoint 验证失败[/red]" + +#: src/app/deployment/agent.py:1245 +#, python-brace-format +msgid " [red]{name} 处理失败: {error}[/red]" +msgstr " [red]{name} 处理失败: {error}[/red]" + +#: src/app/deployment/agent.py:1260 +#, python-brace-format +msgid " [blue]注册 {name}...[/blue]" +msgstr " [blue]注册 {name}...[/blue]" + +#: src/app/deployment/agent.py:1273 +#, python-brace-format +msgid " [cyan]安装 {name} (ID: {service_id})...[/cyan]" +msgstr " [cyan]安装 {name} (ID: {service_id})...[/cyan]" + +#: src/app/deployment/agent.py:1278 +#, python-brace-format +msgid " [dim]等待 {name} 安装完成...[/dim]" +msgstr " [dim]等待 {name} 安装完成...[/dim]" + +#: src/app/deployment/agent.py:1280 +#, python-brace-format +msgid " [red]{name} 安装超时[/red]" +msgstr " [red]{name} 安装超时[/red]" + +#: src/app/deployment/agent.py:1293 +#, python-brace-format +msgid " [yellow]激活 {name}...[/yellow]" +msgstr " [yellow]激活 {name}...[/yellow]" + +#: src/app/deployment/agent.py:1295 +#, python-brace-format +msgid " [green]{name} 处理完成[/green]" +msgstr " [green]{name} 处理完成[/green]" + +#: src/app/deployment/agent.py:1307 +#, python-brace-format +msgid "[magenta]验证 SSE Endpoint: {name} -> {url}[/magenta]" +msgstr "[magenta]验证 SSE Endpoint: {name} -> {url}[/magenta]" + +#: src/app/deployment/agent.py:1320 src/app/deployment/agent.py:1330 +#, python-brace-format +msgid " [green]{name} SSE Endpoint 验证通过[/green]" +msgstr " [green]{name} SSE Endpoint 验证通过[/green]" + +#: src/app/deployment/agent.py:1343 +#, python-brace-format +msgid " [red]{name} SSE Endpoint 验证失败: 30秒内无法连接[/red]" +msgstr " [red]{name} SSE Endpoint 验证失败: 30秒内无法连接[/red]" + +#: src/app/settings.py:57 msgid "设置" msgstr "设置" -#: src/app/settings.py:66 +#: src/app/settings.py:64 src/app/dialogs/user.py:179 +msgid "保存" +msgstr "保存" + +#: src/app/settings.py:174 msgid "后端:" msgstr "后端:" -#: src/app/settings.py:76 +#: src/app/settings.py:184 msgid "Base URL:" msgstr "基础 URL:" -#: src/app/settings.py:88 +#: src/app/settings.py:196 msgid "API Key:" msgstr "API 密钥:" -#: src/app/settings.py:95 +#: src/app/settings.py:203 msgid "API 访问密钥,可选" msgstr "API 访问密钥,可选" -#: src/app/settings.py:103 src/app/settings.py:239 +#: src/app/settings.py:213 msgid "模型:" msgstr "模型:" -#: src/app/settings.py:108 src/app/settings.py:244 +#: src/app/settings.py:218 msgid "模型名称,可选" msgstr "模型名称,可选" -#: src/app/settings.py:117 src/app/settings.py:272 -msgid "MCP 工具授权:" -msgstr "MCP 工具授权:" - -#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:274 -#: src/app/settings.py:549 src/app/settings.py:555 -msgid "自动执行" -msgstr "自动执行" - -#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:181 -#: src/app/settings.py:274 src/app/settings.py:549 src/app/settings.py:555 -msgid "手动确认" -msgstr "手动确认" - -#: src/app/settings.py:133 -msgid "保存" -msgstr "保存" +#: src/app/settings.py:228 +msgid "用户设置:" +msgstr "用户设置:" -#: src/app/settings.py:134 src/app/dialogs/common.py:27 -msgid "取消" -msgstr "取消" +#: src/app/settings.py:230 +msgid "更改用户设置" +msgstr "更改用户设置" -#: src/app/settings.py:437 +#: src/app/settings.py:407 msgid "Base URL 不能为空" msgstr "基础 URL 不能为空" -#: src/app/settings.py:534 -msgid "切换中..." -msgstr "切换中..." - -#: src/app/tui.py:217 +#: src/app/tui.py:216 msgid "Enter command or question..." msgstr "输入命令或问题..." -#: src/app/tui.py:226 +#: src/app/tui.py:225 msgid "Quit" msgstr "退出" -#: src/app/tui.py:227 +#: src/app/tui.py:226 msgid "Settings" msgstr "设置" -#: src/app/tui.py:228 +#: src/app/tui.py:227 msgid "Reset" msgstr "重置对话" -#: src/app/tui.py:229 +#: src/app/tui.py:228 msgid "Agent" msgstr "选择智能体" -#: src/app/tui.py:230 +#: src/app/tui.py:229 msgid "Cancel" msgstr "取消" -#: src/app/tui.py:231 +#: src/app/tui.py:230 msgid "Focus" msgstr "切换焦点" -#: src/app/tui.py:255 +#: src/app/tui.py:254 #, python-brace-format msgid "Intelligent CLI Assistant {version}" msgstr "智能命令行助手 {version}" -#: src/app/tui.py:371 +#: src/app/tui.py:372 msgid "[Cancelled]" msgstr "[已取消]" -#: src/app/tui.py:576 +#: src/app/tui.py:588 msgid "" "No response received, please check network connection or try again later" msgstr "没有收到响应,请检查网络连接或稍后重试" -#: src/app/tui.py:684 +#: src/app/tui.py:696 msgid "Request timeout, processing stopped" msgstr "请求超时,已停止处理" -#: src/app/tui.py:691 +#: src/app/tui.py:703 msgid "No response for a long time, processing stopped" msgstr "长时间无响应,已停止处理" -#: src/app/tui.py:744 src/app/tui.py:902 +#: src/app/tui.py:756 src/app/tui.py:914 msgid "Request timeout, please try again later" msgstr "请求超时,请稍后重试" -#: src/app/tui.py:888 +#: src/app/tui.py:900 #, python-brace-format msgid "Server error: {message}" msgstr "服务端错误: {message}" -#: src/app/tui.py:890 +#: src/app/tui.py:902 #, python-brace-format msgid "Request failed: {message}" msgstr "请求失败: {message}" -#: src/app/tui.py:894 +#: src/app/tui.py:906 msgid "Network connection interrupted, please check network and try again" msgstr "网络连接异常中断,请检查网络连接后重试" -#: src/app/tui.py:906 +#: src/app/tui.py:918 msgid "Network connection error, please check network and try again" msgstr "网络连接错误,请检查网络后重试" -#: src/app/tui.py:915 src/app/tui.py:949 +#: src/app/tui.py:927 src/app/tui.py:961 msgid "Server response error, please try again later" msgstr "服务端响应异常,请稍后重试" -#: src/app/tui.py:920 +#: src/app/tui.py:932 msgid "Data format error, please try again later" msgstr "数据格式错误,请稍后重试" -#: src/app/tui.py:927 +#: src/app/tui.py:939 msgid "Authentication failed, please check configuration" msgstr "认证失败,请检查配置" -#: src/app/tui.py:951 +#: src/app/tui.py:963 #, python-brace-format msgid "Error processing command: {error}" msgstr "处理命令时出错: {error}" -#: src/app/tui.py:1170 +#: src/app/tui.py:1088 src/app/tui.py:1114 src/app/tui.py:1296 +#: src/app/tui.py:1457 src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 +#: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 +#: src/app/dialogs/agent.py:208 src/tool/oi_select_agent.py:67 +#: src/tool/oi_select_agent.py:94 +msgid "智能问答" +msgstr "智能问答" + +#: src/app/tui.py:1214 msgid "💡 MCP response sent" msgstr "💡 MCP 响应已发送" -#: src/app/tui.py:1173 src/app/tui.py:1202 +#: src/app/tui.py:1217 src/app/tui.py:1246 msgid "❌ Current client does not support MCP response" msgstr "❌ 当前客户端不支持 MCP 响应功能" -#: src/app/tui.py:1182 +#: src/app/tui.py:1226 #, python-brace-format msgid "❌ Failed to send MCP response: {error}" msgstr "❌ 发送 MCP 响应失败: {error}" -#: src/app/tui.py:1249 -#, python-brace-format -msgid "⏱️ MCP response timeout ({seconds} seconds)" -msgstr "⏱️ MCP 响应超时 ({seconds}秒)" - -#: src/app/tui.py:1253 +#: src/app/tui.py:1285 msgid "🚫 MCP response cancelled" msgstr "🚫 MCP 响应被取消" -#: src/app/tui.py:1336 +#: src/app/tui.py:1367 msgid "Backend configuration validation failed, please check and modify" msgstr "后端配置验证失败,请检查并修改配置" -#: src/app/tui.py:1337 +#: src/app/tui.py:1368 msgid "Configuration Error" msgstr "配置错误" +#: src/app/dialogs/user.py:90 +msgid "常规设置" +msgstr "常规设置" + +#: src/app/dialogs/user.py:91 +msgid "大模型设置" +msgstr "大模型设置" + +#: src/app/dialogs/user.py:133 +msgid "用户名:" +msgstr "用户名:" + +#: src/app/dialogs/user.py:142 +msgid "未登录" +msgstr "未登录" + +#: src/app/dialogs/user.py:150 +msgid "管理员" +msgstr "管理员" + +#: src/app/dialogs/user.py:150 +msgid "普通用户" +msgstr "普通用户" + +#: src/app/dialogs/user.py:164 +msgid "MCP 工具授权:" +msgstr "MCP 工具授权:" + +#: src/app/dialogs/user.py:166 src/app/dialogs/user.py:374 +msgid "自动执行" +msgstr "自动执行" + +#: src/app/dialogs/user.py:166 src/app/dialogs/user.py:374 +msgid "手动确认" +msgstr "手动确认" + +#: src/app/dialogs/user.py:214 +msgid "加载模型失败" +msgstr "加载模型失败" + +#: src/app/dialogs/user.py:216 +msgid "加载中..." +msgstr "加载中..." + +#: src/app/dialogs/user.py:219 +msgid "暂无可用模型" +msgstr "暂无可用模型" + +#: src/app/dialogs/user.py:255 +msgid "↑↓: 选择模型 空格: 激活 回车: 保存 ESC: 取消" +msgstr "↑↓: 选择模型 空格: 激活 回车: 保存 ESC: 取消" + +#: src/app/dialogs/user.py:273 +msgid "描述: " +msgstr "描述: " + +#: src/app/dialogs/user.py:284 +msgid "类型: " +msgstr "类型: " + +#: src/app/dialogs/user.py:294 +msgid "最大 Token: " +msgstr "最大 Token: " + #: src/app/dialogs/common.py:25 msgid "确认退出吗?" msgstr "确认退出吗?" @@ -263,13 +1543,6 @@ msgstr "请选择 openEuler Intelligence 后端来使用智能体功能" msgid "按任意键关闭" msgstr "按任意键关闭" -#: src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 -#: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 -#: src/app/dialogs/agent.py:208 src/tool/oi_select_agent.py:67 -#: src/tool/oi_select_agent.py:94 -msgid "智能问答" -msgstr "智能问答" - #: src/app/dialogs/agent.py:113 msgid "OS 智能助手" msgstr "OS 智能助手" @@ -278,6 +1551,34 @@ msgstr "OS 智能助手" msgid "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" msgstr "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" +#: src/backend/hermes/client.py:395 +msgid "未配置 Chat 模型" +msgstr "未配置 Chat 模型" + +#: src/backend/hermes/client.py:396 +msgid "配置步骤" +msgstr "配置步骤" + +#: src/backend/hermes/client.py:397 +msgid "按 Ctrl+S 打开设置" +msgstr "按 Ctrl+S 打开设置" + +#: src/backend/hermes/client.py:398 +msgid "确认后端为 openEuler Intelligence" +msgstr "确认后端为 openEuler Intelligence" + +#: src/backend/hermes/client.py:399 +msgid "点击 \"更改用户设置\" 按钮" +msgstr "点击 \"更改用户设置\" 按钮" + +#: src/backend/hermes/client.py:400 +msgid "切换到 \"大模型设置\" 标签页" +msgstr "切换到 \"大模型设置\" 标签页" + +#: src/backend/hermes/client.py:401 +msgid "使用 ↑↓ 键选择模型,空格激活,回车保存" +msgstr "使用 ↑↓ 键选择模型,空格激活,回车保存" + #: src/backend/hermes/mcp_helpers.py:44 msgid "正在初始化工具" msgstr "正在初始化工具" @@ -318,11 +1619,11 @@ msgstr "名称" msgid "说明" msgstr "说明" -#: src/main.py:28 +#: src/main.py:27 msgid "openEuler Intelligence - Intelligent command-line tool" msgstr "openEuler Intelligence - 智能命令行助手" -#: src/main.py:29 +#: src/main.py:28 msgid "" "\n" "For more information and documentation, please visit:\n" @@ -334,32 +1635,32 @@ msgstr "" " https://gitee.com/openeuler/euler-copilot-shell/tree/master/docs\n" " " -#: src/main.py:39 +#: src/main.py:38 msgid "General Options" msgstr "通用选项" -#: src/main.py:40 +#: src/main.py:39 msgid "Show help and version information" msgstr "显示帮助信息和版本信息" -#: src/main.py:46 +#: src/main.py:45 msgid "Show this help message and exit" msgstr "显示此帮助信息并退出" -#: src/main.py:53 +#: src/main.py:52 msgid "Show program version number and exit" msgstr "显示程序版本号并退出" -#: src/main.py:58 +#: src/main.py:57 msgid "Backend Configuration Options" msgstr "后端配置选项" -#: src/main.py:59 +#: src/main.py:58 msgid "" "For initializing and configuring openEuler Intelligence backend services" msgstr "用于初始化和配置 openEuler Intelligence 后端服务" -#: src/main.py:65 +#: src/main.py:64 msgid "" "Initialize openEuler Intelligence backend\n" " * Initialization requires administrator privileges and network connection" @@ -367,7 +1668,7 @@ msgstr "" "初始化 openEuler Intelligence 后端\n" " * 初始化操作需要管理员权限和网络连接" -#: src/main.py:73 +#: src/main.py:72 msgid "" "Change openEuler Intelligence LLM settings (requires valid local backend " "service)\n" @@ -376,105 +1677,113 @@ msgstr "" "更改 openEuler Intelligence 大模型设置(需要有效的本地后端服务)\n" " * 配置编辑操作需要管理员权限" -#: src/main.py:80 +#: src/main.py:79 msgid "Application Configuration Options" msgstr "应用配置选项" -#: src/main.py:81 +#: src/main.py:80 msgid "For configuring application frontend behavior and preferences" msgstr "用于配置应用前端行为和偏好设置" -#: src/main.py:86 +#: src/main.py:85 msgid "Select default agent" msgstr "选择默认智能体" +#: src/main.py:90 +msgid "Authentication Management Options" +msgstr "身份认证管理选项" + #: src/main.py:91 +msgid "For managing login and authentication" +msgstr "用于管理登录和身份认证" + +#: src/main.py:96 +msgid "Login via browser to obtain API key" +msgstr "通过浏览器登录以获取 API 密钥" + +#: src/main.py:101 msgid "Language Settings" msgstr "语言设置" -#: src/main.py:92 +#: src/main.py:102 msgid "For configuring application display language" msgstr "用于配置应用显示语言" -#: src/main.py:100 +#: src/main.py:110 #, python-brace-format msgid "Set display language (available: {locales})" msgstr "设置显示语言 (可选: {locales})" -#: src/main.py:105 +#: src/main.py:115 msgid "Log Management Options" msgstr "日志管理选项" -#: src/main.py:106 +#: src/main.py:116 msgid "For viewing and configuring log output" msgstr "用于查看和配置日志输出" -#: src/main.py:111 +#: src/main.py:121 msgid "Show latest log content (up to 1000 lines)" msgstr "显示最新的日志内容(最多1000行)" -#: src/main.py:117 +#: src/main.py:127 msgid "Set log level (available: DEBUG, INFO, WARNING, ERROR)" msgstr "设置日志级别 (可选: DEBUG, INFO, WARNING, ERROR)" -#: src/main.py:140 +#: src/main.py:150 #, python-brace-format msgid "Failed to retrieve logs: {error}\n" msgstr "获取日志失败: {error}\n" -#: src/main.py:147 +#: src/main.py:157 #, python-brace-format msgid "Invalid log level: {level}\n" msgstr "无效的日志级别: {level}\n" -#: src/main.py:156 +#: src/main.py:166 #, python-format msgid "Log level has been set to: %s" msgstr "日志级别已设置为: %s" -#: src/main.py:157 +#: src/main.py:167 msgid "This is a DEBUG level test message" msgstr "这是一条 DEBUG 级别的测试消息" -#: src/main.py:158 +#: src/main.py:168 msgid "This is an INFO level test message" msgstr "这是一条 INFO 级别的测试消息" -#: src/main.py:159 +#: src/main.py:169 msgid "This is a WARNING level test message" msgstr "这是一条 WARNING 级别的测试消息" -#: src/main.py:160 +#: src/main.py:170 msgid "This is an ERROR level test message" msgstr "这是一条 ERROR 级别的测试消息" -#: src/main.py:162 +#: src/main.py:172 #, python-brace-format msgid "✓ Log level successfully set to: {level}\n" msgstr "✓ 日志级别已成功设置为: {level}\n" -#: src/main.py:163 +#: src/main.py:173 msgid "✓ Logging system initialized\n" msgstr "✓ 日志系统初始化完成\n" -#: src/main.py:191 +#: src/main.py:201 #, python-brace-format msgid "✓ Language set to: {locale}\n" msgstr "✓ 语言已设置为: {locale}\n" -#: src/main.py:193 +#: src/main.py:203 #, python-brace-format msgid "✗ Unsupported language: {locale}\n" msgstr "✗ 不支持的语言: {locale}\n" -#: src/main.py:228 +#: src/main.py:246 msgid "Fatal error in Intelligent Shell application" msgstr "智能 Shell 应用发生致命错误" -#: src/tool/oi_select_agent.py:29 -msgid "退出" -msgstr "退出" - #: src/tool/oi_select_agent.py:123 #, python-brace-format msgid "✓ 默认智能体已设置为: {name}\n" @@ -545,198 +1854,202 @@ msgstr "访问配置文件时发生错误: {error}" msgid "权限不足:无法访问配置文件 {filename},请以管理员身份运行" msgstr "权限不足:无法访问配置文件 {filename},请以管理员身份运行" -#: src/tool/validators.py:135 src/tool/validators.py:584 -#: src/tool/validators.py:647 +#: src/tool/oi_backend_init.py:39 +msgid "openEuler Intelligence 部署助手" +msgstr "openEuler Intelligence 部署助手" + +#: src/tool/validators.py:165 src/tool/validators.py:614 +#: src/tool/validators.py:677 #, python-brace-format msgid "连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}" msgstr "连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}" -#: src/tool/validators.py:140 +#: src/tool/validators.py:170 #, python-brace-format msgid "LLM 配置验证失败: {error}" msgstr "LLM 配置验证失败: {error}" -#: src/tool/validators.py:144 +#: src/tool/validators.py:174 msgid "LLM 配置验证成功" msgstr "LLM 配置验证成功" -#: src/tool/validators.py:146 +#: src/tool/validators.py:176 #, python-brace-format msgid " - 支持工具调用,类型: {func_type}" msgstr " - 支持工具调用,类型: {func_type}" -#: src/tool/validators.py:148 +#: src/tool/validators.py:178 msgid " - 不支持工具调用" msgstr " - 不支持工具调用" -#: src/tool/validators.py:201 +#: src/tool/validators.py:231 msgid "无法连接到 Embedding 模型服务。" msgstr "无法连接到 Embedding 模型服务。" -#: src/tool/validators.py:241 +#: src/tool/validators.py:271 msgid "基本对话测试失败" msgstr "基本对话测试失败" -#: src/tool/validators.py:244 +#: src/tool/validators.py:274 msgid "基本对话功能正常" msgstr "基本对话功能正常" -#: src/tool/validators.py:246 +#: src/tool/validators.py:276 msgid "对话响应为空" msgstr "对话响应为空" -#: src/tool/validators.py:318 +#: src/tool/validators.py:348 msgid "不支持任何 function_call 格式" msgstr "不支持任何 function_call 格式" -#: src/tool/validators.py:353 +#: src/tool/validators.py:383 #, python-brace-format msgid "tools 格式测试失败: {error}" msgstr "tools 格式测试失败: {error}" -#: src/tool/validators.py:358 +#: src/tool/validators.py:388 msgid "支持 tools 格式的 function_call" msgstr "支持 tools 格式的 function_call" -#: src/tool/validators.py:360 +#: src/tool/validators.py:390 msgid "不支持工具调用功能" msgstr "不支持工具调用功能" -#: src/tool/validators.py:401 +#: src/tool/validators.py:431 #, python-brace-format msgid "structured_output 格式测试失败: {error}" msgstr "structured_output 格式测试失败: {error}" -#: src/tool/validators.py:409 +#: src/tool/validators.py:439 msgid "structured_output 响应不是有效 JSON" msgstr "structured_output 响应不是有效 JSON" -#: src/tool/validators.py:411 +#: src/tool/validators.py:441 msgid "支持 structured_output 格式" msgstr "支持 structured_output 格式" -#: src/tool/validators.py:413 +#: src/tool/validators.py:443 msgid "structured_output 响应为空" msgstr "structured_output 响应为空" -#: src/tool/validators.py:439 +#: src/tool/validators.py:469 #, python-brace-format msgid "json_mode 格式测试失败: {error}" msgstr "json_mode 格式测试失败: {error}" -#: src/tool/validators.py:447 +#: src/tool/validators.py:477 msgid "json_mode 响应不是有效 JSON" msgstr "json_mode 响应不是有效 JSON" -#: src/tool/validators.py:449 +#: src/tool/validators.py:479 msgid "支持 json_mode 格式" msgstr "支持 json_mode 格式" -#: src/tool/validators.py:451 +#: src/tool/validators.py:481 msgid "json_mode 响应为空" msgstr "json_mode 响应为空" -#: src/tool/validators.py:499 +#: src/tool/validators.py:529 msgid "支持 vLLM 结构化输出(部分支持)" msgstr "支持 vLLM 结构化输出(部分支持)" -#: src/tool/validators.py:504 +#: src/tool/validators.py:534 #, python-brace-format msgid "不支持 vLLM guided_json 格式: {error}" msgstr "不支持 vLLM guided_json 格式: {error}" -#: src/tool/validators.py:508 +#: src/tool/validators.py:538 msgid "vLLM guided_json 响应无效" msgstr "vLLM guided_json 响应无效" -#: src/tool/validators.py:555 +#: src/tool/validators.py:585 msgid "支持 Ollama function_call 格式" msgstr "支持 Ollama function_call 格式" -#: src/tool/validators.py:558 +#: src/tool/validators.py:588 #, python-brace-format msgid "不支持 Ollama function_call 格式: {error}" msgstr "不支持 Ollama function_call 格式: {error}" -#: src/tool/validators.py:561 +#: src/tool/validators.py:591 msgid "Ollama function_call 响应无效" msgstr "Ollama function_call 响应无效" -#: src/tool/validators.py:589 +#: src/tool/validators.py:619 #, python-brace-format msgid "OpenAI Embedding 配置验证失败: {error}" msgstr "OpenAI Embedding 配置验证失败: {error}" -#: src/tool/validators.py:598 +#: src/tool/validators.py:628 #, python-brace-format msgid "OpenAI Embedding 配置验证成功 - 维度: {dimension}" msgstr "OpenAI Embedding 配置验证成功 - 维度: {dimension}" -#: src/tool/validators.py:606 +#: src/tool/validators.py:636 msgid "OpenAI Embedding 响应为空" msgstr "OpenAI Embedding 响应为空" -#: src/tool/validators.py:634 +#: src/tool/validators.py:664 #, python-brace-format msgid "MindIE Embedding 配置验证成功 - 维度: {dimension}" msgstr "MindIE Embedding 配置验证成功 - 维度: {dimension}" -#: src/tool/validators.py:644 +#: src/tool/validators.py:674 msgid "MindIE Embedding 响应格式不正确" msgstr "MindIE Embedding 响应格式不正确" -#: src/tool/validators.py:652 +#: src/tool/validators.py:682 #, python-brace-format msgid "MindIE Embedding 配置验证失败: {error}" msgstr "MindIE Embedding 配置验证失败: {error}" -#: src/tool/validators.py:674 +#: src/tool/validators.py:704 msgid "服务 URL 必须以 http:// 或 https:// 开头" msgstr "服务 URL 必须以 http:// 或 https:// 开头" -#: src/tool/validators.py:685 +#: src/tool/validators.py:715 msgid "访问令牌格式无效" msgstr "访问令牌格式无效" -#: src/tool/validators.py:710 +#: src/tool/validators.py:740 msgid "服务返回的数据格式不正确" msgstr "服务返回的数据格式不正确" -#: src/tool/validators.py:716 +#: src/tool/validators.py:746 msgid "连接成功" msgstr "连接成功" -#: src/tool/validators.py:718 +#: src/tool/validators.py:748 #, python-brace-format msgid "服务返回错误代码: {code}" msgstr "服务返回错误代码: {code}" -#: src/tool/validators.py:721 +#: src/tool/validators.py:751 msgid "无法连接到服务,请检查 URL 和网络连接" msgstr "无法连接到服务,请检查 URL 和网络连接" -#: src/tool/validators.py:723 +#: src/tool/validators.py:753 msgid "连接超时,请检查网络连接或服务状态" msgstr "连接超时,请检查网络连接或服务状态" -#: src/tool/validators.py:726 +#: src/tool/validators.py:756 #, python-brace-format msgid "连接验证失败: {error}" msgstr "连接验证失败: {error}" -#: src/tool/validators.py:732 +#: src/tool/validators.py:762 msgid "访问令牌无效或已过期" msgstr "访问令牌无效或已过期" -#: src/tool/validators.py:733 +#: src/tool/validators.py:763 msgid "访问权限不足" msgstr "访问权限不足" -#: src/tool/validators.py:734 +#: src/tool/validators.py:764 msgid "API 接口不存在,请检查服务版本" msgstr "API 接口不存在,请检查服务版本" -#: src/tool/validators.py:737 +#: src/tool/validators.py:767 #, python-brace-format msgid "服务响应异常,状态码: {status_code}" msgstr "服务响应异常,状态码: {status_code}" @@ -788,3 +2101,118 @@ msgstr "" #: src/tool/command_processor.py:206 msgid "读取 stderr 失败" msgstr "读取 stderr 失败" + +#: src/tool/oi_login.py:67 +#, python-brace-format +msgid "✗ Failed to get authorization URL: {error}\n" +msgstr "✗ 获取授权 URL 失败: {error}\n" + +#: src/tool/oi_login.py:136 +msgid "✗ Error: Browser is not available in current environment\n" +msgstr "✗ 错误: 当前环境不支持浏览器\n" + +#: src/tool/oi_login.py:137 +msgid "This feature requires a graphical environment with browser support.\n" +msgstr "此功能需要图形环境和浏览器支持。\n" + +#: src/tool/oi_login.py:138 +msgid "If you are using SSH, please run this command on the server directly\n" +msgstr "如果您正在使用 SSH,请直接在服务器上运行此命令\n" + +#: src/tool/oi_login.py:139 +msgid "or use X11 forwarding / VNC to enable graphical access.\n" +msgstr "或使用 X11 转发 / VNC 来启用图形访问。\n" + +#: src/tool/oi_login.py:151 +msgid "" +"\n" +"\n" +"✗ Login cancelled by user\n" +msgstr "" +"\n" +"\n" +"✗ 用户取消登录\n" + +#: src/tool/oi_login.py:155 +#, python-brace-format +msgid "" +"\n" +"✗ An error occurred during login: {error}\n" +msgstr "" +"\n" +"✗ 登录过程中发生错误: {error}\n" + +#: src/tool/oi_login.py:169 +msgid "✗ Error: openEuler Intelligence URL not configured\n" +msgstr "✗ 错误: openEuler Intelligence URL 未配置\n" + +#: src/tool/oi_login.py:170 +msgid "Please run deployment initialization first: oi --init\n" +msgstr "请先运行部署初始化: oi --init\n" + +#: src/tool/oi_login.py:181 +msgid "Getting authorization URL from server...\n" +msgstr "正在从服务器获取授权 URL...\n" + +#: src/tool/oi_login.py:185 +msgid "✗ Failed to get authorization URL\n" +msgstr "✗ 获取授权 URL 失败\n" + +#: src/tool/oi_login.py:194 +msgid "Opening browser for login...\n" +msgstr "正在打开浏览器进行登录...\n" + +#: src/tool/oi_login.py:195 +msgid "If the browser doesn't open automatically, please visit:\n" +msgstr "如果浏览器没有自动打开,请访问:\n" + +#: src/tool/oi_login.py:202 +msgid "Waiting for login to complete...\n" +msgstr "等待登录完成...\n" + +#: src/tool/oi_login.py:215 +msgid "" +"\n" +"✓ Login successful!\n" +msgstr "" +"\n" +"✓ 登录成功!\n" + +#: src/tool/oi_login.py:216 +msgid "✓ API Key has been saved to configuration\n" +msgstr "✓ API 密钥已保存到配置\n" + +#: src/tool/oi_login.py:219 +msgid "" +"\n" +"✗ Login failed: No session ID received\n" +msgstr "" +"\n" +"✗ 登录失败: 未收到会话 ID\n" + +#: src/tool/oi_login.py:223 +#, python-brace-format +msgid "" +"\n" +"✗ Login failed: {error}\n" +msgstr "" +"\n" +"✗ 登录失败: {error}\n" + +#: src/tool/oi_login.py:226 +msgid "" +"\n" +"✗ Login failed: Unknown result\n" +msgstr "" +"\n" +"✗ 登录失败: 未知结果\n" + +#~ msgid "需要用户确认是否执行此工具" +#~ msgstr "需要用户确认是否执行此工具" + +#~ msgid "需要补充参数" +#~ msgstr "需要补充参数" + +#, python-brace-format +#~ msgid "⏱️ MCP response timeout ({seconds} seconds)" +#~ msgstr "⏱️ MCP 响应超时 ({seconds}秒)" diff --git a/src/main.py b/src/main.py index bc2dd290644bbf969831f75ac19b5174fce0c23e..26ecc6d0150d0a65500575982c9d0e1f7c201e5e 100644 --- a/src/main.py +++ b/src/main.py @@ -17,7 +17,7 @@ from log.manager import ( get_logger, setup_logging, ) -from tool import backend_init, llm_config, select_agent +from tool import backend_init, browser_login, llm_config, select_agent def parse_args() -> argparse.Namespace: @@ -85,6 +85,17 @@ For more information and documentation, please visit: help=_("Select default agent"), ) + # 认证管理选项组 + auth_group = parser.add_argument_group( + _("Authentication Management Options"), + _("For managing login and authentication"), + ) + auth_group.add_argument( + "--login", + action="store_true", + help=_("Login via browser to obtain API key"), + ) + # 语言设置选项组 i18n_group = parser.add_argument_group( _("Language Settings"), @@ -162,7 +173,7 @@ def set_log_level(config_manager: ConfigManager, level: str) -> None: sys.stdout.write(_("✓ Logging system initialized\n")) -def main() -> None: +def main() -> None: # noqa: C901, PLR0911 """主函数""" # 首先初始化配置管理器 config_manager = ConfigManager() @@ -214,6 +225,11 @@ def main() -> None: set_log_level(config_manager, args.log_level) return + # 处理认证相关参数 + if args.login: + browser_login() + return + setup_logging(config_manager) # 在 TUI 模式下禁用控制台日志输出,避免干扰界面 disable_console_output() diff --git a/src/tool/__init__.py b/src/tool/__init__.py index 9816b701c6616be5365ab2feca7f10641444c6c2..d1ce5e616b236b1f7000ca1e5125ccb33dfae0c8 100644 --- a/src/tool/__init__.py +++ b/src/tool/__init__.py @@ -3,6 +3,14 @@ from .command_processor import is_command_safe, process_command from .oi_backend_init import backend_init from .oi_llm_config import llm_config +from .oi_login import browser_login from .oi_select_agent import select_agent -__all__ = ["backend_init", "is_command_safe", "llm_config", "process_command", "select_agent"] +__all__ = [ + "backend_init", + "browser_login", + "is_command_safe", + "llm_config", + "process_command", + "select_agent", +] diff --git a/src/tool/callback_server.py b/src/tool/callback_server.py new file mode 100644 index 0000000000000000000000000000000000000000..5ddd918d5b252675e606ceb4eae993a5c1e691e4 --- /dev/null +++ b/src/tool/callback_server.py @@ -0,0 +1,345 @@ +""" +HTTP 回调服务器 + +用于接收 OAuth2/OIDC 认证流程的回调 +通过本地 HTML 页面启动浏览器登录,并接收 postMessage 传递的 sessionId +""" + +import socket +import socketserver +import threading +from http.server import BaseHTTPRequestHandler +from threading import Thread +from typing import ClassVar +from urllib.parse import parse_qs, urlparse + +from log.manager import get_logger + +logger = get_logger(__name__) + + +class CallbackHandler(BaseHTTPRequestHandler): + """处理 OAuth2/OIDC 回调请求""" + + auth_result: ClassVar[dict] = {} + auth_event: ClassVar[threading.Event] = threading.Event() + auth_url: ClassVar[str] = "" # 存储授权 URL + + def do_GET(self) -> None: + """处理 GET 请求""" + parsed = urlparse(self.path) + + if parsed.path in {"/", "/launcher"}: + # 返回启动器页面 + self._send_launcher_page() + elif parsed.path == "/callback": + # 接收来自前端页面的 sessionId + params = parse_qs(parsed.query) + session_id = params.get("sessionId", [None])[0] + + if session_id: + CallbackHandler.auth_result = { + "type": "session", + "sessionId": session_id, + } + self._send_success_page() + # 设置事件,通知主线程认证完成 + CallbackHandler.auth_event.set() + else: + CallbackHandler.auth_result = { + "type": "error", + "error": "missing_session", + "error_description": "未收到 session ID", + } + self._send_error_page() + else: + # 其他路径返回 404 + self.send_response(404) + self.end_headers() + + def _send_launcher_page(self) -> None: + """发送启动器页面,用于打开授权 URL 并接收 postMessage""" + html = f""" + + + + + openEuler Intelligence - 浏览器登录 + + + +
+
openEuler Intelligence 浏览器登录
+
+
+

正在打开登录窗口...

+
+
+ + + + """ + self.send_response(200) + self.send_header("Content-type", "text/html; charset=utf-8") + self.end_headers() + self.wfile.write(html.encode("utf-8")) + + def _send_success_page(self) -> None: + """发送成功页面""" + html = """ + + + + + 登录成功 + + + +
✓ 登录成功!
+

认证信息已保存,您可以关闭此窗口。

+ + + """ + self.send_response(200) + self.send_header("Content-type", "text/html; charset=utf-8") + self.end_headers() + self.wfile.write(html.encode("utf-8")) + + def _send_error_page(self) -> None: + """发送错误页面""" + error = CallbackHandler.auth_result.get("error", "unknown") + error_desc = CallbackHandler.auth_result.get("error_description", "未知错误") + + html = f""" + + + + + 登录失败 + + + +
✗ 登录失败
+

错误: {error}

+

描述: {error_desc}

+

您可以关闭此窗口并返回终端重试。

+ + + """ + self.send_response(200) + self.send_header("Content-type", "text/html; charset=utf-8") + self.end_headers() + self.wfile.write(html.encode("utf-8")) + + def log_message(self, format: str, *args: object) -> None: # noqa: A002 + """重写日志方法,使用我们的 logger""" + logger.debug(format, *args) + + +class CallbackServer: + """回调服务器管理类""" + + def __init__(self, start_port: int = 8081, max_attempts: int = 20) -> None: + """ + 初始化回调服务器 + + Args: + start_port: 起始端口号,默认 8081 + max_attempts: 最大尝试次数,默认 20 + + """ + self.start_port = start_port + self.max_attempts = max_attempts + self.port = None + self.server = None + self.thread = None + + def _find_available_port(self) -> int: + """ + 查找可用端口 + + Returns: + 可用的端口号 + + Raises: + RuntimeError: 如果找不到可用端口 + + """ + for port in range(self.start_port, self.start_port + self.max_attempts): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + try: + sock.bind(("127.0.0.1", port)) + except OSError: + logger.debug("端口 %d 被占用,尝试下一个", port) + continue + else: + logger.info("找到可用端口: %d", port) + return port + + msg = f"无法找到可用端口 ({self.start_port}-{self.start_port + self.max_attempts})" + raise RuntimeError(msg) + + def start(self, auth_url: str) -> str: + """ + 启动服务器,返回 launcher URL + + Args: + auth_url: 授权 URL(从后端获取) + + Returns: + launcher 页面的 URL + + """ + # 重置状态 + CallbackHandler.auth_result = {} + CallbackHandler.auth_event.clear() + CallbackHandler.auth_url = auth_url + + # 查找可用端口 + self.port = self._find_available_port() + + # 创建服务器 + self.server = socketserver.TCPServer(("127.0.0.1", self.port), CallbackHandler) + + # 在新线程中启动服务器 + self.thread = Thread(target=self.server.serve_forever, daemon=True) + self.thread.start() + + launcher_url = f"http://127.0.0.1:{self.port}/launcher" + logger.info("回调服务器已启动: %s", launcher_url) + return launcher_url + + def wait_for_auth(self, timeout: int = 300) -> dict: + """ + 等待接收认证结果 + + Args: + timeout: 超时时间(秒),默认 5 分钟 + + Returns: + 认证结果字典 + + """ + logger.info("等待用户完成登录...") + success = CallbackHandler.auth_event.wait(timeout=timeout) + + if not success: + logger.error("等待登录超时") + return {"type": "error", "error": "timeout", "error_description": "登录超时"} + + return CallbackHandler.auth_result + + def stop(self) -> None: + """停止服务器""" + if self.server: + logger.info("正在关闭回调服务器...") + self.server.shutdown() + self.server.server_close() + if self.thread: + self.thread.join(timeout=2) + logger.info("回调服务器已关闭") diff --git a/src/tool/command_processor.py b/src/tool/command_processor.py index bfa91be860c25aa3a8e55eac4e2eec557228e467..5c7b8d7fdedfe05c7fb1887a10bf7d0d20dd244e 100644 --- a/src/tool/command_processor.py +++ b/src/tool/command_processor.py @@ -21,7 +21,7 @@ if TYPE_CHECKING: import logging from collections.abc import AsyncGenerator - from backend.base import LLMClientBase + from backend import LLMClientBase # 定义危险命令黑名单 BLACKLIST = ["rm", "sudo", "shutdown", "reboot", "mkfs"] diff --git a/src/tool/oi_backend_init.py b/src/tool/oi_backend_init.py index af07b372bfd70e17d954aaa10172cea8b592536a..768da7247722c2866679811cc350899c1a77c926 100644 --- a/src/tool/oi_backend_init.py +++ b/src/tool/oi_backend_init.py @@ -2,13 +2,13 @@ from __future__ import annotations -import json from pathlib import Path from textual.app import App from app.deployment import InitializationModeScreen from config.manager import ConfigManager +from i18n.manager import _ from log.manager import get_logger @@ -20,9 +20,6 @@ def backend_init() -> None: # 首先检查和更新配置文件 logger.info("检查配置文件...") - # 在部署阶段,使用普通配置管理器操作 root 用户的配置 - # 这样 Agent 初始化时可以正常写入 AppID 等信息 - # 部署完成后会将完整配置复制为全局模板 config_manager = ConfigManager() config_updated = config_manager.validate_and_update_config() @@ -39,7 +36,7 @@ def backend_init() -> None: """部署 TUI 应用""" CSS_PATH = css_path - TITLE = "openEuler Intelligence 部署助手" + TITLE = _("openEuler Intelligence 部署助手") def on_mount(self) -> None: """启动时先显示初始化模式选择界面""" @@ -49,9 +46,6 @@ def backend_init() -> None: result = app.run() logger.info("部署结果: %s", result) - # 部署完成后,强制从全局模板刷新当前用户的配置 - _refresh_user_config_from_template() - except KeyboardInterrupt: logger.warning("用户中断部署") except ImportError: @@ -61,48 +55,3 @@ def backend_init() -> None: except Exception: logger.exception("未预期的错误") raise - - -def _refresh_user_config_from_template() -> None: - """ - 部署完成后强制从全局模板刷新当前用户的配置 - - 确保部署时创建的全局模板配置能够应用到当前用户 - """ - logger = get_logger(__name__) - - try: - # 检查全局模板是否存在 - if not ConfigManager.GLOBAL_CONFIG_PATH.exists(): - logger.warning("全局配置模板不存在,跳过配置刷新") - return - - # 确保用户配置目录存在 - ConfigManager.USER_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) - - # 从全局模板读取配置 - with ConfigManager.GLOBAL_CONFIG_PATH.open(encoding="utf-8") as f: - template_config = json.load(f) - - # 如果用户配置已存在,保留用户的个性化设置(如 locale) - user_config = template_config.copy() - if ConfigManager.USER_CONFIG_PATH.exists(): - try: - with ConfigManager.USER_CONFIG_PATH.open(encoding="utf-8") as f: - existing_config = json.load(f) - # 保留用户的语言设置 - if "locale" in existing_config: - user_config["locale"] = existing_config["locale"] - except (json.JSONDecodeError, OSError): - logger.warning("读取现有用户配置失败,将使用模板配置") - - # 写入用户配置 - with ConfigManager.USER_CONFIG_PATH.open("w", encoding="utf-8") as f: - json.dump(user_config, f, indent=4, ensure_ascii=False) - - logger.info("已从全局模板刷新当前用户配置: %s", ConfigManager.USER_CONFIG_PATH) - - except (OSError, json.JSONDecodeError): - logger.exception("从全局模板刷新用户配置失败") - except Exception: - logger.exception("刷新用户配置时发生未预期的错误") diff --git a/src/tool/oi_login.py b/src/tool/oi_login.py new file mode 100644 index 0000000000000000000000000000000000000000..1371b517fa949b7b466719f07b710f282cf96650 --- /dev/null +++ b/src/tool/oi_login.py @@ -0,0 +1,234 @@ +""" +浏览器登录功能 + +实现通过浏览器跳转进行 openEuler Intelligence 登录 +""" + +import json +import sys +import time +import urllib.error +import urllib.request +import webbrowser + +from config.manager import ConfigManager +from i18n.manager import _ +from log.manager import get_logger +from tool.callback_server import CallbackServer +from tool.validators import is_browser_available + +logger = get_logger(__name__) + +# HTTP 状态码常量 +HTTP_OK = 200 + + +def get_auth_url(base_url: str) -> tuple[str | None, str | None]: + """ + 从后端获取授权 URL 和登录令牌 + + Args: + base_url: openEuler Intelligence 的基础 URL + + Returns: + (授权 URL, 登录令牌) 元组,如果获取失败则返回 (None, None) + + """ + base_url = base_url.rstrip("/") + request_url = f"{base_url}/api/auth/redirect?action=login" + logger.info("请求授权 URL: %s", request_url) + + if not request_url.startswith(("http://", "https://")): + logger.error("无效的 URL 协议: %s", request_url) + return None, None + + try: + with urllib.request.urlopen(request_url, timeout=10) as response: # noqa: S310 + data = json.loads(response.read().decode("utf-8")) + logger.debug("后端响应: %s", data) + + if data.get("code") == HTTP_OK and "result" in data: + result = data["result"] + auth_url = result.get("url") + login_token = result.get("token") # 后端可能返回一个临时令牌用于验证 + + if auth_url: + logger.info("获取到授权 URL: %s", auth_url) + return auth_url, login_token + + logger.error("响应中缺少 result.url 字段") + return None, None + + logger.error("后端返回错误: %s", data.get("message", "未知错误")) + return None, None + + except Exception as e: + logger.exception("获取授权 URL 失败") + sys.stderr.write(_("✗ Failed to get authorization URL: {error}\n").format(error=e)) + return None, None + + +def poll_login_status(base_url: str, max_attempts: int = 60, interval: int = 2) -> str | None: + """ + 轮询检查登录状态并获取 session + + Args: + base_url: openEuler Intelligence 的基础 URL + max_attempts: 最大尝试次数,默认 60 次(2 分钟) + interval: 轮询间隔(秒),默认 2 秒 + + Returns: + ECSESSION(API Key),如果登录失败或超时则返回 None + + """ + base_url = base_url.rstrip("/") + check_url = f"{base_url}/api/user/session" + + logger.info("开始轮询登录状态...") + + for attempt in range(1, max_attempts + 1): + try: + # 尝试获取当前 session + with urllib.request.urlopen(check_url, timeout=5) as response: # noqa: S310 + data = json.loads(response.read().decode("utf-8")) + + if data.get("code") == HTTP_OK and "result" in data: + result = data["result"] + session_id = result.get("sessionId") + if session_id: + logger.info("检测到登录成功,获得 session") + return session_id + + except urllib.error.HTTPError: + # HTTP 错误,可能还未登录 + pass + except Exception: + # 其他错误,继续尝试 + logger.exception("轮询出错:") + + # 显示进度 + _print_progress(attempt, max_attempts) + + # 等待后再次尝试 + if attempt < max_attempts: + time.sleep(interval) + + logger.error("登录超时") + return None + + +def browser_login() -> None: + """ + 执行浏览器登录流程 + + 1. 从配置读取 openEuler Intelligence 的地址 + 2. 获取授权 URL + 3. 启动本地回调服务器 + 4. 打开浏览器访问 launcher 页面 + 5. launcher 页面打开授权 URL 并接收 postMessage + 6. 接收 sessionId 并保存到配置 + + """ + logger.info("开始浏览器登录流程") + + # 检查浏览器是否可用 + if not is_browser_available(): + sys.stdout.write(_("✗ Error: Browser is not available in current environment\n")) + sys.stdout.write(_("This feature requires a graphical environment with browser support.\n")) + sys.stdout.write(_("If you are using SSH, please run this command on the server directly\n")) + sys.stdout.write(_("or use X11 forwarding / VNC to enable graphical access.\n")) + sys.exit(1) + + config_manager = _load_config_and_check_url() + callback_server = CallbackServer() + + try: + _initiate_login_flow(config_manager, callback_server) + auth_result = callback_server.wait_for_auth(timeout=300) + _handle_auth_result(auth_result, config_manager) + except KeyboardInterrupt: + logger.info("用户取消登录") + sys.stdout.write(_("\n\n✗ Login cancelled by user\n")) + sys.exit(130) # 标准的 SIGINT 退出码 + except Exception as e: + logger.exception("登录过程中发生错误") + sys.stdout.write(_("\n✗ An error occurred during login: {error}\n").format(error=e)) + sys.exit(1) + finally: + # 确保关闭服务器 + callback_server.stop() + + +def _load_config_and_check_url() -> ConfigManager: + """加载配置并检查 openEuler Intelligence URL。""" + config_manager = ConfigManager() + base_url = config_manager.get_eulerintelli_url() + + if not base_url: + sys.stdout.write( + _("✗ Error: openEuler Intelligence URL not configured\n") + + _("Please run deployment initialization first: oi --init\n"), + ) + sys.exit(1) + + logger.info("使用 openEuler Intelligence URL: %s", base_url) + return config_manager + + +def _initiate_login_flow(config_manager: ConfigManager, callback_server: CallbackServer) -> None: + """获取授权 URL,启动服务器并打开浏览器。""" + base_url = config_manager.get_eulerintelli_url() + sys.stdout.write(_("Getting authorization URL from server...\n")) + auth_url, _token = get_auth_url(base_url) + + if not auth_url: + sys.stdout.write(_("✗ Failed to get authorization URL\n")) + sys.exit(1) + + logger.info("授权 URL: %s", auth_url) + + # 启动回调服务器并获取 launcher URL + launcher_url = callback_server.start(auth_url) + + # 打开浏览器访问 launcher 页面 + sys.stdout.write(_("Opening browser for login...\n")) + sys.stdout.write(_("If the browser doesn't open automatically, please visit:\n")) + sys.stdout.write(f" {launcher_url}\n\n") + sys.stdout.flush() + + webbrowser.open(launcher_url) + + # 等待回调 + sys.stdout.write(_("Waiting for login to complete...\n")) + + +def _handle_auth_result(auth_result: dict, config_manager: ConfigManager) -> None: + """处理认证结果并保存 session。""" + result_type = auth_result.get("type") + + if result_type == "session": + session_id = auth_result.get("sessionId") + if session_id: + config_manager.set_eulerintelli_key(session_id) + logger.info("已保存 API Key 到配置") + + sys.stdout.write(_("\n✓ Login successful!\n")) + sys.stdout.write(_("✓ API Key has been saved to configuration\n")) + sys.exit(0) + else: + sys.stdout.write(_("\n✗ Login failed: No session ID received\n")) + sys.exit(1) + elif result_type == "error": + error_desc = auth_result.get("error_description", "未知错误") + sys.stdout.write(_("\n✗ Login failed: {error}\n").format(error=error_desc)) + sys.exit(1) + else: + sys.stdout.write(_("\n✗ Login failed: Unknown result\n")) + sys.exit(1) + + +def _print_progress(attempt: int, max_attempts: int) -> None: + """打印轮询进度""" + if attempt % 5 == 0: + sys.stdout.write(f" 等待登录... ({attempt}/{max_attempts})\n") + sys.stdout.flush() diff --git a/src/tool/oi_select_agent.py b/src/tool/oi_select_agent.py index c13023b28e444ad8b7bd51a0e628b236b2b5c40d..70e1cba47e1157d2e04bdb7e84c31c14cdbbaedd 100644 --- a/src/tool/oi_select_agent.py +++ b/src/tool/oi_select_agent.py @@ -10,7 +10,7 @@ from textual.app import App, ComposeResult from textual.containers import Container from app.dialogs import AgentSelectionDialog -from backend.factory import BackendFactory +from backend import BackendFactory from config.manager import ConfigManager from config.model import Backend from i18n.manager import _ diff --git a/src/tool/validators.py b/src/tool/validators.py index 23ff210c8e7b6c3d0a649ebade178c5124fc8d58..fb2a2aaa70f35719572a93658dc4856ddd1a18de 100644 --- a/src/tool/validators.py +++ b/src/tool/validators.py @@ -9,6 +9,7 @@ from __future__ import annotations import json import os +import webbrowser from typing import Any import httpx @@ -73,6 +74,35 @@ def should_verify_ssl(*, verify_ssl: bool | None = None) -> bool: return _resolve_verify_ssl(verify_ssl=verify_ssl) +def is_browser_available() -> bool: + """ + 检测浏览器是否可用 + + 在纯命令行或 SSH 环境下,webbrowser 模块无法正常工作。 + 此函数用于检测当前环境是否支持打开浏览器。 + + Returns: + True 如果可以打开浏览器,False 否则 + + """ + logger = get_logger(__name__) + + try: + # 尝试获取默认浏览器 + browser = webbrowser.get() + except webbrowser.Error: + # 没有可用的浏览器 + logger.debug("未检测到可用的浏览器") + return False + except (OSError, RuntimeError) as e: + # 其他异常也认为浏览器不可用 + logger.debug("检测浏览器可用性时发生异常: %s", e) + return False + else: + # 如果能获取到浏览器实例,认为可用 + return browser is not None + + class APIValidator: """API 配置验证器""" diff --git a/tests/app/deployment/test_agent_manager.py b/tests/app/deployment/test_agent_manager.py deleted file mode 100644 index 7e4823a24545c65833dd6aab5034543e1dfde055..0000000000000000000000000000000000000000 --- a/tests/app/deployment/test_agent_manager.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -测试 AgentManager 的 RPM 包安装功能 - -这个脚本会测试: -1. 检查 systrace 配置目录是否存在 -2. 模拟 RPM 包安装过程 - -使用方法: source .venv/bin/activate && PYTHONPATH=src python tests/app/deployment/test_agent_manager.py -""" - -import asyncio -import sys -from pathlib import Path - -from app.deployment.agent import AgentManager -from app.deployment.models import DeploymentState - -# 添加项目路径到 Python 模块搜索路径 -project_root = Path(__file__).parent -sys.path.insert(0, str(project_root / "src")) - - -def progress_callback(state: DeploymentState) -> None: - """打印进度信息""" - if state.output_log: - _output(f"[进度] {state.output_log[-1]}") - - -async def test_agent_manager() -> None: - """测试 AgentManager 的功能""" - _output("开始测试 AgentManager...") - - # 创建 AgentManager 实例 - manager = AgentManager() - - if not manager.resource_dir: - _output("❌ 资源目录未找到,测试终止") - return - - _output(f"✅ 找到资源目录: {manager.resource_dir}") - _output(f"✅ MCP 配置目录: {manager.mcp_config_dir}") - - # 测试检查 systrace 配置 - state = DeploymentState() - systrace_exists = manager._check_systrace_config(state, progress_callback) # noqa: SLF001 - _output(f"sysTrace 配置检查结果: {systrace_exists}") - - # 测试必要包安装功能(模拟) - _output("\n测试必要包安装功能(模拟)...") - result = await manager._install_prerequisite_packages(state, progress_callback) # noqa: SLF001 - _output(f"必要包安装结果: {result}") - - # 测试安装必要的包(仅模拟) - _output("\n测试 RPM 包安装功能(仅检查文件存在性)...") - - # 检查 sysTrace.rpmlist 文件 - systrace_rpm_file = manager.resource_dir / "sysTrace.rpmlist" - if systrace_rpm_file.exists(): - _output(f"✅ 找到 sysTrace.rpmlist: {systrace_rpm_file}") - with systrace_rpm_file.open() as f: - packages = [line.strip() for line in f if line.strip() and not line.startswith("#")] - _output(f" 需要安装的包: {packages}") - else: - _output(f"⚠️ sysTrace.rpmlist 文件不存在: {systrace_rpm_file}") - - # 检查 mcp-servers.rpmlist 文件 - mcp_rpm_file = manager.resource_dir / "mcp-servers.rpmlist" - if mcp_rpm_file.exists(): - _output(f"✅ 找到 mcp-servers.rpmlist: {mcp_rpm_file}") - with mcp_rpm_file.open() as f: - packages = [line.strip() for line in f if line.strip() and not line.startswith("#")] - _output(f" 需要安装的包: {packages}") - else: - _output(f"⚠️ mcp-servers.rpmlist 文件不存在: {mcp_rpm_file}") - - _output("\n✅ 测试完成") - - -def _output(message: str = "") -> None: - """输出消息到标准输出""" - sys.stdout.write(f"{message}\n") - sys.stdout.flush() - - -if __name__ == "__main__": - asyncio.run(test_agent_manager()) diff --git a/tests/app/deployment/test_agent_skip.py b/tests/app/deployment/test_agent_skip.py deleted file mode 100644 index 659fb4322029772d7d0341c025e0039c93508eee..0000000000000000000000000000000000000000 --- a/tests/app/deployment/test_agent_skip.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -测试智能体初始化跳过功能 - -验证当 RPM 包不可用时,智能体初始化会被跳过但部署继续进行。 -使用方法: source .venv/bin/activate && PYTHONPATH=src python tests/app/deployment/test_agent_skip.py -""" - -import asyncio -import sys -import traceback -from pathlib import Path - -from app.deployment.agent import AgentManager -from app.deployment.models import AgentInitStatus, DeploymentState - -# 添加 src 目录到 Python 路径 -src_path = Path(__file__).parent / "src" -sys.path.insert(0, str(src_path)) - - -class TestProgress: - """测试用的进度回调类""" - - def __call__(self, state: DeploymentState) -> None: - """显示进度信息""" - if state.output_log: - # 获取最新的日志条目 - latest_log = state.output_log[-1] - _output(f"[测试] {latest_log}") - - -async def test_agent_init_skip() -> bool: - """测试智能体初始化跳过功能""" - _output("=== 测试智能体初始化跳过功能 ===") - - # 创建 AgentManager 实例 - agent_manager = AgentManager() - - if not agent_manager.resource_dir: - _output("❌ 未找到资源目录,测试失败") - return False - - # 创建测试状态和回调 - callback = TestProgress() - - _output(f"\n资源目录: {agent_manager.resource_dir}") - - # 执行智能体初始化 - _output("\n开始测试智能体初始化...") - init_status = await agent_manager.initialize_agents(callback) - - _output("\n=== 测试结果 ===") - _output(f"初始化状态: {init_status}") - - if init_status == AgentInitStatus.SUCCESS: - _output("✅ 智能体初始化成功") - return True - if init_status == AgentInitStatus.SKIPPED: - _output("⚠️ 智能体初始化已跳过(这是预期结果)") - _output("✅ 测试通过:部署应该继续进行并显示成功") - return True - # FAILED - _output("❌ 智能体初始化失败") - return False - - -async def main() -> None: - """主函数""" - try: - result = await test_agent_init_skip() - - _output("\n=== 总结 ===") - if result: - _output("✅ 测试通过:智能体初始化跳过逻辑正常工作") - _output("📋 部署流程应该显示:'⚠ Agent 初始化已跳过(RPM 包不可用),但部署将继续进行'") - _output("🎯 最终部署结果应该显示为成功") - else: - _output("❌ 测试失败:智能体初始化跳过逻辑有问题") - - except Exception as e: # noqa: BLE001 - _output(f"❌ 测试执行失败: {e}") - - traceback.print_exc() - - -def _output(message: str = "") -> None: - """输出消息到标准输出""" - sys.stdout.write(f"{message}\n") - sys.stdout.flush() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/tests/app/deployment/test_validate_llm_config.py b/tests/app/deployment/test_validate_llm_config.py index 2ee022895623e467b9fde16af5af9b8e867f0c31..9cda74899f9bae540ff93bbc9a4df6464f7fc45f 100644 --- a/tests/app/deployment/test_validate_llm_config.py +++ b/tests/app/deployment/test_validate_llm_config.py @@ -51,7 +51,6 @@ async def main() -> None: _output("=" * 40) config = DeploymentConfig( - server_ip="127.0.0.1", deployment_mode="light", llm=LLMConfig( endpoint="http://127.0.0.1:1234/v1", diff --git a/tests/backend/test_llm_id_validation.py b/tests/backend/test_llm_id_validation.py new file mode 100644 index 0000000000000000000000000000000000000000..adb850f81bae4b284cd7c89504dbb8c1ed8bfc06 --- /dev/null +++ b/tests/backend/test_llm_id_validation.py @@ -0,0 +1,57 @@ +"""测试 HermesChatClient 的 llm_id 验证功能""" + +import asyncio +from unittest.mock import Mock + +from backend.hermes.client import HermesChatClient +from backend.hermes.exceptions import HermesAPIError + + +async def test_llm_id_validation(): + """测试 llm_id 验证""" + print("=" * 60) + print("测试 llm_id 验证功能") + print("=" * 60 + "\n") + + # 测试 1: 没有 llm_id 应该抛出异常 + print("测试 1: 创建没有 llm_id 的客户端(模拟未配置)") + # 创建一个模拟的 ConfigManager,返回空的 llm_id + mock_config_empty = Mock() + mock_config_empty.get_llm_chat_model.return_value = "" + + client = HermesChatClient( + base_url="http://localhost:8000", + config_manager=mock_config_empty, + ) + print(f" 配置的 llm_id: '{client._get_llm_id()}'") + + try: + # 尝试生成响应,应该抛出异常 + print(" 尝试生成响应...") + async for _ in client.get_llm_response("测试"): + pass + print(" ✗ 应该抛出异常但没有") + except HermesAPIError as e: + print(f" ✓ 成功捕获异常 (状态码: {e.status_code})") + print(f"\n错误消息:\n{e.message}\n") + + # 测试 2: 有 llm_id 的客户端不应该在验证阶段抛出异常 + print("测试 2: 创建有 llm_id 的客户端(模拟已配置)") + # 创建一个模拟的 ConfigManager,返回有效的 llm_id + mock_config_with_llm = Mock() + mock_config_with_llm.get_llm_chat_model.return_value = "test-model-id" + + client_with_llm = HermesChatClient( + base_url="http://localhost:8000", + config_manager=mock_config_with_llm, + ) + print(f" 配置的 llm_id: '{client_with_llm._get_llm_id()}'") + print(" ✓ llm_id 验证应该通过(实际请求会因为连接问题失败)\n") + + print("=" * 60) + print("测试完成") + print("=" * 60) + + +if __name__ == "__main__": + asyncio.run(test_llm_id_validation()) diff --git a/tests/backend/test_model_info.py b/tests/backend/test_model_info.py new file mode 100644 index 0000000000000000000000000000000000000000..71c493dac595cf18cb5bbe7c7857047f8079113b --- /dev/null +++ b/tests/backend/test_model_info.py @@ -0,0 +1,97 @@ +""" +测试 ModelInfo 数据类 + +运行方法: + +```shell +source .venv/bin/activate && PYTHONPATH=src python tests/backend/test_model_info.py +``` +""" + +from backend.models import LLMType, ModelInfo + +# 测试常量 +GPT4_MAX_TOKENS = 8192 + + +def test_model_info_creation() -> None: + """测试创建 ModelInfo 对象""" + # OpenAI 风格(只有 model_name) + openai_model = ModelInfo(model_name="gpt-4") + assert openai_model.model_name == "gpt-4" + assert openai_model.llm_id is None + assert openai_model.llm_description is None + assert openai_model.llm_type == [] + assert openai_model.max_tokens is None + + +def test_model_info_hermes_full() -> None: + """测试创建完整的 Hermes ModelInfo 对象""" + hermes_model = ModelInfo( + model_name="gpt-4", + llm_id="gpt-4", + llm_description="OpenAI GPT-4 model", + llm_type=[LLMType.CHAT, LLMType.FUNCTION], + max_tokens=GPT4_MAX_TOKENS, + ) + assert hermes_model.model_name == "gpt-4" + assert hermes_model.llm_id == "gpt-4" + assert hermes_model.llm_description == "OpenAI GPT-4 model" + assert hermes_model.llm_type == [LLMType.CHAT, LLMType.FUNCTION] + assert hermes_model.max_tokens == GPT4_MAX_TOKENS + + +def test_model_info_string_representation() -> None: + """测试 ModelInfo 的字符串表示""" + model = ModelInfo( + model_name="gpt-3.5-turbo", + llm_id="gpt-3.5-turbo", + ) + assert str(model) == "gpt-3.5-turbo" + assert "gpt-3.5-turbo" in repr(model) + + +def test_parse_llm_types_valid() -> None: + """测试解析合法的 LLM 类型""" + # 测试所有合法类型 + valid_types = ["chat", "function", "embedding", "vision", "thinking"] + parsed = ModelInfo.parse_llm_types(valid_types) + expected = [ + LLMType.CHAT, + LLMType.FUNCTION, + LLMType.EMBEDDING, + LLMType.VISION, + LLMType.THINKING, + ] + assert len(parsed) == len(expected) + assert parsed == expected + + +def test_parse_llm_types_invalid() -> None: + """测试过滤不合法的 LLM 类型""" + # 包含合法和不合法的类型 + mixed_types = ["chat", "invalid_type", "function", "unknown", "vision"] + parsed = ModelInfo.parse_llm_types(mixed_types) + # 只保留合法的类型 + expected = [LLMType.CHAT, LLMType.FUNCTION, LLMType.VISION] + assert len(parsed) == len(expected) + assert parsed == expected + + +def test_parse_llm_types_empty() -> None: + """测试空列表和 None""" + assert ModelInfo.parse_llm_types([]) == [] + assert ModelInfo.parse_llm_types(None) == [] + + +if __name__ == "__main__": + test_model_info_creation() + test_model_info_hermes_full() + test_model_info_string_representation() + test_parse_llm_types_valid() + test_parse_llm_types_invalid() + test_parse_llm_types_empty() + # 测试完成 + import sys + + sys.stdout.write("所有测试通过!✅\n") diff --git a/tests/tool/test_browser_availability.py b/tests/tool/test_browser_availability.py new file mode 100644 index 0000000000000000000000000000000000000000..b6ee9080052c2387e4f1976c2ca9bc069ddf4c47 --- /dev/null +++ b/tests/tool/test_browser_availability.py @@ -0,0 +1,61 @@ +""" +测试浏览器可用性检测功能 + +验证在不同环境下浏览器可用性检测是否正常工作。 +""" + +import sys +import unittest +import webbrowser +from pathlib import Path +from unittest.mock import MagicMock, patch + +# 添加 src 目录到 Python 路径 +sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src")) + +from tool.validators import is_browser_available + + +class TestBrowserAvailability(unittest.TestCase): + """测试浏览器可用性检测""" + + @patch("tool.validators.webbrowser.get") + def test_browser_available(self, mock_get: MagicMock) -> None: + """测试浏览器可用的情况""" + # 模拟浏览器可用 + mock_browser = MagicMock() + mock_get.return_value = mock_browser + + result = is_browser_available() + assert result is True + + @patch("tool.validators.webbrowser.get") + def test_browser_not_available_error(self, mock_get: MagicMock) -> None: + """测试浏览器不可用的情况(抛出 Error)""" + # 模拟浏览器不可用 + mock_get.side_effect = webbrowser.Error("No web browser found") + + result = is_browser_available() + assert result is False + + @patch("tool.validators.webbrowser.get") + def test_browser_not_available_exception(self, mock_get: MagicMock) -> None: + """测试浏览器检测时出现异常的情况""" + # 模拟检测时出现异常 + mock_get.side_effect = OSError("Failed to detect browser") + + result = is_browser_available() + assert result is False + + @patch("tool.validators.webbrowser.get") + def test_browser_returns_none(self, mock_get: MagicMock) -> None: + """测试浏览器返回 None 的情况""" + # 模拟浏览器返回 None + mock_get.return_value = None + + result = is_browser_available() + assert result is False + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/tool/test_login.py b/tests/tool/test_login.py new file mode 100644 index 0000000000000000000000000000000000000000..70e762005e2078aa48f040ef39b7dbdad9f1301e --- /dev/null +++ b/tests/tool/test_login.py @@ -0,0 +1,106 @@ +"""测试登录功能模块""" + +import json +import unittest +from unittest.mock import MagicMock, Mock, patch + +from tool.callback_server import CallbackServer +from tool.oi_login import get_auth_url + + +class TestLoginFunctions(unittest.TestCase): + """测试登录相关函数""" + + @patch("urllib.request.urlopen") + def test_get_auth_url_success(self, mock_urlopen: MagicMock) -> None: + """测试成功获取授权 URL""" + # 模拟后端响应(包含 Web 端的 redirect_uri) + mock_response = Mock() + mock_response.read.return_value = json.dumps( + { + "code": 200, + "message": "success", + "result": { + "url": "https://auth.example.com/login?client_id=123&redirect_uri=https://web.example.com/callback", + }, + }, + ).encode("utf-8") + mock_response.__enter__ = Mock(return_value=mock_response) + mock_response.__exit__ = Mock(return_value=False) + mock_urlopen.return_value = mock_response + + callback_url = "http://localhost:8081/callback" + result = get_auth_url("https://api.example.com", callback_url) + + # 验证返回的 URL 包含本地回调地址 + self.assertIsNotNone(result) + self.assertIn("redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fcallback", result) # type: ignore[ignore] + self.assertIn("client_id=123", result) # type: ignore[ignore] + + @patch("urllib.request.urlopen") + def test_get_auth_url_error_response(self, mock_urlopen: MagicMock) -> None: + """测试后端返回错误""" + # 模拟后端错误响应 + mock_response = Mock() + mock_response.read.return_value = json.dumps({"code": 400, "message": "Bad request", "result": None}).encode( + "utf-8", + ) + mock_response.__enter__ = Mock(return_value=mock_response) + mock_response.__exit__ = Mock(return_value=False) + mock_urlopen.return_value = mock_response + + result = get_auth_url("https://api.example.com", "http://localhost:8081/callback") + + self.assertIsNone(result) + + +class TestCallbackServer(unittest.TestCase): + """测试回调服务器""" + + @patch("socket.socket") + def test_find_available_port_success(self, mock_socket_class: MagicMock) -> None: + """测试查找可用端口成功""" + mock_socket = MagicMock() + mock_socket_class.return_value.__enter__.return_value = mock_socket + + # 模拟第一次尝试成功 + mock_socket.bind.return_value = None + + server = CallbackServer(start_port=8081) + port = server._find_available_port() # noqa: SLF001 + + self.assertEqual(port, 8081) + + @patch("socket.socket") + def test_find_available_port_retry(self, mock_socket_class: MagicMock) -> None: + """测试查找可用端口需要重试""" + mock_socket = MagicMock() + mock_socket_class.return_value.__enter__.return_value = mock_socket + + # 模拟前两次失败,第三次成功 + mock_socket.bind.side_effect = [OSError, OSError, None] + + server = CallbackServer(start_port=8081) + port = server._find_available_port() # noqa: SLF001 + + self.assertEqual(port, 8083) + + @patch("socket.socket") + def test_find_available_port_failure(self, mock_socket_class: MagicMock) -> None: + """测试查找可用端口全部失败""" + mock_socket = MagicMock() + mock_socket_class.return_value.__enter__.return_value = mock_socket + + # 模拟所有尝试都失败 + mock_socket.bind.side_effect = OSError + + server = CallbackServer(start_port=8081, max_attempts=3) + + with self.assertRaises(RuntimeError) as context: + server._find_available_port() # noqa: SLF001 + + self.assertIn("无法找到可用端口", str(context.exception)) + + +if __name__ == "__main__": + unittest.main()