|
|
@@ -0,0 +1,188 @@
|
|
|
+# 2026-10-09
|
|
|
+
|
|
|
+## 启动前后端
|
|
|
+
|
|
|
+- 前端(`ai-electron/frontend`):`./node_modules/.bin/vite dev --port 3100 --strictPort` → http://localhost:3100/
|
|
|
+- 后端(`ai-server`):`mvn -pl ai-server spring-boot:run -Dspring-boot.run.workingDirectory=E:/workspace/zsjz-ai`
|
|
|
+ → http://127.0.0.1:8980/js/a (`Started App in 5.6s`,`/sys/health` 返回 200)
|
|
|
+- `/tmp/mvnw.sh` 已重建(本机 mvn 包装脚本,见 skill §1)。
|
|
|
+
|
|
|
+## ★ 环境陷阱:宿主注入的 `SERVER__PORT` 会污染 Spring Boot 端口
|
|
|
+
|
|
|
+首次启动报 `PortInUseException: Port 51536 is already in use`,而 `application.yaml` 明明写的是 `8980`。
|
|
|
+
|
|
|
+根因:WorkBuddy 宿主 shell 环境里有 `SERVER__PORT=51536` / `SERVER__HOST=127.0.0.1`。
|
|
|
+Spring Boot 的松散绑定把它解析成 `server.port` / `server.address`,
|
|
|
+**操作系统环境变量优先级高于 application.yaml**,于是后端去绑 51536 —— 而那个端口正是宿主进程自己在监听。
|
|
|
+
|
|
|
+解法(二选一):
|
|
|
+- `env -u SERVER__PORT -u SERVER__HOST bash /tmp/mvnw.sh ...`(推荐,恢复配置文件原行为)
|
|
|
+- 或 `-Dspring-boot.run.arguments=--server.port=8980`(命令行参数优先级最高)
|
|
|
+
|
|
|
+⚠️ 同类隐患:任何从本 shell 启动的 Spring Boot / Node 服务,都要先确认没有 `SERVER__*` 之类的宿主变量覆盖配置。
|
|
|
+
|
|
|
+## 智能工作台「插件与技能」:技能新增/编辑改造(按技能目录规范)
|
|
|
+
|
|
|
+需求:技能按规范落地(`SKILL.md` 必需 + `scripts/` `references/` `assets/` `testcases/` 可选),
|
|
|
+新增与编辑都要能管附属文件;查看详情要按**文件树**并能**看文件内容**。
|
|
|
+
|
|
|
+### 后端(ai-server)
|
|
|
+- `SkillStore`:
|
|
|
+ - `save(...)` 新增 7 参重载(`files` / `deletePaths`),**overwrite 时不再整目录删重建**
|
|
|
+ —— 旧行为会让编辑一次就丢光附属文件(既有隐患,本次一并修正);
|
|
|
+ - 新增 `readFile` 读单个文件:UTF-8 严格解码判文本/二进制、2MB 预览上限、路径穿越防护;
|
|
|
+ - 新增 `pruneEmptyDirs`(删空文件后不留空目录);旧 5 参重载保留,测试零改动。
|
|
|
+- 新增 `SkillFileContentVO`;`SaveSkillDTO` 加 `files`/`deletePaths`;
|
|
|
+ `AgentCapabilityService(+Impl)` 的 createSkill/updateSkill 扩参并新增 `readSkillFile`;
|
|
|
+ Controller 新增 `GET /capability/skills/{name}/file?path=`。
|
|
|
+- `SkillStoreTest` 补 4 个用例(附属文件写入/读回、路径穿越与 SKILL.md 拒绝、编辑保留未提交文件、
|
|
|
+ 二进制 base64),15 个全绿。
|
|
|
+
|
|
|
+### 前端(ai-electron/frontend)
|
|
|
+- `src/ai/components/SkillEditorModal.vue` 重写为**新增/编辑共用**:按 4 个规范目录分页签管理文件
|
|
|
+ (增/改/删/改名),二进制文件只读且保存时原样保留;编辑模式读磁盘真实内容预填。
|
|
|
+- `src/ai/views/aiPlugin/index.vue`:技能详情抽屉改「左文件树 + 右内容预览」,点任意节点按需拉内容
|
|
|
+ (图片走 base64 预览);技能卡片加「编辑」入口。
|
|
|
+- `capabilityApi.readSkillFile` + `types.ts` 的 `SaveSkillFile` / `SkillFileContent`。
|
|
|
+
|
|
|
+### 验证
|
|
|
+- 后端:`mvn -pl ai-server test -Dtest=SkillStoreTest` 15/15 通过;已重启服务(8980)。
|
|
|
+- 前端:`vue-tsc` 对 `src/ai/**` 零错误(core 的 3 个是既有问题);eslint 干净;
|
|
|
+ stylelint 对比 HEAD 无新增错误(aiPlugin 既有 28 处 rgba/alpha 写法问题未动)。
|
|
|
+- **未验证**:UI 端到端 —— 接口需登录态,curl 只能确认路由已注册(返回业务码 401)。
|
|
|
+
|
|
|
+### 补充:技能编辑器补 label(用户反馈「不知道要填什么」)
|
|
|
+- `SkillEditorModal.vue`:技能名 / 描述 / 正文 / 附属文件名 / 附属文件内容 全部改成
|
|
|
+ 「label 在上、控件在下」(`__field` + `__label` + `__req` 必填星号),placeholder 只留示例。
|
|
|
+- `AgentFormModal.vue` 专家弹窗里的技能子面板同样补 label:用 `__sfield` / `__slabel`,
|
|
|
+ **不能复用该文件的 `__label`** —— 那是横向表单行用的(`width:96px; text-align:right`),
|
|
|
+ 而 `__seditor` 是纵向 flex 布局。
|
|
|
+- 验证:`vue-tsc` 对 `src/ai` 零错误;eslint 干净;stylelint 对比 HEAD 无新增(AgentFormModal 既有 6 处 rgba 写法未动)。
|
|
|
+
|
|
|
+### 补充:技能详情抽屉宽度回退
|
|
|
+- 用户反馈「查看详情侧边栏太宽」→ 抽屉从 1000 回到原来的 **720**,左文件树从 340 收到 **240**(两栏内容区约 424px)。
|
|
|
+- 结论:**详情抽屉宽度沿用 720 这个既有口径**,别再自行放大;两栏布局时左树固定 240px。
|
|
|
+
|
|
|
+### 补充:技能编辑弹框限高
|
|
|
+- 用户反馈「新增/编辑弹框不能一直增高,要用滚动条」→ `SkillEditorModal` 的 BasicModal 加
|
|
|
+ `:body-style="{ maxHeight: '64vh', overflowY: 'auto' }"`。
|
|
|
+- **口径**:本项目的长内容弹框统一用 `maxHeight: 64vh + overflowY: auto`
|
|
|
+ (aiPlugin 的工具包弹框、AgentFormModal、ArtifactPreviewModal 都是这套),新弹框照抄即可。
|
|
|
+
|
|
|
+### 技能详情改为只读弹框(与编辑弹框同构)
|
|
|
+- 用户要求「查看详情也用新增编辑一样的弹框,只读即可」→ 新增 `src/ai/components/SkillDetailModal.vue`:
|
|
|
+ 900 宽 BasicModal、`:footer="null"`、bodyStyle 64vh 限高、SKILL.md + 四个规范目录页签全只读;
|
|
|
+ 打开时读详情并**并发**拉全部附属文件内容(文本 content / 图片 base64)。
|
|
|
+- 抽 `src/ai/utils/skill.ts`:`SKILL_STD_DIRS` / `SKILL_NAME_RE` / `formatFileSize` / `splitSkillMd` /
|
|
|
+ `topDirOf` / `leafPath` / `groupSkillFiles` / `isImagePath` / `imageMimeOf` ——
|
|
|
+ 详情与编辑弹框共用,避免两边各写一份后漂移。
|
|
|
+- `aiPlugin/index.vue` 大幅瘦身:删掉详情抽屉(a-drawer)、`skillTree`、`openDirs/isDirOpen/toggleDir/
|
|
|
+ dirTag/leafName/formatSize`、`openSkillFile`、`IMAGE_MIME/fileExt/isImageFile/imageMime`、
|
|
|
+ `readSkill/readSkillFile` 引用、`Drawer` import、`skillLoading`;改为
|
|
|
+ `<SkillDetailModal v-model:open="skillOpen" :skill="skillViewing" :case-id="caseId" />`。
|
|
|
+- ★ 顺手修掉上一轮引入的样式冲突:详情抽屉的 `&__detail` 与**工具详情弹框**既有的 `&__detail` 同名,
|
|
|
+ LESS 会把两条规则合并(工具的详情布局被带偏)—— 已随抽屉样式一起删除。
|
|
|
+- 验证:`vue-tsc` 对 `src/ai` 零错误;eslint 干净;stylelint 26 处(比改动前少 1,全是既有 rgba 写法)。
|
|
|
+
|
|
|
+### 研判方案「新建/编辑方案」弹框行距修正
|
|
|
+- 用户反馈「表单间距太大」→ 根因:`a-form-item` 默认带 `margin-bottom: 24px`(antd 给校验提示留位),
|
|
|
+ 而这个弹框的字段名是**自己画的 label(在 form-item 外面)**,再叠 24px 会把行距从设计意图的
|
|
|
+ 20px(gap 12 + row margin 8)顶成 44px。
|
|
|
+- 修法:`&__form :deep(.ant-form-item) { margin-bottom: 0; }`,行距交回 `__row` 统一控制。
|
|
|
+ 校验失败时 explain 在 form-item 内部、会把该行撑高,不会压到下一行。
|
|
|
+- **同类坑**:凡是「自绘 label + a-form-item 只包控件」的表单,都要压平 form-item 的 24px 下边距。
|
|
|
+- 验证:eslint 干净;stylelint 6 处(与改动前一致,全是既有 rgba 写法)。
|
|
|
+
|
|
|
+### 研判方案弹框:移除「改技能」入口
|
|
|
+- 用户要求「弹框里选择技能那里不可以去编辑技能」→ `AgentFormModal.vue` 删除:
|
|
|
+ skillEditor 子面板(模板 + reactive 状态 + `openSkillEditor/closeSkillEditor/prefillSkill/saveSkill/
|
|
|
+ splitSkillMd/reloadSkills`)、技能卡片上的铅笔按钮(`__card-ops` / `__iconbtn` 及样式);
|
|
|
+ 顺带清掉不再使用的 `Button` import 与 `SaveSkillParams` 类型引用。
|
|
|
+- 文案改为「技能的新增与编辑在『插件与技能 → 技能』页进行」,卡片只剩勾选语义。
|
|
|
+- **口径**:研判方案弹框里的技能/工具包一律只做勾选,**任何技能编辑能力都归「插件与技能」页**。
|
|
|
+- 验证:`vue-tsc` 对 `src/ai` 零错误;eslint 干净;stylelint 6 处(与改动前一致)。
|
|
|
+
|
|
|
+### 技能详情弹框:只读控件底纹置灰
|
|
|
+- 用户要求「查看技能的时候输入框这些底纹置灰色」→ `SkillDetailModal.vue` 加:
|
|
|
+ `:deep(.ant-input) { &[readonly] { background: var(--ai-bg-subtle, #f5f5f5); ... } }`,
|
|
|
+ hover/focus 一并覆盖为同色 + `box-shadow: none`,避免只读控件看起来像能编辑。
|
|
|
+- **口径**:antd 的 `readonly` 输入框默认仍是白底(只有 `disabled` 才自动变灰),
|
|
|
+ 凡是「只读展示」的 a-input / a-textarea 都要自己压底色;底色取 `--ai-bg-subtle`
|
|
|
+ (= `@cb-vscode-input-background`,就是项目里输入框的底色 token)。
|
|
|
+- 验证:eslint 干净;stylelint 干净。**视觉未在真实页面确认**(需登录态)。
|
|
|
+
|
|
|
+### 工具界面中文化(展示名与调用名分离)
|
|
|
+
|
|
|
+需求:「工具界面工具名都是英文,能不能做成中文;会不会影响 agent 调用;有影响就加表字段」。
|
|
|
+
|
|
|
+- **结论:不影响调用,因此未加表字段。**
|
|
|
+ - 调用名是 `@Tool(name)` → `ToolSchema.getName()` → 模型 function name,与页面显示完全无关。
|
|
|
+ - 反过来,**直接改 `@Tool(name)` 为中文会炸**:OpenAI/兼容 API 的 function name 只接受
|
|
|
+ `[a-zA-Z0-9_-]{1,64}`;且 `agent_tool_pack.tool_names`、MCP `ToolDefinition.name`、
|
|
|
+ 历史消息里的工具名都按英文名引用。
|
|
|
+- **依据(已实测)**:`ai-electron/frontend/src/ai/utils/constants.ts` 的 `TOOL_LABELS`
|
|
|
+ (上个提交 df03094 引入,聊天时间线 / 状态栏 / 产物栏在用)**已完整覆盖**后端全部 64 个 `@Tool`——
|
|
|
+ 用 grep 提取 `@Tool(name=...)` 与 `TOOL_LABELS` 的 key 做 `comm` 差集,结果为空;
|
|
|
+ 前端多出的 5 条是 MCP 案件工具(list_cases/open_case/current_case/close_case)与框架内置
|
|
|
+ (reset_equipped_tools),它们不进 `agent_tool` 表。所以工具页复用这张表即可 100% 中文,零后端/DB 改动。
|
|
|
+- 改动(全前端):
|
|
|
+ - `aiPlugin/index.vue`:工具卡片主标题、工具包编辑器卡片、工具包详情弹框的工具名 → `toolLabel()`;
|
|
|
+ 英文调用名降为小字等宽副信息(新增 `__tool-code` / `__pack-card-code` / `__pack-tool-code`,
|
|
|
+ 并给 `__card-meta` 加 flex 布局);工具详情弹框标题改中文并新增「调用名(模型按它调用工具)」块;
|
|
|
+ 工具页与工具包编辑器两处搜索框都支持**中文名和英文名同时命中**;placeholder 补中文示例。
|
|
|
+ - `AgentFormModal.vue`:研判方案弹框里工具包卡片的工具 chips 同样走 `toolLabel()`。
|
|
|
+- 验证:eslint 干净;stylelint 与 HEAD 基线**完全一致**(aiPlugin 仅剩既有 8 处 rgba + 1 处
|
|
|
+ comment-empty-line-before + 1 处 `__mono` 的 font-family 引号;AgentFormModal 仅既有 2 处 rgba);
|
|
|
+ `vue-tsc` 全量 87 个错误全在 call/case/core/graph/otg/person/trans,**`src/ai` 零错误**。
|
|
|
+- **遗留隐患**:中文名表在前端,后端新增工具必须同步补 `TOOL_LABELS`(constants.ts 里已写明)。
|
|
|
+ 若日后要「后端权威」,需在 `agent_tool` 加 `display_name` 列(固定表名 DDL 只进 `sql/agent_tool.sql`,
|
|
|
+ 由人工执行 ALTER),但**省不掉**前端那份表 —— 聊天时间线是流式渲染,SSE 事件里只有 name。
|
|
|
+
|
|
|
+### 补充:工具界面只留中文(撤掉英文原名)
|
|
|
+
|
|
|
+- 用户要求「工具列表上不展示英文名称,只展示中文」→ 撤掉上一轮加的英文副信息:
|
|
|
+ `__tool-code` / `__pack-card-code` / `__pack-tool-code` 三处模板节点与样式、工具详情弹框的
|
|
|
+ 「调用名」块、工具卡片的 `:title="toolName"`、工具包 chips 的 `:title`,以及 placeholder / hint
|
|
|
+ 里的英文示例(`stat_trans_big` → 「大额交易」,`execute_sql` → 「执行 SQL 查询」)全部删除。
|
|
|
+- **口径**:工具界面上不出现任何英文工具名(含悬停提示与占位文案)。
|
|
|
+ 但搜索仍**同时匹配中英文**——能力保留,只是不再提示;从日志里复制英文名来搜是常见用法。
|
|
|
+- 验证:eslint 干净;stylelint 与基线一致(8 rgba / 1 comment / 1 quotes,全既有);
|
|
|
+ `vue-tsc` 的 `src/ai` 零错误。
|
|
|
+
|
|
|
+### 自主 ⇄ 智能 模式切换:各模式记住自己的停留位置
|
|
|
+
|
|
|
+- 问题:来回切每次都掉回默认落点(切回自主 → 首页;切回智能 → 工作台默认视图)。
|
|
|
+- 根因两处:
|
|
|
+ 1. `core/logics/workbenchMode.ts` 的 `setMode` 固定跳 `WORKBENCH_PATH` / `BASE_HOME`;
|
|
|
+ 2. 智能工作台是**单路由全屏壳**(`/workbench`,内部 `view` 不落 URL),切走即卸载,视图回到默认落点。
|
|
|
+- 改法:
|
|
|
+ - `core/enums/cacheEnum.ts` 新增 `APP_WORKBENCH_PATH_KEY` / `APP_WORKBENCH_VIEW_KEY`。
|
|
|
+ - `core/logics/workbenchMode.ts` 新增 `get/setLastTraditionalPath`、`get/setLastWorkbenchView`(**sessionStorage**,
|
|
|
+ 读写带 try/catch 兜底);`setMode` 改为「切走前记下当前页 → 切回时跳上次停留处,没记忆才回默认落点」。
|
|
|
+ 切到 traditional 仍只在「当前在工作台」时才跳 —— 在传统壳点自己那一侧只对齐偏好,不该把人挪走。
|
|
|
+ - `ai/views/aiWorkbench/index.vue`:`view` 初值 = `?view=` 参数 ?? 上次停留视图 ?? `'chat'`;
|
|
|
+ `watch(view)` 持续写回;`userChoseView` 初值 = 是否有记忆视图;`applyDefaultView` 条件改为
|
|
|
+ `userChoseView && (view !== 'chat' || hasConversation)` —— 「对话」要靠会话上下文才成立,
|
|
|
+ 刷新后 store 是新的、会话还没选,那种情况仍交给默认落点选一条,免得落在一屏空白欢迎页。
|
|
|
+- **为什么用 sessionStorage 而非 localStorage**:只在本次运行内有效,重启后各模式回默认落点,
|
|
|
+ 免得上次遗留的页面在案件上下文变化后变成一次莫名其妙的跳转。
|
|
|
+- **会话/消息不用另外存**:`useChatStreamStore` 是 Pinia 全局单例(`defineStore('ai-chat-stream')`),
|
|
|
+ 组件卸载不销毁;且 `store.init` 有幂等保护(`initialized && workspaceId 相同` 直接 return),
|
|
|
+ 所以切回工作台时会话选择与消息都还在。
|
|
|
+- 验证:eslint / stylelint 干净;`vue-tsc` 的 `src/ai` 零错误,`src/core` 仅 3 个**既有**错误
|
|
|
+ (BasicButton 的 `Fn`、TableAction 的 `ActionItem`、useECharts 的恒真判断),与本次改动无关。
|
|
|
+- **未验证**:真实来回点击(需登录态 + 案件上下文)。
|
|
|
+
|
|
|
+### 提交 `1f7ae24`(分支 dev_4)
|
|
|
+
|
|
|
+- 范围:本轮工作 6 个文件(SkillDetailModal / aiPlugin / AgentFormModal / cacheEnum /
|
|
|
+ workbenchMode / aiWorkbench)+ 在途的工作台改动。
|
|
|
+- `aiWorkbench/index.vue` 里我的改动与**在途改动**(移除「文件管理」视图、去掉顶栏会话 chip)
|
|
|
+ 交织在同一批 hunk 里,无法按路径拆开,因此连配套的 `ai/views/aiCaseData/` 两个已 staged 删除
|
|
|
+ 一起提交(否则会留下两个没人引用的孤儿文件)。commit message 末尾已注明这一点。
|
|
|
+- 未提交:`ChatComposer.vue`、`aiExpert/index.vue`(纯 UI 元素顺序调整,与本轮无关)、
|
|
|
+ `.workbuddy-ai/memory/2026-10-09.md`(本地工作记忆,不入库)。
|
|
|
+- ⚠️ 提交命令末尾会出现 `PROGRAM BLOCKED BY SECURITY POLICY`(git 内部调用 `sc.exe` / `reg.exe`
|
|
|
+ 被沙箱黑名单拦下)—— **提交本身已成功**,看 `git log` 的输出为准,别被这行吓到。
|
|
|
+
|
|
|
+
|