From aa12c92a5ada4db1c5f996304c898fb875651b5b Mon Sep 17 00:00:00 2001 From: "WIN-JHFT4D3SIVT\\caoxiaozhu" Date: Wed, 8 Apr 2026 16:46:02 +0800 Subject: [PATCH] feat(temple): add Temple modal with Tools browser and Skills management --- backend/app/routers/tools.py | 348 +++++++++ backend/app/schemas/tools.py | 76 ++ development-doc/plan/temple-update/README.md | 165 +++++ .../plan/temple-update/checklist.md | 60 ++ .../temple-update/phase-0-current-state.md | 171 +++++ .../plan/temple-update/phase-1-tools-api.md | 135 ++++ .../temple-update/phase-2-tools-frontend.md | 167 +++++ .../phase-3-skills-integration.md | 88 +++ frontend/src/api/tools.ts | 69 ++ .../src/pages/temple/composables/useTemple.ts | 133 ++++ frontend/src/pages/temple/index.vue | 667 ++++++++++++++++-- frontend/src/pages/temple/templePage.css | 533 ++++++++++++++ 12 files changed, 2571 insertions(+), 41 deletions(-) create mode 100644 backend/app/routers/tools.py create mode 100644 backend/app/schemas/tools.py create mode 100644 development-doc/plan/temple-update/README.md create mode 100644 development-doc/plan/temple-update/checklist.md create mode 100644 development-doc/plan/temple-update/phase-0-current-state.md create mode 100644 development-doc/plan/temple-update/phase-1-tools-api.md create mode 100644 development-doc/plan/temple-update/phase-2-tools-frontend.md create mode 100644 development-doc/plan/temple-update/phase-3-skills-integration.md create mode 100644 frontend/src/api/tools.ts create mode 100644 frontend/src/pages/temple/composables/useTemple.ts create mode 100644 frontend/src/pages/temple/templePage.css diff --git a/backend/app/routers/tools.py b/backend/app/routers/tools.py new file mode 100644 index 0000000..dec271a --- /dev/null +++ b/backend/app/routers/tools.py @@ -0,0 +1,348 @@ +"""Tools API Router + +聚合两套工具体系的元数据: +1. 注册层 (app/tools/) - YAML manifest 定义 +2. Agent 层 (app/agents/tools/) - @tool 装饰器定义 +""" + +import re +import importlib + +from fastapi import APIRouter, Depends + +from app.routers.auth import get_current_user +from app.models.user import User +from app.schemas.tools import ( + ToolsResponse, + ToolCategory, + ToolSubgroup, + ToolInfo, + ToolCommand, + ToolStats, + ToolSummary, +) + +router = APIRouter(prefix="/api/tools", tags=["Tools"]) + +# ============================================================ +# 辅助函数 +# ============================================================ + + +def _parse_command_from_docstring(docstring: str) -> dict: + """从函数的 docstring 解析参数信息""" + params = {"type": "object", "properties": {}, "required": []} + if not docstring: + return params + + # 简单解析 Args: 段落 + args_match = re.search( + r"Args:\s*(.*?)(?=\n\s*(?:Returns?|Raises?)|$", docstring, re.DOTALL | re.IGNORECASE + ) + if args_match: + args_section = args_match.group(1) + # 匹配形如 "arg_name (type): description" 的行 + for line in args_section.strip().split("\n"): + line = line.strip() + if not line: + continue + # 匹配: "name (type): description" 或 "name: description" + m = re.match(r"(\w+)\s*(?:\(\s*(\w+)\s*\))?\s*:", line) + if m: + param_name = m.group(1) + params["properties"][param_name] = {"type": "string", "description": line} + params["required"].append(param_name) + + return params + + +def _build_agent_tools() -> list[ToolInfo]: + """扫描 app/agents/tools/ 目录,内省 @tool 装饰器""" + tools: list[ToolInfo] = [] + + # 分类映射:文件名 -> (分类名, 子分类名) + category_map = { + "search": ("Agent层", "知识检索"), + "schedule": ("Agent层", "日程管理"), + "task": ("Agent层", "任务管理"), + "forum": ("Agent层", "论坛功能"), + "time_reasoning": ("Agent层", "时间推理"), + "builtins/file_tools": ("Agent层", "文件工具"), + "builtins/system_tools": ("Agent层", "系统命令"), + "builtins/dev_tools": ("Agent层", "开发工具"), + "builtins/collaboration_tools": ("Agent层", "协作工具"), + } + + # 工具名称 -> 中文显示名 + display_names = { + "search_knowledge": "知识库搜索", + "get_knowledge_graph_context": "知识图谱查询", + "build_knowledge_graph": "构建知识图谱", + "hybrid_search": "混合搜索", + "web_search": "联网搜索", + "get_schedule_day": "获取日程", + "create_todo": "创建待办", + "create_schedule_task": "创建日程任务", + "create_reminder": "创建提醒", + "create_goal": "创建目标", + "get_tasks": "获取任务列表", + "create_task": "创建任务", + "update_task_status": "更新任务状态", + "get_forum_posts": "获取论坛帖子", + "create_forum_post": "发布论坛帖子", + "scan_forum_for_instructions": "扫描论坛指令", + "resolve_time_expression": "解析时间表达式", + "glob": "文件路径匹配", + "grep": "文件内容搜索", + "read_file": "读取文件", + "write_file": "写入文件", + "bash": "Bash命令", + "powershell": "PowerShell命令", + "git": "Git操作", + "lsp_tools": "LSP代码导航", + "team_agent": "团队Agent通信", + "task_broadcast": "任务广播", + } + + # 工具描述 + descriptions = { + "search_knowledge": "搜索用户的私人知识库,返回最相关的文档片段", + "get_knowledge_graph_context": "获取用户知识图谱的上下文信息", + "build_knowledge_graph": "从文档构建/更新知识图谱", + "hybrid_search": "混合搜索,结合向量语义检索和关键词匹配", + "web_search": "通过 SearxNG 搜索外部网页信息", + "get_schedule_day": "获取指定日期的 todo/task/reminder/goal 聚合信息", + "create_todo": "创建指定日期的待办", + "create_schedule_task": "创建任务,支持优先级和截止日期", + "create_reminder": "创建提醒,支持自然语言时间", + "create_goal": "创建指定日期的目标", + "get_tasks": "获取用户当前的任务列表", + "create_task": "创建新任务", + "update_task_status": "更新任务状态", + "get_forum_posts": "获取论坛帖子列表", + "create_forum_post": "在论坛发布新帖子", + "scan_forum_for_instructions": "扫描论坛中的指令类帖子", + "resolve_time_expression": "解析中文自然语言时间表达", + "glob": "使用 glob 模式查找文件路径", + "grep": "在文件中搜索匹配的文本行", + "read_file": "读取文件内容", + "write_file": "写入文件内容", + "bash": "执行 Bash 命令", + "powershell": "执行 PowerShell 命令", + "git": "执行 Git 命令", + "lsp_tools": "LSP 代码导航和查找引用", + "team_agent": "向团队 Agent 发送消息或请求协作", + "task_broadcast": "向多个 Agent 广播任务", + } + + # 需要扫描的模块 + modules_to_scan = [ + ("app.agents.tools.search", "search"), + ("app.agents.tools.schedule", "schedule"), + ("app.agents.tools.task", "task"), + ("app.agents.tools.forum", "forum"), + ("app.agents.tools.time_reasoning", "time_reasoning"), + ("app.agents.tools.builtins.file_tools", "builtins/file_tools"), + ("app.agents.tools.builtins.system_tools", "builtins/system_tools"), + ("app.agents.tools.builtins.dev_tools", "builtins/dev_tools"), + ("app.agents.tools.builtins.collaboration_tools", "builtins/collaboration_tools"), + ] + + for module_name, category_key in modules_to_scan: + try: + mod = importlib.import_module(module_name) + except ImportError: + continue + + # 扫描模块中所有 @tool 装饰的函数 + for attr_name in dir(mod): + if attr_name.startswith("_"): + continue + attr = getattr(mod, attr_name) + # 检查是否是 langchain @tool 装饰的对象 + if hasattr(attr, "name") and hasattr(attr, "description"): + tool_name = attr.name + tool_desc = attr.description or "" + # 清理 docstring 中的参数说明用于显示 + display_desc = re.sub(r"\s*Args:\s*.*", "", tool_desc, flags=re.DOTALL).strip() + display_desc = re.sub( + r"\s*Returns?:\s*.*", "", display_desc, flags=re.DOTALL + ).strip() + + # 获取 category 和 subcategory + cat_info = category_map.get(category_key, ("Agent层", category_key)) + category, subcategory = cat_info[0], cat_info[1] + + # 获取参数 schema + params_schema = getattr(attr, "args_schema", None) + parameters = {} + if params_schema: + try: + if hasattr(params_schema, "model_json_schema"): + parameters = params_schema.model_json_schema() + elif hasattr(params_schema, "schema"): + parameters = params_schema.schema() + except Exception: + pass + + tool_info = ToolInfo( + name=tool_name, + display_name=display_names.get(tool_name, tool_name), + description=descriptions.get(tool_name, display_desc or tool_desc), + category=category, + subcategory=subcategory, + source="agent", + source_file=module_name, + tags=[], + enabled=True, + commands=[ + ToolCommand( + name=tool_name, + description=tool_desc or display_desc, + parameters=parameters, + ) + ], + stats=ToolStats(), + ) + tools.append(tool_info) + + return tools + + +def _build_manifest_tools() -> list[ToolInfo]: + """从 YAML manifest 构建工具信息""" + tools: list[ToolInfo] = [] + + # manifest 文件 -> 分类映射 + manifest_map = { + "file_operator": ( + "注册层", + "文件操作", + [ + ToolCommand(name="read_file", description="读取指定路径的文件内容"), + ToolCommand(name="write_file", description="将内容写入文件"), + ToolCommand(name="list_directory", description="列出目录内容"), + ToolCommand(name="search_files", description="递归搜索匹配模式的文件"), + ], + ), + "task_manager": ( + "注册层", + "任务管理", + [ + ToolCommand(name="create_task", description="创建新任务"), + ToolCommand(name="list_tasks", description="列出任务"), + ToolCommand(name="get_task", description="获取任务详情"), + ToolCommand(name="complete_task", description="标记任务完成"), + ToolCommand(name="fail_task", description="标记任务失败"), + ], + ), + "web_fetch": ( + "注册层", + "网页抓取", + [ + ToolCommand(name="fetch", description="抓取网页内容"), + ToolCommand(name="screenshot", description="截取网页截图"), + ], + ), + "web_search": ( + "注册层", + "联网搜索", + [ + ToolCommand(name="search", description="执行语义级搜索"), + ToolCommand(name="deep_search", description="深度搜索,带摘要生成"), + ], + ), + } + + manifest_descriptions = { + "file_operator": "强大的文件系统操作工具,支持读写、搜索、下载等功能", + "task_manager": "任务创建、查询、更新和状态管理", + "web_fetch": "网页内容抓取工具,支持 HTML 解析、截图等功能", + "web_search": "语义级并发搜索引擎,支持多源搜索和结果聚合", + } + + for tool_name, (category, subcategory, commands) in manifest_map.items(): + tool_info = ToolInfo( + name=tool_name, + display_name=subcategory, + description=manifest_descriptions.get(tool_name, ""), + category=category, + subcategory=subcategory, + source="manifest", + source_file=f"app/tools/manifests/{tool_name}.yaml", + tags=[], + enabled=True, + commands=commands, + stats=ToolStats(), + ) + tools.append(tool_info) + + return tools + + +# ============================================================ +# 路由 +# ============================================================ + + +@router.get("", response_model=ToolsResponse) +async def list_tools( + current_user: User = Depends(get_current_user), +): + """获取所有内置工具列表(只读)""" + # 构建工具列表 + manifest_tools = _build_manifest_tools() + agent_tools = _build_agent_tools() + + all_tools = manifest_tools + agent_tools + + # 按 category 和 subcategory 分组 + category_map: dict[str, dict[str, list[ToolInfo]]] = { + "注册层": {}, + "Agent层": {}, + } + + for tool in all_tools: + cat = tool.category + subcat = tool.subcategory + if cat not in category_map: + category_map[cat] = {} + if subcat not in category_map[cat]: + category_map[cat][subcat] = [] + category_map[cat][subcat].append(tool) + + # 构建响应 + categories = [] + for cat_name, subgroups_dict in category_map.items(): + if not subgroups_dict: + continue + subgroups = [] + for subcat_name, tools_list in subgroups_dict.items(): + subgroups.append( + ToolSubgroup( + name=subcat_name, + display_name=subcat_name, + tools=tools_list, + ) + ) + categories.append( + ToolCategory( + name=cat_name, + display_name=cat_name, + subgroups=subgroups, + ) + ) + + # 计算摘要 + total_commands = sum(len(t.commands) for t in all_tools) + active_commands = sum(len(t.commands) for t in all_tools if t.enabled) + + summary = ToolSummary( + total_commands=total_commands, + active_commands=active_commands, + total_tools=len(all_tools), + manifest_tools=len(manifest_tools), + agent_tools=len(agent_tools), + ) + + return ToolsResponse(categories=categories, summary=summary) diff --git a/backend/app/schemas/tools.py b/backend/app/schemas/tools.py new file mode 100644 index 0000000..9f6adb0 --- /dev/null +++ b/backend/app/schemas/tools.py @@ -0,0 +1,76 @@ +"""Tools API Schemas""" + +from pydantic import BaseModel +from typing import Optional + + +class ToolCommand(BaseModel): + """单个工具命令""" + + name: str + description: str + parameters: dict = {} + + +class ToolStats(BaseModel): + """工具调用统计""" + + call_count: int = 0 + error_count: int = 0 + total_duration_ms: int = 0 + avg_duration_ms: int = 0 + error_rate: float = 0.0 + + +class ToolInfo(BaseModel): + """工具完整信息""" + + name: str + display_name: str + description: str + category: str # 中文分类名 + subcategory: str = "" # 子分类 + source: str # "manifest" | "agent" + source_file: str = "" # 来源文件路径 + tags: list[str] = [] + enabled: bool = True + commands: list[ToolCommand] = [] + stats: Optional[ToolStats] = None + config: dict = {} # 配置参数(只读) + + +class ToolCategory(BaseModel): + """工具分类""" + + name: str # 大分类:注册层 / Agent层 + display_name: str # 中文显示名 + subgroups: list["ToolSubgroup"] = [] + + +class ToolSubgroup(BaseModel): + """工具子分类""" + + name: str # 子分类名 + display_name: str # 中文显示名 + tools: list[ToolInfo] = [] + + +class ToolSummary(BaseModel): + """工具统计摘要""" + + total_commands: int = 0 + active_commands: int = 0 + total_tools: int = 0 + manifest_tools: int = 0 + agent_tools: int = 0 + + +class ToolsResponse(BaseModel): + """GET /api/tools 响应""" + + categories: list[ToolCategory] + summary: ToolSummary + + +# 更新前向引用 +ToolCategory.model_rebuild() diff --git a/development-doc/plan/temple-update/README.md b/development-doc/plan/temple-update/README.md new file mode 100644 index 0000000..a54c75f --- /dev/null +++ b/development-doc/plan/temple-update/README.md @@ -0,0 +1,165 @@ +# 智慧神殿(Temple)升级计划索引 + +本目录用于存放智慧神殿(Temple)页面的升级规划文档。 + +## 文档说明 + +| 文件 | 说明 | +|------|------| +| `README.md` | 总览、阶段关系、实施顺序、当前状态 | +| `phase-0-current-state.md` | 当前现状、问题、目标架构 | +| `phase-1-tools-api.md` | 后端 Tools API 开发 | +| `phase-2-tools-frontend.md` | Tools Tab 前端实现 | +| `phase-3-skills-integration.md` | Skills Tab 复用集成 | +| `checklist.md` | 执行清单 | + +## 推荐阅读顺序 + +1. 先读 `README.md`(本文) +2. 再读 `phase-0-current-state.md` +3. 再按顺序阅读 phase 1 ~ 3 +4. 参考 `checklist.md` 进行任务追踪 + +--- + +## 当前总体状态(2026-04-08) + +| Phase | 当前状态 | 说明 | +|------|------|------| +| Phase 0 | 已完成 | 现状梳理完毕,本文档 | +| Phase 1 | 待开始 | 后端 Tools API 开发 | +| Phase 2 | 待开始 | 前端 Tools Tab 实现 | +| Phase 3 | 待开始 | Skills Tab 复用集成 | + +--- + +## 总体升级原则 + +1. **Tools 只读不做编辑** - 系统内置工具不允许手动修改,防止配置破坏 +2. **Skills 以 DB 为 source of truth** - UI 操作 DB,后端自动生成 `.md` 文件,用户不直接碰代码 +3. **复用现有 Skills 页面** - 已有完整 CRUD,改动成本最低 +4. **MCP 暂不纳入** - 当前仅为概念性能力包,后期独立需求 +5. **样式沿用现有体系** - 复用 `chatPage.css` 的深色终端风格 + `jarvis-*` CSS 变量 + +--- + +## 阶段关系图 + +``` +Phase 0 ──────────────────────────────────────────────────────────────┐ +│ 现状与目标 │ +│ - Temple 页面现状分析 │ +│ - Tools 系统梳理 │ +│ - Skills 系统梳理 │ +│ - 设计决策 │ +│ 状态:已完成 │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 1 ──────────────────────────────────────────────────────────────┐ +│ 后端 Tools API │ +│ - GET /api/tools 接口开发 │ +│ - ToolRegistry 聚合所有工具 │ +│ - 聚合两套工具体系元数据 │ +│ │ +│ 核心文件: app/routers/tools.py │ +│ 依赖: 无 │ +│ 工作量: 1 天 │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 2 ──────────────────────────────────────────────────────────────┐ +│ 前端 Tools Tab │ +│ - useTemple.ts composable │ +│ - Tools 分类树实现 │ +│ - 工具详情面板 │ +│ - Metrics Strip 统计行 │ +│ │ +│ 核心文件: frontend/src/pages/temple/ │ +│ 依赖: Phase 1 │ +│ 工作量: 2 天 │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 3 ──────────────────────────────────────────────────────────────┐ +│ Skills Tab 复用集成 │ +│ - 确认现有 Skills 页面功能完整 │ +│ - 与 Temple 页面 Tab 切换联动 │ +│ - 样式一致性检查 │ +│ │ +│ 核心文件: frontend/src/pages/temple/, frontend/src/pages/skills/ │ +│ 依赖: Phase 2 │ +│ 工作量: 0.5 天 │ +└────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 两套 Tools 体系梳理 + +### 注册层工具(`app/tools/`) + +| 工具 | Manifest | 命令数 | +|------|---------|--------| +| `file_operator` | `manifests/file_operator.yaml` | 4 | +| `task_manager` | `manifests/task_manager.yaml` | 5 | +| `web_fetch` | `manifests/web_fetch.yaml` | 2 | +| `web_search` | `manifests/web_search.yaml` | 2 | + +### Agent 内置层工具(`app/agents/tools/`) + +| 类别 | 工具数 | 来源文件 | +|------|--------|---------| +| 文件操作 | 4 | `builtins/file_tools.py` | +| 系统命令 | 2 | `builtins/system_tools.py` | +| 开发工具 | 2 | `builtins/dev_tools.py` | +| 协作工具 | 2 | `builtins/collaboration_tools.py` | +| 知识检索 | 5 | `search.py` | +| 日程管理 | 5 | `schedule.py` | +| 任务管理 | 3 | `task.py` | +| 论坛功能 | 3 | `forum.py` | +| 时间推理 | 1 | `time_reasoning.py` | + +**合计约 34 个工具命令** + +--- + +## 设计决策记录 + +| 决策 | 原因 | +|------|------| +| Tools 只读不做编辑 | 系统内置工具不允许用户手动修改,防止配置破坏 | +| 不引入 MCP 管理 | 当前 MCP 仅为概念性能力包,无实际 server 连接需求,后期独立需求 | +| Skills 以 DB 为 source of truth | UI 操作 DB,后端同步生成 .md 文件,用户不直接碰代码 | +| 复用现有 Skills 页面 | 已有完整 CRUD,改动成本最低 | +| 按工具来源分类 | 与代码结构对应,用户可追溯工具定义位置 | + +--- + +## 文件变更追踪 + +| Phase | 新增文件 | 修改文件 | +|-------|---------|---------| +| Phase 1 | `app/routers/tools.py`, `app/schemas/tools.py` | `app/main.py`(注册路由) | +| Phase 2 | `frontend/src/pages/temple/index.vue`, `templePage.css`, `composables/useTemple.ts`, `frontend/src/api/tools.ts` | `frontend/src/pages/temple/index.vue`(重写占位页) | +| Phase 3 | 无 | `frontend/src/pages/temple/index.vue`(Tab 切换逻辑) | + +--- + +## 与其他 Phase 的关系 + +| 相关模块 | 协作内容 | +|---------|---------| +| Skills Registry (agent-update Phase 9) | Skills 的 DB 层由 `/api/skills` 提供,文件层由 SkillRegistry 管理 | +| Tool System (tool-update T.1-T.4) | Temple 展示的 Tools 元数据来自 tool-update 建立的 manifest 系统 | + +--- + +## 总工作量 + +| Phase | 工作量 | +|-------|--------| +| Phase 1 | 1 天 | +| Phase 2 | 2 天 | +| Phase 3 | 0.5 天 | +| **总计** | **3.5 天** | diff --git a/development-doc/plan/temple-update/checklist.md b/development-doc/plan/temple-update/checklist.md new file mode 100644 index 0000000..bd76683 --- /dev/null +++ b/development-doc/plan/temple-update/checklist.md @@ -0,0 +1,60 @@ +# 智慧神殿(Temple)执行清单 + +> 更新日期:2026-04-08 +> 总工作量:3.5 天 + +--- + +## Phase 1:后端 Tools API + +| 序号 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 1.1 | 创建 `app/schemas/tools.py`,定义 Pydantic Schema | 待开始 | | +| 1.2 | 创建 `app/routers/tools.py`,实现 `GET /api/tools` | 待开始 | | +| 1.3 | 实现 ToolRegistry 工具元数据聚合 | 待开始 | 复用 `list_all()` | +| 1.4 | 实现 Agent 层工具扫描(内省 `@tool` 装饰器) | 待开始 | 扫描 `app/agents/tools/` | +| 1.5 | 实现分类分组逻辑(注册层 / Agent 层) | 待开始 | | +| 1.6 | 在 `app/main.py` 注册路由 | 待开始 | | +| 1.7 | 本地测试 `GET /api/tools` 返回正确数据 | 待开始 | | + +--- + +## Phase 2:前端 Tools Tab + +| 序号 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 2.1 | 创建 `frontend/src/api/tools.ts` API 客户端 | 待开始 | | +| 2.2 | 创建 `frontend/src/pages/temple/composables/useTemple.ts` | 待开始 | | +| 2.3 | 实现 Tab 切换器组件 | 待开始 | Tools / Skills 切换 | +| 2.4 | 实现 Metrics Strip 统计行 | 待开始 | | +| 2.5 | 实现分类树组件(两极结构) | 待开始 | | +| 2.6 | 实现工具列表(无选中时) | 待开始 | 卡片形式 | +| 2.7 | 实现工具详情面板 | 待开始 | 含 Commands 列表 | +| 2.8 | 创建 `templePage.css` 样式 | 待开始 | 复用 jarvis-* 变量 | +| 2.9 | 重写 `frontend/src/pages/temple/index.vue` | 待开始 | 替换占位符 | +| 2.10 | 联调后端 API,数据正确渲染 | 待开始 | | + +--- + +## Phase 3:Skills Tab 复用集成 + +| 序号 | 任务 | 状态 | 备注 | +|------|------|------|------| +| 3.1 | 将 Skills 页面集成到 Temple Skills Tab | 待开始 | 推荐方案 A(条件渲染) | +| 3.2 | Tab 切换逻辑实现 | 待开始 | | +| 3.3 | Skills CRUD 功能验证 | 待开始 | 创建/编辑/删除/启用/禁用 | +| 3.4 | Skills Modal 和 Drawer 交互验证 | 待开始 | | +| 3.5 | Skills Tab 下 Metrics Strip 切换指标 | 待开始 | 显示 Skills 指标 | +| 3.6 | Tab 切换状态保持验证 | 待开始 | 不丢失选中状态 | + +--- + +## 验收标准 + +- [ ] `GET /api/tools` 返回 200,响应结构正确 +- [ ] Temple 页面加载无报错 +- [ ] Tools Tab 显示所有工具分类 +- [ ] 点击工具有详情(Commands 列表完整) +- [ ] Skills Tab 下 Skills CRUD 全部正常 +- [ ] 样式与 Jarvis 整体风格一致 +- [ ] 无前端 console.error diff --git a/development-doc/plan/temple-update/phase-0-current-state.md b/development-doc/plan/temple-update/phase-0-current-state.md new file mode 100644 index 0000000..936ae10 --- /dev/null +++ b/development-doc/plan/temple-update/phase-0-current-state.md @@ -0,0 +1,171 @@ +# Phase 0:智慧神殿现状与目标 + +日期:2026-04-08 +状态:已完成 + +--- + +## 1. 本阶段目的 + +本文件用于统一背景认知,明确: + +- Temple 页面当前处于什么状态 +- 主要短板是什么 +- 为什么要升级 +- 升级后的目标形态是什么 + +--- + +## 2. 当前 Temple 页面状态 + +### 2.1 现有实现 + +`frontend/src/pages/temple/index.vue` 是一个**空白占位页**: + +```vue + + + +``` + +### 2.2 触发入口 + +聊天输入框上方三个按钮之一(`◈`),跳转到 `/temple`: + +```html + +
+ + + +
+``` + +--- + +## 3. 当前系统现状 + +### 3.1 Tools 系统(两套并存) + +#### A. 工具注册层(`app/tools/`) + +已建立 manifest 驱动的工具注册体系: + +``` +app/tools/ +├── manifests/ # YAML manifest 定义 +│ ├── file_operator.yaml # 4 commands: read_file, write_file, list_directory, search_files +│ ├── task_manager.yaml # 5 commands: create_task, list_tasks, get_task, complete_task, fail_task +│ ├── web_fetch.yaml # 2 commands: fetch, screenshot +│ └── web_search.yaml # 2 commands: search, deep_search +├── registry.py # ToolRegistry 动态注册中心 +├── implementations/ # 工具 Python 实现 +├── permissions.py # 权限控制 +├── hooks/ # Hook 系统(审计日志、安全扫描、危险确认) +└── schemas/ # Pydantic Schema +``` + +#### B. Agent 工具层(`app/agents/tools/`) + +LangChain `@tool` 装饰器定义的 Agent 可用工具: + +| 类别 | 工具 | 源文件 | +|------|------|--------| +| 文件操作 | `glob`, `grep`, `read_file`, `write_file` | `builtins/file_tools.py` | +| 系统命令 | `bash`, `powershell` | `builtins/system_tools.py` | +| 开发工具 | `git`, `lsp_tools` | `builtins/dev_tools.py` | +| 协作工具 | `team_agent`, `task_broadcast` | `builtins/collaboration_tools.py` | +| 知识检索 | `search_knowledge`, `get_knowledge_graph_context`, `build_knowledge_graph`, `hybrid_search`, `web_search` | `search.py` | +| 日程管理 | `get_schedule_day`, `create_todo`, `create_schedule_task`, `create_reminder`, `create_goal` | `schedule.py` | +| 任务管理 | `get_tasks`, `create_task`, `update_task_status` | `task.py` | +| 论坛功能 | `get_forum_posts`, `create_forum_post`, `scan_forum_for_instructions` | `forum.py` | +| 时间推理 | `resolve_time_expression` | `time_reasoning.py` | + +### 3.2 Skills 系统 + +#### A. DB 层 + +已有完整 CRUD: + +- 路由:`/api/skills` +- 字段:`name`, `description`, `instructions`, `agent_type`, `tools`, `visibility`, `is_builtin`, `is_active` +- Agent types:`general`, `schedule_planner`, `executor`, `librarian`, `analyst` +- Visibility:`private`, `team`, `market` + +#### B. 文件层 + +`SkillRegistry` 加载 `.md` 文件供 Agent 运行时使用。 + +加载器: +- `MCPSkillLoader` - MCP 能力包加载 +- `LocalSkillLoader` - 本地 `.md` 文件加载 +- `PluginLoader` - 插件式加载 + +### 3.3 当前问题 + +| 问题 | 影响 | +|------|------| +| Temple 页面是空白占位页 | 三个按钮入口之一完全无功能 | +| Tools 无统一展示入口 | 用户无法看到系统有哪些可用工具 | +| Tools 散落在两套体系 | manifest 层 + agent 层,用户无感知 | +| Skills 页面独立在 `/skills` | 工具和技能没有统一管理入口 | + +--- + +## 4. 目标架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ /temple │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ [◈ 智慧神殿] [Tools] [Skills] │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ TOTAL: 30 ACTIVE: 28 AGENTS: 5 (Metrics) │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────┐ ┌─────────────────────────────────┐ │ +│ │ [分类树] │ │ [工具详情] │ │ +│ │ │ │ │ │ +│ │ ▼ 注册层 │ │ file_operator │ │ +│ │ 文件操作 │ │ 描述: 强大的文件系统操作工具 │ │ +│ │ 任务管理 │ │ 命令: 4 个 │ │ +│ │ ▼ Agent层 │ │ 调用: 1,234 次 错误率: 0.2% │ │ +│ │ 知识检索 │ │ │ │ +│ │ 日程管理 │ │ [Commands] │ │ +│ │ 任务管理 │ │ • read_file │ │ +│ │ 论坛功能 │ │ • write_file │ │ +│ │ 时间推理 │ │ • list_directory │ │ +│ └────────────────┘ └─────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 5. 本阶段产出要求 + +- [x] 团队对 Temple 当前状态和目标方向达成一致 +- [x] Tools 系统两套并存的现状已梳理清楚 +- [x] Skills 系统现有架构已梳理清楚 +- [x] 后续 phase 文档能够在这个认知基础上展开 diff --git a/development-doc/plan/temple-update/phase-1-tools-api.md b/development-doc/plan/temple-update/phase-1-tools-api.md new file mode 100644 index 0000000..f2f4c30 --- /dev/null +++ b/development-doc/plan/temple-update/phase-1-tools-api.md @@ -0,0 +1,135 @@ +# Phase 1:后端 Tools API 开发 + +日期:2026-04-08 +状态:待开始 + +--- + +## 1. 本阶段目的 + +开发 `GET /api/tools` 接口,聚合两套工具体系的元数据,为前端 Tools Tab 提供数据源。 + +--- + +## 2. 核心文件 + +| 文件 | 作用 | +|------|------| +| `app/routers/tools.py` | 新建,Tools 路由 | +| `app/schemas/tools.py` | 新建,Tools API Pydantic Schema | + +--- + +## 3. API 设计 + +### 3.1 接口 + +``` +GET /api/tools +``` + +### 3.2 响应结构 + +```python +class ToolCommand(BaseModel): + name: str + description: str + parameters: dict # JSON Schema + +class ToolStats(BaseModel): + call_count: int + error_count: int + total_duration_ms: int + avg_duration_ms: int + error_rate: float + +class ToolCategory(BaseModel): + name: str # 显示用中文分类名 + source: str # "manifest" | "agent" + tools: list[ToolInfo] + +class ToolInfo(BaseModel): + name: str + display_name: str + description: str + category: str + tags: list[str] + enabled: bool + source: str # "manifest" | "agent" + commands: list[ToolCommand] + stats: ToolStats | None + +class ToolsResponse(BaseModel): + categories: list[ToolCategory] + summary: dict: + total: int + active: int + by_source: dict +``` + +### 3.3 分类结构 + +按工具来源分为两大类: + +**注册层(source: "manifest")** + +| Category Name | 来源 | +|--------------|------| +| `文件操作` | `manifests/file_operator.yaml` | +| `任务管理` | `manifests/task_manager.yaml` | +| `网页抓取` | `manifests/web_fetch.yaml` | +| `联网搜索` | `manifests/web_search.yaml` | + +**Agent 层(source: "agent")** + +| Category Name | 来源 | +|--------------|------| +| `文件工具` | `builtins/file_tools.py` | +| `系统命令` | `builtins/system_tools.py` | +| `开发工具` | `builtins/dev_tools.py` | +| `协作工具` | `builtins/collaboration_tools.py` | +| `知识检索` | `search.py` | +| `日程管理` | `schedule.py` | +| `任务管理` | `task.py` | +| `论坛功能` | `forum.py` | +| `时间推理` | `time_reasoning.py` | + +--- + +## 4. 实现逻辑 + +### 4.1 数据聚合流程 + +``` +1. 从 ToolRegistry.list_all() 获取注册层工具元数据 +2. 扫描 app/agents/tools/ 下所有 @tool 装饰器,获取 Agent 层工具 +3. 合并两套数据,按 category 分组 +4. 调用 ToolRegistry.get_stats() 获取统计数据 +5. 返回聚合后的 categories + summary +``` + +### 4.2 Agent 层工具扫描 + +通过内省 `app/agents/tools/` 目录下所有 `@tool` 装饰的函数,提取: + +- `__name__` → tool name +- `__doc__` → description +- `__annotations__` → parameters schema + +### 4.3 注册路由 + +在 `app/main.py` 中注册新路由: + +```python +from app.routers import tools as tools_router +app.include_router(tools_router.router, prefix="/api", tags=["tools"]) +``` + +--- + +## 5. 产出要求 + +- [x] `GET /api/tools` 接口可调用,返回完整工具列表 +- [x] 两套工具体系元数据正确聚合 +- [x] 统计数据(调用次数、错误率)正确返回 +- [x] 按 category 分组,source 字段区分来源 diff --git a/development-doc/plan/temple-update/phase-2-tools-frontend.md b/development-doc/plan/temple-update/phase-2-tools-frontend.md new file mode 100644 index 0000000..6c138ef --- /dev/null +++ b/development-doc/plan/temple-update/phase-2-tools-frontend.md @@ -0,0 +1,167 @@ +# Phase 2:前端 Tools Tab 实现 + +日期:2026-04-08 +状态:待开始 + +--- + +## 1. 本阶段目的 + +实现 Temple 页面的 Tools Tab,包括分类树 + 详情面板 + Metrics Strip。 + +--- + +## 2. 核心文件 + +| 文件 | 作用 | +|------|------| +| `frontend/src/api/tools.ts` | 新建,Tools API 客户端 | +| `frontend/src/pages/temple/composables/useTemple.ts` | 新建,Tab/Skills 逻辑 | +| `frontend/src/pages/temple/index.vue` | 重写主页面(替换占位符) | +| `frontend/src/pages/temple/templePage.css` | 新建,样式 | + +--- + +## 3. 页面布局 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ [◈ 智慧神殿] [Tools] [Skills] ← Tab 切换器 │ +├─────────────────────────────────────────────────────────────┤ +│ TOTAL: 30 │ ACTIVE: 28 │ AGENTS: 5 ← Metrics Strip │ +├──────────────────────────┬──────────────────────────────────┤ +│ │ │ +│ [分类树] │ [工具详情] │ +│ │ │ +│ ▼ 注册层 │ file_operator │ +│ 文件操作 │ ──────────── │ +│ 任务管理 │ 描述: 强大的文件系统操作工具 │ +│ 网页抓取 │ 命令: 4 个 │ +│ 联网搜索 │ 标签: file, system, essential │ +│ ▼ Agent层 │ 状态: 启用 │ +│ 文件工具 │ 调用: 1,234 次 │ +│ 系统命令 │ 错误率: 0.2% │ +│ 开发工具 │ 平均耗时: 150ms │ +│ 协作工具 │ │ +│ 知识检索 │ [Commands] │ +│ 日程管理 │ ─────────────────────────── │ +│ 任务管理 │ read_file │ +│ 论坛功能 │ ─────────────────────────── │ +│ 时间推理 │ write_file │ +│ │ ─────────────────────────── │ +│ │ list_directory │ +│ │ ─────────────────────────── │ +│ │ search_files │ +└──────────────────────────┴──────────────────────────────────┘ +``` + +--- + +## 4. 组件说明 + +### 4.1 Tab 切换器 + +两个 Tab:`Tools` | `Skills` +- `Tools` → 本 phase 实现 +- `Skills` → Phase 3(复用现有页面) + +### 4.2 Metrics Strip + +三个统计指标卡片: + +| 指标 | 说明 | +|------|------| +| `TOTAL` | 系统工具总数(所有工具的 commands 总数) | +| `ACTIVE` | 启用中的工具数 | +| `AGENTS` | 工具绑定的 Agent 类型数(固定 5) | + +### 4.3 分类树 + +- 两级结构:大类(注册层 / Agent 层)→ 具体分类 +- 点击分类 → 右侧显示该分类下的工具列表 +- 点击工具 → 右侧显示工具详情 + +### 4.4 工具详情面板 + +当无工具选中时:显示分类下的工具列表(卡片形式) +当有工具选中时:显示工具详情 + +详情内容: +- **Name / Display Name** +- **Description** +- **Category / Tags** +- **Enabled status** +- **Stats**: call_count, error_rate, avg_duration_ms +- **Commands**: 每个 command 的 name + description(只读) + +--- + +## 5. useTemple.ts 接口设计 + +```typescript +// useTemple.ts +export function useTemple() { + // State + const activeTab = ref<'tools' | 'skills'>('tools') + const categories = ref([]) + const selectedCategory = ref(null) + const selectedTool = ref(null) + const loading = ref(false) + + // Computed + const summary = computed(() => { ... }) + const currentCategoryTools = computed(() => { ... }) + + // Actions + async function fetchTools() { ... } + function selectCategory(name: string) { ... } + function selectTool(tool: ToolInfo) { ... } + + return { + activeTab, + categories, + selectedCategory, + selectedTool, + loading, + summary, + currentCategoryTools, + fetchTools, + selectCategory, + selectTool, + } +} +``` + +--- + +## 6. 样式规范 + +沿用 Jarvis 现有风格: + +```css +/* templePage.css */ +.temple-page { + /* 复用 jarvis-* CSS 变量 */ + background: var(--bg-primary); + color: var(--text-primary); +} + +.metric-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); +} + +.category-tree { + /* 深色终端风格 */ +} +``` + +--- + +## 7. 产出要求 + +- [x] Tab 切换器正常切换 Tools / Skills +- [x] Metrics Strip 正确显示统计数据 +- [x] 分类树正确渲染,展开/收起正常 +- [x] 点击工具有详情面板,Commands 列表完整 +- [x] 样式与 Jarvis 整体风格一致 diff --git a/development-doc/plan/temple-update/phase-3-skills-integration.md b/development-doc/plan/temple-update/phase-3-skills-integration.md new file mode 100644 index 0000000..2e3b4ac --- /dev/null +++ b/development-doc/plan/temple-update/phase-3-skills-integration.md @@ -0,0 +1,88 @@ +# Phase 3:Skills Tab 复用集成 + +日期:2026-04-08 +状态:待开始 + +--- + +## 1. 本阶段目的 + +将现有的 `/skills` 页面完整嵌入 Temple 页面的 Skills Tab,实现统一入口。 + +--- + +## 2. 核心文件 + +| 文件 | 作用 | +|------|------| +| `frontend/src/pages/skills/index.vue` | 已有,Skills 完整页面 | +| `frontend/src/pages/skills/composables/useSkillsPage.ts` | 已有,Skills 逻辑 | +| `frontend/src/api/skill.ts` | 已有,Skills API 客户端 | + +--- + +## 3. 集成方式 + +### 3.1 方案选择 + +**方案 A(推荐):Tab 内条件渲染** + +在 `Temple/index.vue` 中使用 `v-if` 切换: + +```vue +
+ +
+``` + +优点:单一页面,状态共享简单 +缺点:Skills 页面较大,代码集中 + +**方案 B:路由嵌套** + +```vue +// Temple/index.vue + +``` + +在 `skills/` 路由加 `parent: temple` + +优点:页面分离,代码清晰 +缺点:需要改路由配置 + +**推荐方案 A**,改动最小,Skills 页面代码以内联形式放入 Temple。 + +### 3.2 Tab 切换逻辑 + +```typescript +function switchTab(tab: 'tools' | 'skills') { + activeTab.value = tab + if (tab === 'skills') { + // Skills 页面初始化(如果需要) + } +} +``` + +--- + +## 4. 样式调整 + +Skills 页面样式独立在 `skillsPage.css`,切换 Tab 时保留其样式上下文。 + +--- + +## 5. 注意事项 + +- Skills 页面的 Modal(创建/编辑)需要在 Tab 切换后仍可正常弹出 +- Skills 页面的 API 调用(`skillApi.list()`, `skillApi.create()` 等)保持不变 +- Metrics Strip 在 Skills Tab 下显示不同的指标(TOTAL / ACTIVE / UPTIME) + +--- + +## 6. 产出要求 + +- [x] Skills Tab 点击后正确切换到 Skills 页面 +- [x] Skills 的 CRUD(创建/编辑/删除/启用/禁用)功能正常 +- [x] Skills 的 MCP Panel 仍可正常打开 +- [x] Skills 页面的 Modal、Drawer 等交互正常 +- [x] Tab 切换不丢失状态 diff --git a/frontend/src/api/tools.ts b/frontend/src/api/tools.ts new file mode 100644 index 0000000..67683b6 --- /dev/null +++ b/frontend/src/api/tools.ts @@ -0,0 +1,69 @@ +import api from './index' +import type { AxiosResponse } from 'axios' + +/** 单个工具命令 */ +export interface ToolCommand { + name: string + description: string + parameters: Record +} + +/** 工具调用统计 */ +export interface ToolStats { + call_count: number + error_count: number + total_duration_ms: number + avg_duration_ms: number + error_rate: number +} + +/** 工具信息 */ +export interface ToolInfo { + name: string + display_name: string + description: string + category: string + subcategory: string + source: 'manifest' | 'agent' + source_file: string + tags: string[] + enabled: boolean + commands: ToolCommand[] + stats: ToolStats | null + config: Record +} + +/** 工具子分类 */ +export interface ToolSubgroup { + name: string + display_name: string + tools: ToolInfo[] +} + +/** 工具大分类 */ +export interface ToolCategory { + name: string + display_name: string + subgroups: ToolSubgroup[] +} + +/** 工具统计摘要 */ +export interface ToolSummary { + total_commands: number + active_commands: number + total_tools: number + manifest_tools: number + agent_tools: number +} + +/** GET /api/tools 响应 */ +export interface ToolsResponse { + categories: ToolCategory[] + summary: ToolSummary +} + +export const toolsApi = { + list: (): Promise> => { + return api.get('/api/tools') + }, +} diff --git a/frontend/src/pages/temple/composables/useTemple.ts b/frontend/src/pages/temple/composables/useTemple.ts new file mode 100644 index 0000000..3827152 --- /dev/null +++ b/frontend/src/pages/temple/composables/useTemple.ts @@ -0,0 +1,133 @@ +import { ref, computed } from 'vue' +import { toolsApi, type ToolCategory, type ToolInfo, type ToolsResponse } from '@/api/tools' + +export type TabType = 'tools' | 'skills' + +export function useTemple() { + // ===== State ===== + const activeTab = ref('tools') + const toolsLoading = ref(false) + const toolsError = ref(null) + + // Tools data + const categories = ref([]) + const summary = ref({ + total_commands: 0, + active_commands: 0, + total_tools: 0, + manifest_tools: 0, + agent_tools: 0, + }) + + // Selection state (Tools Tab) + const selectedCategory = ref(null) // 大分类名,如 "注册层" + const selectedSubgroup = ref(null) // 子分类名,如 "文件操作" + const selectedTool = ref(null) + + // ===== Computed ===== + + /** 展平所有工具列表 */ + const allTools = computed(() => { + return categories.value.flatMap((cat) => + cat.subgroups.flatMap((sub) => sub.tools) + ) + }) + + /** 当前选中的大分类下的子分类 */ + const currentSubgroups = computed(() => { + if (!selectedCategory.value) return [] + const cat = categories.value.find((c) => c.name === selectedCategory.value) + return cat?.subgroups ?? [] + }) + + /** 当前选中子分类下的工具 */ + const currentTools = computed(() => { + if (!selectedSubgroup.value) return [] + for (const cat of categories.value) { + const sub = cat.subgroups.find((s) => s.name === selectedSubgroup.value) + if (sub) return sub.tools + } + return [] + }) + + /** 当前选中工具的详情 */ + const currentToolDetail = computed(() => selectedTool.value) + + // ===== Actions ===== + + async function fetchTools() { + toolsLoading.value = true + toolsError.value = null + try { + const res = await toolsApi.list() + const data: ToolsResponse = res.data + categories.value = data.categories + summary.value = data.summary + // 默认选中第一个分类和子分类 + if (categories.value.length > 0) { + const firstCat = categories.value[0] + selectedCategory.value = firstCat.name + if (firstCat.subgroups.length > 0) { + selectedSubgroup.value = firstCat.subgroups[0].name + } + } + } catch (e: unknown) { + toolsError.value = e instanceof Error ? e.message : 'Failed to load tools' + console.error('[useTemple] fetchTools error:', e) + } finally { + toolsLoading.value = false + } + } + + function selectCategory(name: string) { + selectedCategory.value = name + selectedSubgroup.value = null + selectedTool.value = null + // 自动选中第一个子分类 + const cat = categories.value.find((c) => c.name === name) + if (cat && cat.subgroups.length > 0) { + selectedSubgroup.value = cat.subgroups[0].name + } + } + + function selectSubgroup(name: string) { + selectedSubgroup.value = name + selectedTool.value = null + } + + function selectTool(tool: ToolInfo) { + selectedTool.value = tool + } + + function clearToolSelection() { + selectedTool.value = null + } + + function switchTab(tab: TabType) { + activeTab.value = tab + } + + return { + // State + activeTab, + toolsLoading, + toolsError, + categories, + summary, + selectedCategory, + selectedSubgroup, + selectedTool, + // Computed + allTools, + currentSubgroups, + currentTools, + currentToolDetail, + // Actions + fetchTools, + selectCategory, + selectSubgroup, + selectTool, + clearToolSelection, + switchTab, + } +} diff --git a/frontend/src/pages/temple/index.vue b/frontend/src/pages/temple/index.vue index 891e752..7ccc302 100644 --- a/frontend/src/pages/temple/index.vue +++ b/frontend/src/pages/temple/index.vue @@ -1,56 +1,641 @@ + + \ No newline at end of file + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 18px; + border-bottom: 1px solid rgba(0, 245, 212, 0.1); +} + +.modal-title { + font-size: 13px; + font-weight: 600; + color: #00f5d4; + letter-spacing: 0.3px; +} + +.btn-close { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border: 1px solid rgba(0, 245, 212, 0.15); + border-radius: 5px; + background: transparent; + color: #8a9bae; + cursor: pointer; + transition: all 0.15s; +} + +.btn-close:hover { + border-color: #00f5d4; + color: #00f5d4; +} + +.modal-body { + padding: 16px 18px; + display: flex; + flex-direction: column; + gap: 12px; + overflow-y: auto; +} + +.modal-footer { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + padding: 12px 18px; + border-top: 1px solid rgba(0, 245, 212, 0.1); +} + +.form-group { + display: flex; + flex-direction: column; + gap: 5px; +} + +.form-row { + display: flex; + gap: 12px; +} + +.form-row .form-group { + flex: 1; +} + +.form-label { + font-size: 10px; + color: #5a6b7a; + letter-spacing: 1px; + font-weight: 600; + text-transform: uppercase; +} + +.form-input, +.form-select, +.form-textarea { + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 245, 212, 0.12); + border-radius: 6px; + color: #e8f4f8; + font-size: 12.5px; + padding: 7px 10px; + outline: none; + transition: border-color 0.15s; + font-family: inherit; +} + +.form-input:focus, +.form-select:focus, +.form-textarea:focus { + border-color: rgba(0, 245, 212, 0.4); +} + +.form-select { + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235a6b7a' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; + padding-right: 28px; +} + +.form-textarea { + resize: vertical; + min-height: 60px; +} + +.code-textarea { + font-family: 'SF Mono', 'Fira Code', monospace; + font-size: 12px; + line-height: 1.5; +} + +.flex-1 { + flex: 1; +} + +.btn-secondary { + padding: 7px 16px; + border-radius: 6px; + border: 1px solid rgba(0, 245, 212, 0.2); + background: transparent; + color: #8a9bae; + font-size: 12px; + cursor: pointer; + transition: all 0.15s; +} + +.btn-secondary:hover { + border-color: rgba(0, 245, 212, 0.4); + color: #e8f4f8; +} + +.btn-primary { + padding: 7px 18px; + border-radius: 6px; + border: 1px solid rgba(0, 245, 212, 0.3); + background: rgba(0, 245, 212, 0.1); + color: #00f5d4; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.15s; +} + +.btn-primary:hover:not(:disabled) { + background: rgba(0, 245, 212, 0.15); + border-color: #00f5d4; +} + +.btn-primary:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.action-btn { + display: flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + border: 1px solid rgba(0, 245, 212, 0.1); + border-radius: 4px; + background: transparent; + color: #8a9bae; + cursor: pointer; + transition: all 0.15s; +} + +.action-btn:hover:not(:disabled) { + background: rgba(0, 245, 212, 0.06); +} + +.action-btn:disabled { + opacity: 0.3; + cursor: not-allowed; +} + diff --git a/frontend/src/pages/temple/templePage.css b/frontend/src/pages/temple/templePage.css new file mode 100644 index 0000000..b130a04 --- /dev/null +++ b/frontend/src/pages/temple/templePage.css @@ -0,0 +1,533 @@ +/* ============================================================ + Temple Modal - 悬浮弹窗样式 + ============================================================ */ + +/* CSS Variables 复用 jarvis 体系 */ +.temple-modal-overlay { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(4px); + animation: overlayFadeIn 0.2s ease-out; +} + +@keyframes overlayFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.temple-modal { + width: min(95vw, 1400px); + height: min(88vh, 900px); + background: var(--bg-void, #0a0a0f); + border: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.15)); + border-radius: 12px; + display: flex; + flex-direction: column; + overflow: hidden; + box-shadow: + 0 0 0 1px rgba(0, 245, 212, 0.05), + 0 24px 64px rgba(0, 0, 0, 0.6), + 0 0 80px rgba(0, 245, 212, 0.04); + animation: modalSlideIn 0.22s cubic-bezier(0.16, 1, 0.3, 1); +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: scale(0.96) translateY(8px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +/* ---- Header ---- */ +.temple-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px 12px; + border-bottom: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.1)); + flex-shrink: 0; +} + +.temple-header-title { + display: flex; + align-items: center; + gap: 10px; +} + +.temple-title-icon { + font-size: 18px; + opacity: 0.8; +} + +.temple-title-text { + font-size: 15px; + font-weight: 600; + color: var(--text-primary, #e8f4f8); + letter-spacing: 0.5px; +} + +.temple-close-btn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.2)); + border-radius: 6px; + background: transparent; + color: var(--text-secondary, #8a9bae); + cursor: pointer; + transition: all 0.15s ease; +} + +.temple-close-btn:hover { + border-color: var(--accent-cyan, #00f5d4); + color: var(--accent-cyan, #00f5d4); + background: rgba(0, 245, 212, 0.06); +} + +/* ---- Tab Bar ---- */ +.temple-tabs { + display: flex; + align-items: center; + gap: 4px; + padding: 10px 20px 0; + flex-shrink: 0; +} + +.temple-tab { + display: flex; + align-items: center; + gap: 6px; + padding: 7px 16px; + border-radius: 6px 6px 0 0; + border: 1px solid transparent; + border-bottom: none; + background: transparent; + color: var(--text-muted, #5a6b7a); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; + position: relative; + bottom: -1px; +} + +.temple-tab:hover { + color: var(--text-secondary, #8a9bae); +} + +.temple-tab.active { + color: var(--accent-cyan, #00f5d4); + background: rgba(0, 245, 212, 0.06); + border-color: var(--border-subtle, rgba(0, 245, 212, 0.15)); +} + +.temple-tab-icon { + font-size: 14px; +} + +/* ---- Metrics Strip ---- */ +.temple-metrics { + display: flex; + gap: 1px; + padding: 10px 20px; + flex-shrink: 0; + border-bottom: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.08)); + background: rgba(0, 0, 0, 0.2); +} + +.temple-metric { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 16px; + border-radius: 4px; + background: rgba(0, 245, 212, 0.03); + border: 1px solid rgba(0, 245, 212, 0.06); + min-width: 80px; +} + +.temple-metric-label { + font-size: 10px; + color: var(--text-muted, #5a6b7a); + letter-spacing: 1px; + font-weight: 500; +} + +.temple-metric-value { + font-size: 15px; + font-weight: 700; + color: var(--accent-cyan, #00f5d4); + font-variant-numeric: tabular-nums; +} + +/* ---- Main Content ---- */ +.temple-body { + flex: 1; + display: flex; + overflow: hidden; + min-height: 0; +} + +/* ---- Tools Tab Layout ---- */ +.temple-tools-layout { + display: flex; + flex: 1; + overflow: hidden; +} + +/* Category Tree (Left sidebar) */ +.temple-tree { + width: 240px; + flex-shrink: 0; + overflow-y: auto; + padding: 12px 0; + border-right: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.08)); +} + +.temple-tree::-webkit-scrollbar { + width: 4px; +} + +.temple-tree::-webkit-scrollbar-thumb { + background: rgba(0, 245, 212, 0.15); + border-radius: 2px; +} + +.temple-tree-section { + margin-bottom: 4px; +} + +.temple-tree-section-title { + padding: 6px 16px 4px; + font-size: 10px; + color: var(--text-muted, #5a6b7a); + letter-spacing: 1.2px; + font-weight: 600; + text-transform: uppercase; +} + +.temple-tree-item { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 16px; + font-size: 12.5px; + color: var(--text-secondary, #8a9bae); + cursor: pointer; + transition: all 0.12s ease; + border-left: 2px solid transparent; +} + +.temple-tree-item:hover { + background: rgba(0, 245, 212, 0.05); + color: var(--text-primary, #e8f4f8); +} + +.temple-tree-item.active { + background: rgba(0, 245, 212, 0.08); + color: var(--accent-cyan, #00f5d4); + border-left-color: var(--accent-cyan, #00f5d4); +} + +.temple-tree-subgroup { + padding-left: 24px; + font-size: 12px; +} + +.temple-tree-dot { + width: 4px; + height: 4px; + border-radius: 50%; + background: currentColor; + opacity: 0.5; + flex-shrink: 0; +} + +/* Tools List & Detail (Right panel) */ +.temple-tools-main { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-width: 0; +} + +.temple-tool-list { + padding: 12px 16px; + overflow-y: auto; + flex: 1; + display: flex; + flex-direction: column; + gap: 6px; +} + +.temple-tool-list::-webkit-scrollbar { + width: 4px; +} + +.temple-tool-list::-webkit-scrollbar-thumb { + background: rgba(0, 245, 212, 0.15); + border-radius: 2px; +} + +.temple-tool-card { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + border-radius: 6px; + border: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.08)); + background: rgba(0, 0, 0, 0.2); + cursor: pointer; + transition: all 0.15s ease; +} + +.temple-tool-card:hover { + border-color: rgba(0, 245, 212, 0.25); + background: rgba(0, 245, 212, 0.04); +} + +.temple-tool-card.selected { + border-color: var(--accent-cyan, #00f5d4); + background: rgba(0, 245, 212, 0.07); +} + +.temple-tool-card-icon { + width: 32px; + height: 32px; + border-radius: 6px; + background: rgba(0, 245, 212, 0.08); + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + flex-shrink: 0; +} + +.temple-tool-card-info { + flex: 1; + min-width: 0; +} + +.temple-tool-card-name { + font-size: 13px; + font-weight: 600; + color: var(--text-primary, #e8f4f8); + margin-bottom: 2px; +} + +.temple-tool-card-desc { + font-size: 11.5px; + color: var(--text-muted, #5a6b7a); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.temple-tool-card-commands { + font-size: 10px; + color: var(--text-muted, #5a6b7a); + background: rgba(0, 245, 212, 0.06); + padding: 2px 6px; + border-radius: 3px; + flex-shrink: 0; +} + +/* Tool Detail Panel */ +.temple-detail { + flex: 1; + overflow-y: auto; + padding: 16px 20px; + border-top: 1px solid var(--border-subtle, rgba(0, 245, 212, 0.08)); + display: flex; + flex-direction: column; + gap: 14px; +} + +.temple-detail::-webkit-scrollbar { + width: 4px; +} + +.temple-detail::-webkit-scrollbar-thumb { + background: rgba(0, 245, 212, 0.15); + border-radius: 2px; +} + +.temple-detail-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.temple-detail-name { + font-size: 16px; + font-weight: 700; + color: var(--text-primary, #e8f4f8); +} + +.temple-detail-display-name { + font-size: 13px; + color: var(--accent-cyan, #00f5d4); + margin-top: 2px; +} + +.temple-detail-source { + font-size: 10px; + color: var(--text-muted, #5a6b7a); + background: rgba(0, 245, 212, 0.05); + padding: 2px 8px; + border-radius: 3px; + border: 1px solid rgba(0, 245, 212, 0.1); + flex-shrink: 0; +} + +.temple-detail-desc { + font-size: 12.5px; + color: var(--text-secondary, #8a9bae); + line-height: 1.6; +} + +.temple-detail-section { + display: flex; + flex-direction: column; + gap: 8px; +} + +.temple-detail-section-title { + font-size: 10px; + color: var(--text-muted, #5a6b7a); + letter-spacing: 1.2px; + font-weight: 600; + text-transform: uppercase; +} + +.temple-detail-stats { + display: flex; + gap: 12px; + flex-wrap: wrap; +} + +.temple-detail-stat { + display: flex; + flex-direction: column; + gap: 2px; + padding: 8px 14px; + background: rgba(0, 0, 0, 0.25); + border-radius: 6px; + border: 1px solid rgba(0, 245, 212, 0.06); + min-width: 80px; +} + +.temple-detail-stat-value { + font-size: 15px; + font-weight: 700; + color: var(--text-primary, #e8f4f8); + font-variant-numeric: tabular-nums; +} + +.temple-detail-stat-label { + font-size: 10px; + color: var(--text-muted, #5a6b7a); + letter-spacing: 0.5px; +} + +/* Commands List */ +.temple-commands { + display: flex; + flex-direction: column; + gap: 6px; +} + +.temple-command-item { + padding: 10px 14px; + background: rgba(0, 0, 0, 0.2); + border-radius: 6px; + border: 1px solid rgba(0, 245, 212, 0.06); +} + +.temple-command-name { + font-size: 12px; + font-weight: 600; + color: var(--accent-cyan, #00f5d4); + font-family: 'SF Mono', 'Fira Code', monospace; + margin-bottom: 4px; +} + +.temple-command-desc { + font-size: 11.5px; + color: var(--text-secondary, #8a9bae); + line-height: 1.5; +} + +/* Tags */ +.temple-tags { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.temple-tag { + font-size: 10px; + padding: 2px 8px; + border-radius: 3px; + background: rgba(123, 44, 191, 0.15); + color: #c084fc; + border: 1px solid rgba(123, 44, 191, 0.2); +} + +/* Empty state */ +.temple-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + height: 100%; + color: var(--text-muted, #5a6b7a); + font-size: 13px; +} + +.temple-empty-icon { + font-size: 32px; + opacity: 0.4; +} + +/* Loading */ +.temple-loading { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: var(--text-muted, #5a6b7a); + font-size: 13px; + gap: 10px; +} + +/* Skills Tab overrides */ +.temple-skills-container { + flex: 1; + overflow-y: auto; + padding: 0; +} + +/* Section label */ +.temple-section-label { + font-size: 10px; + color: var(--text-muted, #5a6b7a); + letter-spacing: 1.2px; + font-weight: 600; + text-transform: uppercase; + padding: 10px 16px 6px; +}