diff --git a/.cursor/changelog/changelog-full.md b/.cursor/changelog/changelog-full.md index 0b4cd32..8e84bca 100644 --- a/.cursor/changelog/changelog-full.md +++ b/.cursor/changelog/changelog-full.md @@ -4,6 +4,207 @@ ## 记录 +### [CL-20260417-1510] 2026-04-17 15:10 — Cloudflare Quick Tunnel 脚本修复(PS5.1 编码 + 本地 cloudflared) +- **tags**: 内网穿透, cloudflared, PowerShell, start-tunnel, UTF-8, gitignore +- **affected_files**: + - art-agent/start-tunnel.ps1 + - .gitignore +- **what**: 修复 `start-tunnel.ps1` 在 Windows PowerShell 5.1 下因脚本 UTF-8 无 BOM、含中文的 here-string(`@"`)被误解析而导致的 `ParserError`。将写入 `frontend/.env.local` 的两处 here-string 改为 `[System.IO.File]::WriteAllLines` + UTF-8 无 BOM(与 Next 读取 `.env` 习惯一致)。用 PowerShell 将脚本全文重写为 **UTF-8 BOM**,便于 PS5.1 正确分词。在脚本开头增加:若存在与脚本同目录的 `cloudflared.exe`,则把该目录插入 `PATH` 最前,便于未全局安装 cloudflared 时使用本地下载的可执行文件。根目录 `.gitignore` 增加 `art-agent/cloudflared.exe`,避免误提交约 65MB 二进制。 +- **why**: 用户请求开启穿透时,脚本在 Cursor 集成终端中立即解析失败,隧道无法拉起;当前环境亦未必安装 winget/系统 PATH 中的 cloudflared,需要可放置于项目目录的 fallback。 +- **decisions**: 不改为仅英文输出(保留原有中文提示与框线字符);`.env.local` 注释改为英文短句以避免无 BOM 环境下再次出现字符串边界问题。 +- **notes**: 会话中曾从 GitHub latest release 下载 `cloudflared-windows-amd64.exe` 至 `art-agent/cloudflared.exe` 并成功启动双隧道;该文件已被 gitignore。 + +### [CL-20260417-1500] 2026-04-17 15:00 — 按 INFOLAYER.md 实现完整前端信息架构(Phase 1+ 占位骨架) +- **tags**: 前端, UI骨架, INFOLAYER, 多项目, 风格库, 角色库, 训练中心, 项目设置, TaskCard, 候选评估, 占位数据 +- **affected_files**: + - art-agent/frontend/src/lib/types.ts + - art-agent/frontend/src/lib/placeholder-store.ts + - art-agent/frontend/src/lib/app-context.tsx + - art-agent/frontend/src/app/globals.css + - art-agent/frontend/src/app/page.tsx + - art-agent/frontend/src/app/gallery/page.tsx + - art-agent/frontend/src/app/styles/page.tsx + - art-agent/frontend/src/app/styles/[id]/page.tsx + - art-agent/frontend/src/app/characters/page.tsx + - art-agent/frontend/src/app/characters/[id]/page.tsx + - art-agent/frontend/src/app/training/page.tsx + - art-agent/frontend/src/app/training/[id]/page.tsx + - art-agent/frontend/src/app/training/new/page.tsx + - art-agent/frontend/src/app/settings/page.tsx + - art-agent/frontend/src/components/layout/top-nav.tsx + - art-agent/frontend/src/components/layout/project-switcher.tsx + - art-agent/frontend/src/components/sidebar/sidebar.tsx + - art-agent/frontend/src/components/sidebar/project-card.tsx + - art-agent/frontend/src/components/chat/session-quick-picks.tsx + - art-agent/frontend/src/components/chat/task-card.tsx + - art-agent/frontend/src/components/workbench/advanced-controls.tsx + - art-agent/frontend/src/components/workbench/candidate-panel.tsx +- **what**: 按 `docs/art-agent/INFOLAYER.md` 规范一次性铺开 Phase 1–3 产品形态的前端 UI 骨架:新增风格库 `/styles`、角色库 `/characters`、训练中心 `/training`、项目设置 `/settings` 四大页面模块(含列表 + 详情 + 创建流程);工作台输入区上方加入风格/角色/资产规格快选条、高级控制抽屉、候选评估侧面板、Task Card IR 占位组件;资源库新增资产类型/风格/角色多维过滤和语义搜索切换;TopNav 扩充导航项与项目切换器,Sidebar 加入项目信息卡;新增 `lib/placeholder-store.ts` 管理 Phase 1+ 占位数据(项目/风格集/角色卡/训练任务)并在 `app-context.tsx` 中统一暴露;`globals.css` 补充 `phase-chip` / `placeholder-card` / `hide-scrollbar` / `section-divider` 工具类。 +- **why**: INFOLAYER.md 规划了 Phase 0–3 完整产品形态,其中 Phase 1+(多项目、风格库/角色库、训练中心、Task Card、候选评估等)尚未具备后端能力。用户希望先把完整交互结构和界面落地,"数据或展示上写占位"即可,方便内部演示、收集反馈以及后续按 Phase 增量接入真实后端。 +- **decisions**: (1) 占位数据用独立的 `placeholder-store.ts` + `localStorage` 持久化,与现有 `store.ts` 解耦,首次加载注入示例数据(2 个项目 / 4 个风格集 / 2 个角色 / 4 个训练任务)。(2) 详情页直接用 `[id]/page.tsx` 同时处理 `new` 与编辑两种模式(判断 `params.id === 'new'`),不再新增 `/styles/new` 等专用子路由。(3) Task Card 作为独立可复用组件 (`components/chat/task-card.tsx`),不强制接入当前 chat loop,等真实 IR 管线就绪时再 wire up。(4) 候选评估面板从"最后一条含 images 的 assistant 消息"取数据做 demo。(5) Gallery 风格/角色过滤用 prompt 关键词子串匹配做占位,未来等后端补 asset 归因字段后再切到真实字段。(6) 所有 Phase 1+ 功能按钮均挂 `phase-chip`(P1/P2/P3)明示阶段,避免用户误以为已可用。 +- **notes**: 所有"应用到会话""Fork""训练""批量下载"等交互都是 `alert("(占位)...")`,不产生真实后端调用;真实接入时搜索 `(占位)` 或 `phase-chip` 可快速定位需要替换的位置。`AppContext` 新增字段:`projects` / `activeProjectId` / `stylePacks` / `characters` / `trainingTasks` / `sessionStyleId` / `sessionCharacterId` / `sessionAssetSpec` 及对应 upsert/delete 方法;切换用户时会重新加载全部占位数据。`/styles/[id]` 和 `/characters/[id]` 的"生成图片"列表也是基于关键词从现有 assets 里筛的占位结果,不是真正的归因。`ChatMessage` 未扩展,Task Card 目前仅以组件形式存在,实际渲染位置需要后续决定(工作台消息流或独立抽屉)。 +- **source_chat**: [实现 INFOLAYER 完整 UI 骨架](607b2d99-ff5b-48a6-91b5-1ada004cd8d5) + +### [CL-20260416-1600] 2026-04-16 16:00 — 视觉风格从暗绿底全面转为白底青绿(绢本山水) +- **tags**: 前端, 视觉风格, 色彩体系, CSS变量, 白底, 千里江山图 +- **affected_files**: + - art-agent/frontend/src/app/globals.css + - art-agent/frontend/src/app/page.tsx + - art-agent/frontend/src/app/login/page.tsx + - art-agent/frontend/src/app/gallery/page.tsx + - art-agent/frontend/src/components/chat/chat-messages.tsx + - art-agent/frontend/src/components/chat/chat-input.tsx + - art-agent/frontend/src/components/layout/top-nav.tsx + - art-agent/frontend/src/components/sidebar/sidebar.tsx + - art-agent/frontend/src/components/sidebar/session-list.tsx + - art-agent/frontend/src/components/profile-modal.tsx + - art-agent/frontend/src/components/detail/image-detail-panel.tsx + - art-agent/frontend/src/components/ui/ambient-particles.tsx + - docs/art-agent/VISUAL-STYLE-GUIDE.md +- **what**: 整个应用从暗色(墨绿黑 #0C1210)底色改为白色(绢本白 #FAFAF7)底色,文字改为墨黑(#1A1A1A),框体和光效保留千里江山图的蓝绿配色但色值加深适配白底 +- **why**: 用户反馈当前风格"太偏绿了"。评估后认同——原版将背景、面板、文字全都带绿调,导致全屏绿色信息过载。千里江山图本身是青绿设色于白色绢本之上,白底才更贴近原画精髓 +- **decisions**: + - 背景 #0C1210 → #FAFAF7(绢本微暖白),面板从绿底毛玻璃改为白色毛玻璃 + - accent #4DB8A4 → #2E8B7A(加深以确保白底上对比度) + - 文字 #E0E8E2 → #1A1A1A(墨黑),次级文字 #7A9485 → #6B7B8A(灰蓝去绿调) + - 所有 rgba(77,184,164,...) 硬编码统一替换为 rgba(46,139,122,...) + - 云雾背景透明度大幅降低(白底上需要更淡才不会突兀) + - Logo 内部颜色从 #0C1210 改为 white(石青底白字) + - 保留所有动效系统(云烟缭绕、萤火粒子、水波纹等),仅调整颜色参数 +- **notes**: + - surface-1/2/3 从深色半透明改为白色半透明,阴影也从重阴影改为轻阴影 + - 网格底纹 opacity 从 0.03 降为 0.018 + - VISUAL-STYLE-GUIDE.md 文档同步重写 +- **source_chat**: [白底青绿视觉风格改造] + +### [CL-20260416-1500] 2026-04-16 15:00 — UX风格指南实现度补全:接入涟漪/sendBounce/会话切换动画/Error飘散/全屏云雾加载/流式opacity阶梯等10项 +- **tags**: 前端, UX补全, 动效, framer-motion, 微交互, 排版使用, 实现状态文档 +- **affected_files**: + - art-agent/frontend/src/components/chat/chat-input.tsx + - art-agent/frontend/src/components/chat/chat-messages.tsx + - art-agent/frontend/src/components/chat/image-grid.tsx + - art-agent/frontend/src/components/ui/motion-presets.ts + - art-agent/frontend/src/app/page.tsx + - art-agent/frontend/src/app/login/page.tsx + - art-agent/frontend/src/app/gallery/page.tsx + - art-agent/frontend/src/app/globals.css + - art-agent/frontend/src/lib/app-context.tsx + - art-agent/frontend/src/components/sidebar/sidebar.tsx + - art-agent/frontend/src/components/profile-modal.tsx + - docs/art-agent/UX-STYLE-GUIDE.md +- **what**: 补全 UX-STYLE-GUIDE.md 中描述但未实现的 10 项特性,并在文档中标注每项实现状态 +- **why**: 分析发现风格指南完成度约 65-70%,多项微交互只写了文档描述或代码定义了但未接入业务 +- **decisions**: 对每项未实现特性逐一补全代码,同时更新文档加入 ✅/🔶/⬜ 状态标记 +- **notes**: 补全项包括:(1) ripple-effect.tsx 接入发送按钮 (2) sendBounce 替代 buttonTap (3) 会话切换 sessionSlide 滑入滑出 (4) Error errorFloat 飘散消失+4s自动清除 (5) 回到底部按钮水滴形 (6) 全屏加载改云雾凝聚 mist-loader (7) AI回复 StreamingLines 逐行 opacity 阶梯 (8) typo-h1/h2/strong 实际使用到组件 (9) 用户消息 .user-bubble 左上切角 (10) 图片占位 .stone-placeholder 石青渐变呼吸脉冲。文档每个 section 加入实现文件路径和状态标记。 + +### [CL-20260416-1330] 2026-04-16 13:30 — 交互体验全面升级:信息层级 + 科技质感 + 微交互趣味性 +- **tags**: 前端, UX升级, 动效, framer-motion, 排版体系, 微交互, 视觉层级, 科技感, 粒子, 流光 +- **affected_files**: + - art-agent/frontend/src/app/globals.css + - art-agent/frontend/src/app/page.tsx + - art-agent/frontend/src/app/login/page.tsx + - art-agent/frontend/src/app/gallery/page.tsx + - art-agent/frontend/src/components/chat/chat-messages.tsx + - art-agent/frontend/src/components/chat/chat-input.tsx + - art-agent/frontend/src/components/chat/image-grid.tsx + - art-agent/frontend/src/components/chat/model-selector.tsx + - art-agent/frontend/src/components/sidebar/sidebar.tsx + - art-agent/frontend/src/components/sidebar/session-list.tsx + - art-agent/frontend/src/components/layout/top-nav.tsx + - art-agent/frontend/src/components/detail/image-detail-panel.tsx + - art-agent/frontend/src/components/profile-modal.tsx + - art-agent/frontend/src/components/ui/motion-presets.ts (新建) + - art-agent/frontend/src/components/ui/ambient-particles.tsx (新建) + - art-agent/frontend/src/components/ui/data-stream.tsx (新建) + - art-agent/frontend/src/components/ui/ripple-effect.tsx (新建) + - art-agent/frontend/src/components/ui/firefly-burst.tsx (新建) + - docs/art-agent/UX-STYLE-GUIDE.md (新建) +- **what**: 四阶段全面升级交互体验:信息层级体系、科技质感注入、微交互趣味性、统一状态反馈 +- **why**: 用户要求信息层级差异更突出、画面更高级典雅且具科技感、交互有巧思让使用心情愉悦 +- **decisions**: + - 新增 framer-motion 作为动画引擎(~30KB gzipped),不引入其他 UI 库 + - 排版从 5 级扩展到 7 级(Display 渐变文字 / H1 底部石青线 / H2 左侧竖线 / Body Strong 石青色 / Caption / Micro 大写加宽字间距),CSS 工具类 `.typo-*` + - Surface 层级从 1 级扩展到 4 级(Surface-0 背景 → Surface-3 弹窗),每级递增 backdrop-blur、border 亮度、box-shadow + - 背景网格底纹(radial-gradient 点阵 opacity 0.03)从 body::before 实现,原云雾改为独立 DOM 元素 `.fog-layer-far/near` + - 环境粒子纯 CSS 实现(15-28 个随机漂浮+闪烁的石青色小光点),空态时增多营造"未探索深山"感 + - 数据流光(石青色 linear-gradient 横向流动 2px),AI 活动时出现在输入栏上方,生图时加速 + - 用户消息改为右对齐渐变背景+右上切角,助手消息改为 surface-2 + 左侧 2px 石青竖线 + - 消息入场动画:用户消息 translateY+scale spring,助手消息 blur 消散(云雾凝聚) + - 发送按钮 whileTap scale 0.85,可发送时呼吸脉冲发光 + - 输入框水面感应:焦点时底部 scaleX 扩展线 + box-shadow 随输入长度增强 + - 收藏星标改为赭金色,点击时 5-7 个金色粒子向四周飘散(萤火点亮) + - 图片水墨晕染:skeleton shimmer 占位 → clip-path circle 从中心扩展揭示 + - Loading 从 bounce 圆点改为水波纹 scaleY 动画,skeleton 用 shimmer 横向扫描 + - 错误状态赭石红 + translateX 震动 2px × 4 次 + - 空态使用 Display 级渐变文字 + stagger 入场动画 + - 滚到底部按钮改圆形 + spring 弹入/退出 + - 侧栏会话项 hover 时左侧 2px 竖线 slide-in + - 所有按钮 hover 添加 translateY(-1px) + shadow 加深浮起效果 + - 所有弹出层改用 surface-3(强模糊+明显阴影+更亮边框) + - 统一动画参数:motion-presets.ts 定义 spring/springGentle、fadeIn/slideUp/mistReveal、messageEnter/assistantEnter、buttonTap/sendBounce、ripple/sparkle/inkReveal、staggerContainer/staggerItem +- **notes**: + - framer-motion 通过 npm install 添加(3 个包),前端 bundle 增加约 30KB gzipped + - 环境粒子使用 CSS animation 而非 Canvas,不影响性能 + - 所有动画使用 transform/opacity/filter,避免触发 layout + - body::before 原来用于云雾现改为点阵网格,云雾改为 AmbientParticles 组件内的 DOM 元素 + - 构建验证通过(next build 无错误),First Load JS 增量约 50KB(framer-motion) +- **source_chat**: [交互体验全面升级](6b0e5860-9c5a-4b44-aa96-affba21e9472) + +### [CL-20260416-1000] 2026-04-16 10:00 — 集成 Zero123++ 视角变换工具(单图→6视角) +- **tags**: 视角变换, Zero123++, Novel View Synthesis, 新工具, Replicate, 图片处理 +- **affected_files**: + - art-agent/backend/app/config.py + - art-agent/backend/app/agent/tools.py + - art-agent/backend/app/agent/loop.py + - art-agent/backend/app/services/view_transform.py +- **what**: 新增 `transform_view` 工具,集成 Zero123++ 模型实现单图生成 6 个固定视角图片 +- **why**: 用户需要将手绘建筑从侧面视角转为其他角度,视角变换是与生图完全不同的能力,需要独立工具 +- **decisions**: + - 视角变换作为独立工具 `transform_view` 而非 `generate_image` 的扩展——输入不需要 prompt,只需一张图 + - 新增独立服务模块 `view_transform.py`,与 `image_gen.py` 分离——逻辑完全不同(无 prompt、grid 拆分) + - 在 `config.py` 中新增 `VIEW_TRANSFORM_MODELS` 独立注册表——与 `IMAGE_MODELS` 分离,未来可扩展更多视角变换模型 + - Zero123++ 输出为 3x2 grid 拼接图,用 Pillow 拆分为 6 张独立图片返回 + - 进阶方案(3D 重建 + ControlNet 风格还原 pipeline)记录到延期方案 `[advanced-view-transform]` +- **notes**: + - Zero123++ 视角固定(Azimuth: 30/90/150/210/270/330°, Elevation: 30/-20 交替),不支持自定义角度 + - 手绘风格保持度可能不理想(模型偏向写实/3D 风格),需实际测试验证 + - 输入图片需为正方形,建议 >= 320x320 + - Replicate 上的模型 ID: `jd7h/zero123plusplus:c69c6559...`,~$0.074/次,~76s + - loop.py 中对 `transform_view` 做了特殊结果处理(返回带视角信息的图片列表) + +### [CL-20260416-0930] 2026-04-16 09:30 — 设备绑定配置与 Skill/Hooks 解耦(local-env.json) +- **tags**: 基础设施, local-env, 跨设备, hooks, project-launcher, Skill +- **affected_files**: + - .cursor/local-env.json + - .cursor/local-env.example.json + - .cursor/hooks.json + - .cursor/hooks/run-hook.ps1 + - .cursor/hooks/session-init.sh + - .cursor/hooks/check-changelog.sh + - .cursor/hooks/README.md + - .cursor/rules/project/local-env-check.mdc + - .cursor/skills/project-launcher/SKILL.md + - .gitignore + - art-agent/ENVIRONMENT.md +- **what**: 将设备绑定的配置(Node.js 路径、shell 类型)从 Skill 和 hooks 中解耦到 `.cursor/local-env.json`(gitignored)。project-launcher Skill 改为从 local-env.json 读取路径;hooks 增加 run-hook.ps1 dispatcher + bash 版脚本支持跨平台;新增 local-env-check Rule 自动探测生成;ENVIRONMENT.md 去除平台硬编码改为通用说明 +- **why**: Node.js 路径和 shell 类型在不同设备上不同,硬编码在 Skill/hooks 中导致换机或跨平台时需要修改 git tracked 文件 +- **decisions**: 采用 `.cursor/local-env.json`(gitignored)+ `.cursor/local-env.example.json`(tracked 模板)的模式,类似 `.env` / `.env.example` 的惯例;hooks.json 的跨平台靠为每个 hook 提供 .ps1 和 .sh 双版本 +- **notes**: hooks.json 的 command 字段本身仍需按平台手动调整(Windows 用 powershell,macOS/Linux 用 bash),这是 Cursor hooks 机制的限制 +- **source_chat**: [local-env 设备配置解耦] + +### [CL-20260416-0900] 2026-04-16 09:00 — 环境依赖全面修复 + 统一环境文档 +- **tags**: 环境, 依赖, Node.js, Python, venv, npm, requirements, ENVIRONMENT +- **affected_files**: + - art-agent/backend/requirements.txt + - art-agent/backend/requirements-lock.txt + - art-agent/ENVIRONMENT.md + - .cursor/skills/project-launcher/SKILL.md +- **what**: 排查并修复缺失的前后端依赖(venv 和 node_modules 均不存在),安装 Node.js v22.15.0 到 %LOCALAPPDATA%\nodejs\,创建 Python venv 并安装全部后端依赖,npm install 前端依赖;requirements.txt 从范围版本更新为精确锁定版本;新增 requirements-lock.txt(完整依赖树 61 个包)和 ENVIRONMENT.md 统一环境文档 +- **why**: venv 和 node_modules 被 gitignore,换机/重装后需要重新搭建;原 Node.js 安装已丢失;requirements.txt 仅用 >= 范围无版本锁定 +- **decisions**: Node.js 采用 zip 解压到 %LOCALAPPDATA% 而非 MSI 安装(无管理员权限);requirements.txt 直接锁定到当前精确版本而非继续用范围约束 +- **notes**: project-launcher Skill 中的 Node.js 路径已同步更新为 %LOCALAPPDATA%\nodejs\ +- **source_chat**: [环境依赖修复与文档生成] + ### [CL-20260416-0830] 2026-04-16 08:30 — 生图按模型预处理 prompt(官网/社区策略对齐) - **tags**: 后端, 生图, prompt, SDXL, Flux, GPT Image, Gemini, Replicate, IP-Adapter, image_gen, Agent - **affected_files**: diff --git a/.cursor/changelog/changelog-headlines.md b/.cursor/changelog/changelog-headlines.md index f820f76..4d566d8 100644 --- a/.cursor/changelog/changelog-headlines.md +++ b/.cursor/changelog/changelog-headlines.md @@ -2,6 +2,14 @@ 最近 ~50 次改动的一句话概要,按时间倒序排列。每次会话自动注入上下文。 +- [CL-20260417-1510] start-tunnel.ps1 修复 PS5.1 中文 here-string 解析失败:WriteAllLines 写 .env、脚本 UTF-8 BOM、同目录 cloudflared.exe 优先;根 .gitignore 忽略下载的 cloudflared.exe +- [CL-20260417-1500] 按 INFOLAYER.md 一次性铺开 Phase 1+ 前端 UI 骨架:新增风格库/角色库/训练中心/项目设置四大页面模块+工作台快选条+高级控制+候选评估+Task Card+资源库多维过滤,placeholder-store.ts 管理占位数据,phase-chip 标注阶段 +- [CL-20260416-1600] 视觉风格从暗绿底全面转为白底青绿(绢本山水):背景#FAFAF7+文字#1A1A1A+accent#2E8B7A,13个文件颜色替换,面板改白色毛玻璃,云雾透明度降低,VISUAL-STYLE-GUIDE.md重写 +- [CL-20260416-1500] UX风格指南实现度补全:涟漪/sendBounce/会话切换动画/Error飘散/云雾加载/流式opacity/排版类使用/左上切角/石青占位等10项全部落地,文档每项标注✅状态+实现文件 +- [CL-20260416-1330] 交互体验全面升级:新增framer-motion动画引擎,7级排版体系+4级Surface层级+网格底纹科技感,萤火粒子/数据流光/水墨晕染/投石入水/云雾凝聚/萤火点亮等微交互,用户/助手消息视觉拉开差距,统一Loading/空态/Error状态,全组件hover浮起+侧栏竖线+呼吸脉冲,新增UX-STYLE-GUIDE.md文档 +- [CL-20260416-1000] 集成Zero123++视角变换:新增transform_view工具+view_transform.py服务,单图输入生成6个固定视角图片(grid拆分),config.py新增VIEW_TRANSFORM_MODELS注册表,loop.py特殊处理视角变换SSE事件,进阶方案记入延期 +- [CL-20260416-0930] 设备绑定配置解耦:新增.cursor/local-env.json(gitignored)存放nodejs_path和shell,project-launcher Skill和hooks改为读取该文件;hooks增加bash版+dispatcher跨平台;新增local-env-check Rule自动探测;ENVIRONMENT.md去除硬编码 +- [CL-20260416-0900] 环境依赖全面修复+统一环境文档:安装Node.js v22.15.0+创建Python venv+安装全部前后端依赖;requirements.txt锁定精确版本;新增requirements-lock.txt和ENVIRONMENT.md;更新project-launcher Skill路径 - [CL-20260416-0830] 生图按模型预处理 prompt:新增 image_prompt_strategy(SDXL 默认负向词+---NEGATIVE---拆分,Flux 剥粘贴的 Negative 段,GPT/Gemini/IP-Adapter 规范化);GenerateResult 带 effective_prompt/negative_prompt;Replicate SDXL 传 negative_prompt;工具说明 SDXL 分隔符 - [CL-20260416-0810] 对话输入栏支持剪贴板粘贴图片为参考图:ChatInput根容器onPasteCapture提取image/*文件并走startUpload,placeholder/title提示 - [CL-20260416-0745] Gemini原生生图:延长读写超时+对RemoteProtocolError等可重试错误自动重试(默认3次指数退避),环境变量可微调VECTORENGINE_GEMINI_*_TIMEOUT @@ -44,20 +52,3 @@ - [CL-20260413-0540] 非 vision LLM + 参考图时禁止猜测风格,prompt 只描述内容不写画风,风格交由 IP-Adapter 处理 - [CL-20260413-0520] 多图生成改为单次 API 调用(利用模型原生批量参数),避免速率限制 + 修复空错误信息显示 - [CL-20260413-0500] 图片生成后在对话中显示实际使用的模型名称(如"由 Kolors IP-Adapter 生成"),便于模型溯源 -- [CL-20260413-0440] 修复 Kolors IP-Adapter 404 错误:社区模型需 `owner/model:version_hash` 格式,补全版本 hash 后生图成功 -- [CL-20260413-0430] 图片生成失败时将具体错误信息(Replicate API 报错等)透传到前端展示,新增 tool_error SSE 事件 -- [CL-20260413-0330] README 补充访问地址汇总(本机/局域网/外网)+ 穿透管理文档(安装/启停/排错) -- [CL-20260412-2345] 参考图上传独立化:选图即上传 + 环形进度条 + 成功/失败状态反馈 + 失败重试,后端新增独立上传端点 -- [CL-20260412-2315] 修复 Cloudflare Tunnel 跨域开发警告:next.config.ts 新增 allowedDevOrigins 通配符 -- [CL-20260412-2300] 集成 Kolors IP-Adapter 模型:参考图可直接用于风格生成,模型注册表新增通用参考图支持机制 -- [CL-20260412-2230] 用户消息气泡中显示参考图缩略图:ChatMessage 新增 refImageUrl,发送后在气泡中渲染参考图 -- [CL-20260412-2200] 修复 DeepSeek 不支持 image_url 参考图上传报错:新增 vision 能力检测,非 vision 模型走文字提示 -- [CL-20260412-2130] 修复图片生成失败时前端显示破碎图标:后端分离错误信息与有效 URL,前端过滤无效 URL 不渲染空图片 -- [CL-20260412-2100] 生图模型动态切换:Provider 抽象架构 + 前端模型选择器 + Replicate 多模型(flux-schnell/flux-dev/SDXL)按消息粒度切换 -- [CL-20260412-1800] 移动端适配 + Cloudflare Tunnel 内网穿透:响应式布局改造(9 个组件)+ 一键穿透脚本 + cloudflared 安装 -- [CL-20260412-1530] 修复 .env 配置加载时序问题:load_dotenv 前置 + override=True + config.py 改函数式懒读取 -- [CL-20260412-1500] 品牌重命名 Art Agent → EPEEKit + 后端 API/模型配置集中化到 .env + config.py -- [CL-20260412-0300] 完成交互原型全面重构:三栏布局 + 多会话标签系统 + Canvas 标注 + 资源库页面 -- [CL-20260411-1630] 安装 Node.js v24.14.1 + Python venv + 前后端依赖,后端启动验证通过 -- [CL-20260411-1600] 从零创建美术 Agent MVP 全部前后端代码(FastAPI + Next.js + Agent Loop + SSE) -- [CL-20260411-1500] 完成美术 Agent 产品决策(5 个核心问题)和技术选型(8 个维度)两份文档 diff --git a/.cursor/changelog/changelog-recent.md b/.cursor/changelog/changelog-recent.md index d15d384..0074918 100644 --- a/.cursor/changelog/changelog-recent.md +++ b/.cursor/changelog/changelog-recent.md @@ -3,6 +3,46 @@ 最近 ~10 次改动的摘要记录,按时间倒序排列。 当 Agent 检测到当前任务与近期改动相关时自动读取。 +### [CL-20260417-1510] 2026-04-17 — Cloudflare 穿透脚本兼容 PS5.1 + 本地 cloudflared +- **tags**: 内网穿透, cloudflared, PowerShell, start-tunnel, env +- **affected_files**: art-agent/start-tunnel.ps1, .gitignore +- **summary**: Windows PowerShell 5.1 在 UTF-8 无 BOM 下解析含中文的 `@"` here-string 会报错,导致穿透脚本无法运行。改为用 `WriteAllLines` 写入 `frontend/.env.local`,并将脚本保存为 UTF-8 BOM。脚本启动时若存在 `art-agent/cloudflared.exe` 则自动加入 PATH 优先使用。根目录 `.gitignore` 忽略本地下载的 `cloudflared.exe`(体积大)。 + +### [CL-20260417-1500] 2026-04-17 — 按 INFOLAYER.md 实现完整前端信息架构(Phase 1+ 占位骨架) +- **tags**: 前端, UI骨架, INFOLAYER, 多项目, 风格库, 角色库, 训练中心, 项目设置, TaskCard, 候选评估, 占位数据 +- **affected_files**: types.ts, placeholder-store.ts, app-context.tsx, globals.css, app/page.tsx, app/gallery/page.tsx, app/styles/*, app/characters/*, app/training/*, app/settings/page.tsx, top-nav.tsx, project-switcher.tsx, project-card.tsx, session-quick-picks.tsx, task-card.tsx, advanced-controls.tsx, candidate-panel.tsx, sidebar.tsx +- **summary**: 一次性铺开 INFOLAYER.md Phase 1–3 完整 UI 骨架:新增 /styles /characters /training /settings 四大模块(列表+详情+创建流程),工作台加入风格/角色/规格快选条、高级控制抽屉、候选评估面板、Task Card 组件,资源库加二级过滤(资产类型/风格/角色)+ 语义搜索切换;TopNav 加项目切换器和新导航项;Sidebar 加项目信息卡。所有 Phase 1+ 数据通过新建的 placeholder-store.ts + localStorage 占位持久化(首次注入示例),AppContext 扩充相关字段。所有真实后端行为均为 `alert("(占位)...")`,UI 上以 phase-chip(P1/P2/P3)明示阶段。搜索"(占位)"可快速定位待接入点。 + +### [CL-20260416-1600] 2026-04-16 — 视觉风格从暗绿底全面转为白底青绿(绢本山水) +- **tags**: 前端, 视觉风格, 色彩体系, CSS变量, 白底, 千里江山图 +- **affected_files**: globals.css, page.tsx, login/page.tsx, gallery/page.tsx, chat-messages.tsx, chat-input.tsx, top-nav.tsx, sidebar.tsx, session-list.tsx, profile-modal.tsx, image-detail-panel.tsx, ambient-particles.tsx, VISUAL-STYLE-GUIDE.md +- **summary**: 背景从墨绿黑(#0C1210)改为绢本白(#FAFAF7),文字改为墨黑(#1A1A1A),accent色加深(#4DB8A4→#2E8B7A)适配白底,所有面板从深色毛玻璃改为白色毛玻璃,13个文件中硬编码的旧rgba全部替换,云雾/粒子透明度降低,VISUAL-STYLE-GUIDE.md同步重写。设计理念:千里江山图是青绿设色于绢本白底,白底留白才能让青绿有呼吸感。 + +### [CL-20260416-1500] 2026-04-16 — UX 风格指南实现度补全(10 项未实现特性 + 文档状态标注) +- **tags**: 前端, UX补全, 动效, framer-motion, 微交互, 文档 +- **affected_files**: chat-input.tsx, chat-messages.tsx, image-grid.tsx, motion-presets.ts, page.tsx, login/page.tsx, gallery/page.tsx, globals.css, app-context.tsx, sidebar.tsx, profile-modal.tsx, UX-STYLE-GUIDE.md +- **summary**: 逐项分析 UX-STYLE-GUIDE.md 中所有特性的实现度(原 ~65%),补全 10 项未实现特性:涟漪接入发送按钮、sendBounce 弹缩、会话切换滑入滑出动画、Error 飘散消失+自动清除、回到底部水滴形、全屏云雾凝聚加载、AI 回复逐行 opacity 阶梯、typo-h1/h2/strong 实际使用、用户消息左上切角、图片占位石青渐变呼吸脉冲。文档更新为每项标注 ✅ 状态 + 实现文件路径。 + +### [CL-20260416-1330] 2026-04-16 — 交互体验全面升级:信息层级 + 科技质感 + 微交互趣味性 +- **tags**: 前端, UX升级, 动效, framer-motion, 排版体系, 微交互, 视觉层级, 科技感 +- **affected_files**: globals.css, page.tsx, chat-messages.tsx, chat-input.tsx, image-grid.tsx, session-list.tsx, sidebar.tsx, top-nav.tsx, image-detail-panel.tsx, profile-modal.tsx, model-selector.tsx, login/page.tsx, gallery/page.tsx, UX-STYLE-GUIDE.md, motion-presets.ts, ambient-particles.tsx, data-stream.tsx, ripple-effect.tsx, firefly-burst.tsx +- **summary**: 四阶段全面升级。Phase 1:globals.css 新增 7 级排版体系(Display 渐变→Micro 大写)+ 4 级 Surface 层级 + 点阵网格底纹 + 水波纹/shimmer/涟漪等CSS动画。Phase 2:新建环境萤火粒子组件(纯CSS)+ 数据流光组件(AI 活动时输入栏上方),用户/助手消息视觉差异(渐变背景+切角 vs glass-panel+左侧竖线)。Phase 3:引入 framer-motion,统一动画参数 motion-presets.ts;发送按钮投石入水(spring 弹缩+呼吸脉冲)、输入框水面感应(焦点扩展线+亮度随输入增强)、AI 回复云雾凝聚(blur 消散入场)、收藏萤火点亮(金色粒子爆发)、图片水墨晕染(clip-path 圆形揭示)。Phase 4:统一 Loading 水波纹 + shimmer 骨架屏 + 错误赭石红震动 + 空态 Display 渐变欢迎语 + stagger 入场,滚到底部按钮改圆形 spring 弹入,侧栏会话左侧竖线 hover,全组件 hover 浮起。新增 UX-STYLE-GUIDE.md 交互体验文档。 + +### [CL-20260416-1000] 2026-04-16 — 集成 Zero123++ 视角变换工具(单图→6视角) +- **tags**: 视角变换, Zero123++, Novel View Synthesis, 新工具, Replicate +- **affected_files**: config.py, tools.py, loop.py, view_transform.py +- **summary**: 新增 `transform_view` 工具集成 Zero123++(Replicate),输入一张图片生成 6 个固定视角图片。独立服务模块 `view_transform.py` 处理 Replicate 调用 + grid 拼接图 Pillow 拆分。config.py 新增 `VIEW_TRANSFORM_MODELS` 注册表。loop.py 对视角变换结果做特殊 SSE 事件处理。进阶方案(3D重建+ControlNet)记入延期方案。 + +### [CL-20260416-0930] 2026-04-16 — 设备绑定配置与 Skill/Hooks 解耦(local-env.json) +- **tags**: 基础设施, local-env, 跨设备, hooks, project-launcher, Skill +- **affected_files**: local-env.json, local-env.example.json, hooks.json, run-hook.ps1, session-init.sh, check-changelog.sh, local-env-check.mdc, project-launcher/SKILL.md, .gitignore, ENVIRONMENT.md +- **summary**: 新增 `.cursor/local-env.json`(gitignored)存放设备绑定配置(nodejs_path、shell),project-launcher Skill 和 hooks 均改为从中读取。hooks 增加 bash 版脚本 + PowerShell dispatcher 支持跨平台。新增 `local-env-check` Rule 在会话开始时自动探测生成。ENVIRONMENT.md 去除平台硬编码改为通用说明。 + +### [CL-20260416-0900] 2026-04-16 — 环境依赖全面修复 + 统一环境文档 +- **tags**: 环境, 依赖, Node.js, Python, venv, npm, requirements, ENVIRONMENT +- **affected_files**: requirements.txt, requirements-lock.txt, ENVIRONMENT.md, project-launcher/SKILL.md +- **summary**: 排查缺失依赖(venv/node_modules 均不存在),安装 Node.js v22.15.0 到 %LOCALAPPDATA%\nodejs\,创建后端 venv 并安装全部依赖(14 个直接 + 47 个间接),npm install 前端依赖(46 个包)。requirements.txt 锁定精确版本,新增 requirements-lock.txt 和 ENVIRONMENT.md 统一环境文档。project-launcher Skill Node.js 路径已同步更新。 + ### [CL-20260416-0830] 2026-04-16 — 生图按模型预处理 prompt - **tags**: 后端, 生图, prompt, SDXL, Flux, Replicate, image_gen, Agent工具 - **affected_files**: image_prompt_strategy.py, image_gen.py, tools.py diff --git a/.cursor/deferred/registry.md b/.cursor/deferred/registry.md index e0b31cb..2a51222 100644 --- a/.cursor/deferred/registry.md +++ b/.cursor/deferred/registry.md @@ -147,6 +147,44 @@ - **chosen_alternative**: 当前资源库使用手动标签 + Prompt 文本搜索 - **deferred_reason**: 当前阶段资源量少,手动管理足够;等资源积累到一定量级后再引入自动化 +### [advanced-view-transform] 进阶视角变换 Pipeline(3D 重建 + ControlNet 风格还原) +- **status**: deferred +- **tags**: 视角变换, 3D重建, ControlNet, Zero123++, TripoSR, InstantMesh, Novel View Synthesis, 手绘风格, IP-Adapter +- **recorded**: 2026-04-16 +- **source_chat**: [视角变换方案讨论与 Zero123++ 集成](view-transform-zero123plus) +- **prerequisite**: Zero123++ 基础验证完成、确认视角变换是高频需求后 +- **related_files**: + - art-agent/backend/app/services/view_transform.py + - art-agent/backend/app/config.py + - art-agent/backend/app/agent/tools.py +- **context**: | + 当前已集成 Zero123++(固定 6 视角输出),但存在两个局限: + 1. 视角固定(6 个预设角度),无法自定义任意角度 + 2. 手绘风格保持度不够——Zero123++ 更擅长写实/3D 渲染风格 + + 进阶方案分三个层级: + + A. 更强的多视角生成模型: + - Era3D:高质量多视角一致性,建筑类友好 + - SyncDreamer:同步多视角生成,一致性好 + - Wonder3D:多视角 + 法线图,精确视角控制 + + B. ControlNet 引导的视角变换: + - 用 Depth Anything V2 从原图提取深度图 + - 用 ControlNet(depth/normal 模式)+ 目标视角描述重新生成 + - 通过 IP-Adapter 注入原图风格保持手绘风格 + + C. 最佳质量方案(多步组合 Pipeline): + 原图 → TripoSR/InstantMesh 3D 重建 → 旋转到任意目标角度渲染灰度/线稿 + → ControlNet(canny/depth) + IP-Adapter(原图风格) → 手绘风格新视角图 + 这是质量最高的方案:视角精准 + 风格保真 + + 实施依赖: + - 方案 B/C 需要 ComfyUI 环境(与 hf-inference-endpoints 延期方案关联) + - 方案 C 的 TripoSR/InstantMesh 可先在 Replicate 上验证 +- **chosen_alternative**: 当前使用 Zero123++ 固定 6 视角输出(基础验证阶段) +- **deferred_reason**: 先用 Zero123++ 验证视角变换的实际需求频率和用户反馈,再投入进阶方案 + --- ## Completed / Cancelled Items diff --git a/.cursor/distillery/golden-rules.md b/.cursor/distillery/golden-rules.md new file mode 100644 index 0000000..9b0d868 --- /dev/null +++ b/.cursor/distillery/golden-rules.md @@ -0,0 +1,5 @@ +# Golden Rules + +经过实践验证(权重 >= 5)的精炼认知,每次新会话自动注入。 + + diff --git a/.cursor/distillery/insights.md b/.cursor/distillery/insights.md new file mode 100644 index 0000000..aaeb59c --- /dev/null +++ b/.cursor/distillery/insights.md @@ -0,0 +1,8 @@ +# Problem Distillery — Insights + +从已解决的顽固问题中蒸馏出的精炼方法论。 + + + +--- +last_distill_date: 2026-04-16 diff --git a/.cursor/distillery/problems.md b/.cursor/distillery/problems.md new file mode 100644 index 0000000..c27e8f4 --- /dev/null +++ b/.cursor/distillery/problems.md @@ -0,0 +1,5 @@ +# Problem Distillery — Problems + +反复出现的顽固问题追踪记录,按时间倒序排列。 + + diff --git a/.cursor/hooks.json b/.cursor/hooks.json index f3c7ae3..b289cdb 100644 --- a/.cursor/hooks.json +++ b/.cursor/hooks.json @@ -3,13 +3,13 @@ "hooks": { "sessionStart": [ { - "command": "powershell -ExecutionPolicy Bypass -File .cursor/hooks/session-init.ps1", + "command": "powershell -ExecutionPolicy Bypass -File .cursor/hooks/run-hook.ps1 session-init", "timeout": 5 } ], "stop": [ { - "command": "powershell -ExecutionPolicy Bypass -File .cursor/hooks/check-changelog.ps1", + "command": "powershell -ExecutionPolicy Bypass -File .cursor/hooks/run-hook.ps1 check-changelog", "timeout": 10, "loop_limit": 1 } diff --git a/.cursor/hooks/README.md b/.cursor/hooks/README.md new file mode 100644 index 0000000..51f3c71 --- /dev/null +++ b/.cursor/hooks/README.md @@ -0,0 +1,38 @@ +# Cursor Hooks 说明 + +## 结构 + +``` +hooks/ +├── run-hook.ps1 # Windows dispatcher(读取 local-env.json 后分发) +├── session-init.ps1 # sessionStart hook(PowerShell 版) +├── session-init.sh # sessionStart hook(bash 版) +├── check-changelog.ps1 # stop hook(PowerShell 版) +├── check-changelog.sh # stop hook(bash 版) +└── README.md # 本文件 +``` + +## 跨平台适配 + +`hooks.json` 中的 `command` 字段是**平台绑定的**——Windows 用 `powershell`,macOS/Linux 用 `bash`。 + +### Windows(当前) + +hooks.json 使用 `powershell ... run-hook.ps1` 作为入口,dispatcher 根据 +`.cursor/local-env.json` 的 `shell` 字段决定执行 `.ps1` 还是 `.sh` 脚本。 + +### 迁移到 macOS / Linux + +将 hooks.json 的 command 改为直接调用 `.sh` 脚本: + +```json +{ + "version": 1, + "hooks": { + "sessionStart": [{ "command": "bash .cursor/hooks/session-init.sh", "timeout": 5 }], + "stop": [{ "command": "bash .cursor/hooks/check-changelog.sh", "timeout": 10, "loop_limit": 1 }] + } +} +``` + +同时更新 `.cursor/local-env.json` 的 `shell` 字段为 `bash` 或 `zsh`。 diff --git a/.cursor/hooks/check-changelog.sh b/.cursor/hooks/check-changelog.sh new file mode 100644 index 0000000..5265952 --- /dev/null +++ b/.cursor/hooks/check-changelog.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Changelog sync guard — stop hook (bash 版) + +input=$(cat) + +# === 豁免检查 1:环境变量跳过标志 === +if [ -n "$CURSOR_SKIP_CHANGELOG" ]; then + echo '{}'; exit 0 +fi + +# === 豁免检查 2:从 stdin 解析 composer_mode === +mode=$(echo "$input" | python3 -c "import sys,json; print(json.load(sys.stdin).get('composer_mode',''))" 2>/dev/null || echo "") +if [ -n "$mode" ] && [ "$mode" != "agent" ]; then + echo '{}'; exit 0 +fi + +# === 豁免检查 3:从 stdin 文本匹配 debug 上下文关键词 === +if echo "$input" | grep -qiE '"mode"\s*:\s*"debug"|debug[\s_-]?mode|Debug Mode'; then + echo '{}'; exit 0 +fi + +changelog=".cursor/changelog/changelog-headlines.md" +srcDir="art-agent" + +# === 豁免检查 4:changelog 文件不存在 === +if [ ! -f "$changelog" ]; then + echo '{}'; exit 0 +fi + +clMtime=$(stat -c %Y "$changelog" 2>/dev/null || stat -f %m "$changelog" 2>/dev/null) + +# === 豁免检查 5:ack 标记文件足够新 === +ackFile=".cursor/changelog/.changelog-ack" +if [ -f "$ackFile" ]; then + ackMtime=$(stat -c %Y "$ackFile" 2>/dev/null || stat -f %m "$ackFile" 2>/dev/null) + if [ "$ackMtime" -ge "$clMtime" ] 2>/dev/null; then + echo '{}'; exit 0 + fi +fi + +# === 豁免检查 6:stdin 中无文件编辑证据 === +if ! echo "$input" | grep -qE 'StrReplace|Write\s*tool|edit_file|file_write|write_to_file|Created file|Modified file|Wrote contents|"tool"\s*:\s*"(str_replace|write|edit)"'; then + echo '{}'; exit 0 +fi + +# === 核心检查:是否有源文件比 changelog 更新 === +newerFile=$(find "$srcDir" -type f \( -name "*.py" -o -name "*.tsx" -o -name "*.ts" -o -name "*.css" \) \ + ! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/__pycache__/*" ! -path "*/venv/*" \ + -newer "$changelog" -print -quit 2>/dev/null) + +if [ -n "$newerFile" ]; then + fname=$(basename "$newerFile") + msg="[Hook] Source file updated (e.g. $fname) but changelog not synced. Run dev-changelog Skill operation A NOW to write all three changelog layers." + echo "{\"followup_message\":\"$msg\"}" +else + echo '{}' +fi +exit 0 diff --git a/.cursor/hooks/run-hook.ps1 b/.cursor/hooks/run-hook.ps1 new file mode 100644 index 0000000..a6b726b --- /dev/null +++ b/.cursor/hooks/run-hook.ps1 @@ -0,0 +1,35 @@ +# run-hook.ps1 — 通用 hook dispatcher +# 从 .cursor/local-env.json 读取 shell 类型,决定执行 .ps1 还是 .sh 脚本 +# 用法:powershell -ExecutionPolicy Bypass -File .cursor/hooks/run-hook.ps1 +# 示例:run-hook.ps1 session-init → 执行 session-init.ps1 或 session-init.sh + +param([string]$HookName) + +$localEnvPath = ".cursor\local-env.json" +$shell = "powershell" + +if (Test-Path $localEnvPath) { + try { + $config = Get-Content $localEnvPath -Raw | ConvertFrom-Json + if ($config.shell) { $shell = $config.shell } + } catch {} +} + +$hookDir = ".cursor\hooks" +$input = [Console]::In.ReadToEnd() + +if ($shell -eq "powershell") { + $scriptPath = Join-Path $hookDir "$HookName.ps1" + if (Test-Path $scriptPath) { + $input | powershell -ExecutionPolicy Bypass -File $scriptPath + } else { + Write-Output '{}' + } +} else { + $scriptPath = Join-Path $hookDir "$HookName.sh" + if (Test-Path $scriptPath) { + $input | bash $scriptPath + } else { + Write-Output '{}' + } +} diff --git a/.cursor/hooks/session-init.sh b/.cursor/hooks/session-init.sh new file mode 100644 index 0000000..34bc498 --- /dev/null +++ b/.cursor/hooks/session-init.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# session-init.sh — 会话启动时检测 composer_mode,非 agent 模式设置跳过标志 + +input=$(cat) + +mode=$(echo "$input" | python3 -c "import sys,json; print(json.load(sys.stdin).get('composer_mode',''))" 2>/dev/null || echo "") + +rm -f ".cursor/changelog/.changelog-ack" + +if [ -n "$mode" ] && [ "$mode" != "agent" ]; then + echo "{\"env\":{\"CURSOR_SKIP_CHANGELOG\":\"1\",\"CURSOR_COMPOSER_MODE\":\"$mode\"}}" +else + echo "{}" +fi +exit 0 diff --git a/.cursor/local-env.example.json b/.cursor/local-env.example.json new file mode 100644 index 0000000..963a16f --- /dev/null +++ b/.cursor/local-env.example.json @@ -0,0 +1,5 @@ +{ + "_comment": "设备绑定的本地环境配置。复制为 local-env.json 并修改为本机实际值。", + "nodejs_path": "C:\\Users\\\\AppData\\Local\\nodejs", + "shell": "powershell" +} diff --git a/.cursor/rules/common/distillery-recall.mdc b/.cursor/rules/common/distillery-recall.mdc new file mode 100644 index 0000000..f47815f --- /dev/null +++ b/.cursor/rules/common/distillery-recall.mdc @@ -0,0 +1,44 @@ +## Problem Distillery 上下文注入 + +### Golden Rules 注入 + +每次会话处理用户第一个任务前,执行以下操作: + +1. 读取 `.cursor/distillery/golden-rules.md`(不存在则跳过) +2. 如文件存在且有实质条目(不仅是标题),将全部条目作为背景知识注入上下文 +3. 这些是经过实践反复验证的精炼认知,Agent 在分析和解决问题时应自然参考 +4. 不需要在回复中显式提及"根据 Golden Rule" + +### 蒸馏提醒检查 + +每次会话处理用户第一个任务前,额外检查: + +1. 读取 `.cursor/distillery/insights.md`(不存在则跳过) +2. 检查文件尾部的 `last_distill_date` 字段 +3. 如果距今超过 7 天,读取 `.cursor/distillery/problems.md` +4. 统计 `status: resolved` 且无 `distilled:` 标记的条目数量 +5. 如有未蒸馏的已解决条目,提醒用户: + > 你有 N 个已解决的顽固问题尚未总结,要花几分钟蒸馏一下吗? +6. 每次会话最多提醒一次 + +### 顽固问题检测 + +Agent 在整个对话过程中应保持对以下信号的被动感知: + +1. 用户表达问题未解决:"还有问题"、"没解决"、"还是一样"、"又出现了"、"不行"、"没用" +2. Agent 自身意识到同一问题已尝试 2 次以上仍未解决 +3. pitfall-journal 中已有记录的问题再次出现 + +检测到上述信号时,读取 `problem-distillery` Skill 并执行其操作 A。 + +### 验证触发 + +当操作 A 触发时(追踪新的或再次出现的顽固问题),如果 `insights.md` 非空, +还应执行操作 D 的被动验证流程——匹配是否有相关的已蒸馏经验可供参考。 + +### 注意 + +- Golden Rules 注入是极轻量操作(预期 < 30 行),每次会话都执行 +- 蒸馏提醒按需触发,只在条件满足时提醒 +- problems.md 的详细记录**不主动读取**,仅在操作 A/B/C 时按需读取 +- 本 Rule 只负责触发和注入,具体操作流程由 `problem-distillery` Skill 定义 diff --git a/.cursor/rules/project/local-env-check.mdc b/.cursor/rules/project/local-env-check.mdc new file mode 100644 index 0000000..b6af07d --- /dev/null +++ b/.cursor/rules/project/local-env-check.mdc @@ -0,0 +1,47 @@ +--- +description: 检查本地环境配置文件 .cursor/local-env.json 是否存在,不存在时自动探测并生成 +globs: +alwaysApply: true +--- + +## 本地环境配置自动初始化 + +每次会话处理用户第一个任务前,检查 `.cursor/local-env.json` 是否存在: + +### 文件存在时 + +读取并验证 JSON 格式正确、包含 `nodejs_path` 和 `shell` 两个字段。 +字段缺失时补充探测(见下方流程),不覆盖已有字段。 + +### 文件不存在时 + +执行自动探测流程,生成 `.cursor/local-env.json`: + +``` +1. 探测 shell 类型: + - Windows($env:OS 包含 "Windows")→ "powershell" + - macOS/Linux → 读取 $SHELL 环境变量末段(bash/zsh/fish 等) + +2. 探测 Node.js 路径: + - Windows:运行 `where.exe node`,排除 Cursor 内置的 node + (路径含 "cursor" 的不算),取第一个有效结果的父目录 + - macOS/Linux:运行 `which node`,取父目录 + - 找不到 → 设为 null + +3. 写入 .cursor/local-env.json: + { + "nodejs_path": "<探测到的路径或 null>", + "shell": "<探测到的 shell>" + } + +4. 简要告知用户: + > 已自动生成本地环境配置 `.cursor/local-env.json`, + > 检测到 Node.js 路径为 xxx,shell 为 xxx。如需调整请手动编辑该文件。 +``` + +### 注意事项 + +- `local-env.json` 已在 `.gitignore` 中,不会进入版本控制 +- 该文件存储**设备绑定**的配置,与 `.env`(存储密钥/API key)互补 +- 只在文件不存在或字段缺失时触发探测,不会每次会话都重新探测 +- 探测结果不一定准确(如多个 Node.js 版本并存),用户可随时手动修改 diff --git a/.cursor/skills/dev-changelog/SKILL.md b/.cursor/skills/dev-changelog/SKILL.md index 20f538f..2375bcd 100644 --- a/.cursor/skills/dev-changelog/SKILL.md +++ b/.cursor/skills/dev-changelog/SKILL.md @@ -83,7 +83,7 @@ Agent 完成了一个涉及**代码或配置文件实质性改动**的任务后 4. 写入三个文件(按以下顺序): a. 读取 changelog-full.md,在 "## 记录" 下方追加 L1 条目 - b. 读取 changelog-recent.md,在顶部插入 L2 条目,如超过 10 条则移除最旧的 + b. 读取 changelog-recent.md,在顶部插入 L2 条目,如超过 20 条则移除最旧的(与下方「L2 窗口」一致) c. 读取 changelog-headlines.md,在顶部插入 L3 条目,如超过 50 条则移除最旧的 5. 在回复末尾附一行提示:"[已记录到开发日志]" @@ -247,3 +247,5 @@ Agent 完成了一个涉及**代码或配置文件实质性改动**的任务后 ### 已知必要检查 1. **大任务收尾遗漏风险** — 当单次任务涉及 5+ 个文件改动时,Agent 容易在输出总结回复后遗漏被动写入流程。应在生成最终回复前,先执行 `changelog-recall.mdc` 中的"任务完成 Checklist",确认三层日志已写入后再输出回复。绝不能"先回复再补写"。 + +2. **L3/L2 滚动窗口必须落地** — 操作 A 第 4 步不是「插入即结束」:写入后必须数清条目(L3 为以 `- [` 开头的列表行,L2 为 `### [CL-` 标题行)。L3 超过 50 条、L2 超过 20 条时,必须从**文件底部(最旧)**整段删除条目直至恰好满足上限;禁止长期只追加不修剪。若发现 `changelog-headlines.md` 列表行多于 50,说明上次写入未执行本项,本次补修剪并自检。 diff --git a/.cursor/skills/epee-orchestrator/registry.md b/.cursor/skills/epee-orchestrator/registry.md index 4934ab2..3753334 100644 --- a/.cursor/skills/epee-orchestrator/registry.md +++ b/.cursor/skills/epee-orchestrator/registry.md @@ -39,7 +39,7 @@ - **触发场景**: "开发日志"、"changelog"、"最近改了什么"、"回顾改动"、"查看开发记录"、代码改动后自动写入 - **输出**: .cursor/changelog/ 下的 changelog-full.md、changelog-recent.md、changelog-headlines.md - **路径**: .cursor/skills/dev-changelog/SKILL.md -- **备注**: 写入由 changelog-recall Rule 的"任务完成 Checklist"触发(原被动写入提醒已升级为强制 checklist);L3 概要每次会话自动注入上下文;stop Hook 双保险兜底 +- **备注**: 写入由 changelog-recall Rule 的"任务完成 Checklist"触发(原被动写入提醒已升级为强制 checklist);L3 概要每次会话自动注入上下文;stop Hook 双保险兜底;写入后须维护 L3≤50 / L2≤20 滚动窗口(见 Skill 自迭代检查项) ### pitfall-journal - **类型**: 基础设施 @@ -49,10 +49,18 @@ - **路径**: .cursor/skills/pitfall-journal/SKILL.md - **备注**: 与 dev-changelog 互补——changelog 记事实,pitfall 记经验;由 pitfall-recall Rule 触发自动检索 +### problem-distillery +- **类型**: 基础设施 +- **能力**: 追踪反复出现的顽固问题,记录解决过程和弯路,定期蒸馏精炼方法论,经实践验证后自动升级为新会话常规注入 +- **触发场景**: "还有问题"、"没解决"、"还是一样"、"又出现了"、"试了好多次"、"反复出现"、Agent 意识到同一问题尝试 2 次以上未解决、"蒸馏"、"总结经验" +- **输出**: .cursor/distillery/ 下的 problems.md、insights.md、golden-rules.md +- **路径**: .cursor/skills/problem-distillery/SKILL.md +- **备注**: 与 pitfall-journal 互补——pitfall 是事后快照,distillery 是过程追踪 + 知识提炼;golden-rules 由 distillery-recall Rule 每次会话注入 + ### project-launcher - **类型**: 项目级 - **能力**: 一键启动 Art Agent 全部服务(Ollama + 后端 FastAPI + 前端 Next.js),自动检测已运行的服务并跳过 - **触发场景**: "启动项目"、"运行项目"、"跑起来"、"打开前端和后端"、"启动服务"、"start"、"launch"、"run dev"、"启动前端"、"启动后端" - **输出**: Ollama 后台服务 + 两个独立的终端窗口(前端 Next.js + 后端 FastAPI) - **路径**: .cursor/skills/project-launcher/SKILL.md -- **备注**: 项目专属 Skill;Ollama 必须先于后端启动(Mem0 embedding 依赖);前后端窗口必须可见 +- **备注**: 项目专属 Skill;启动前必须先读取 `.cursor/local-env.json` 获取 nodejs_path 和 shell;Ollama 必须先于后端启动(Mem0 embedding 依赖);前后端窗口必须可见 diff --git a/.cursor/skills/problem-distillery/SKILL.md b/.cursor/skills/problem-distillery/SKILL.md new file mode 100644 index 0000000..5dfa12e --- /dev/null +++ b/.cursor/skills/problem-distillery/SKILL.md @@ -0,0 +1,232 @@ +--- +name: problem-distillery +description: >- + 从顽固问题中蒸馏方法论的渐进式知识系统。追踪反复出现且未被彻底解决的问题, + 记录解决过程和弯路,定期提炼精炼认知,经实践验证后自动升级为常规注入。 + 当用户表达"还有问题"、"没解决"、"还是一样"、"又出现了"等语义, + 或 Agent 自身意识到同一问题已尝试 2 次以上仍未解决时触发。 +--- + +# Problem Distillery + +从反复出现的顽固问题中,经过"追踪 → 解决 → 蒸馏 → 验证 → 注入"的完整生命周期, +蒸馏出经过实践验证的精炼认知和方法论。 + +与其他系统的区别: +- **pitfall-journal**:互补。pitfall 是事后快照(debug 完记一笔),distillery 是过程追踪 + 知识提炼 +- **dev-changelog**:不重叠。changelog 记事实改动,distillery 记问题解决过程和认知沉淀 + +## 数据文件 + +``` +.cursor/distillery/ + problems.md -- 问题记录(open + resolved),操作 A/B 写入 + insights.md -- 蒸馏后的精炼方法论,操作 C 写入 + golden-rules.md -- 权重达标后升级的条目,操作 D 管理,新会话自动注入 +``` + +## 条目格式 + +### problems.md 条目 + +```markdown +### [PD-YYYYMMDD-HHMM] 一句话标题 +- **status**: open | resolved +- **fingerprint**: 关键特征词列表(用于匹配同一问题) +- **category**: tech | workflow | decision | other +- **first_seen**: YYYY-MM-DD HH:MM +- **attempts**: + 1. [YYYY-MM-DD HH:MM] 尝试了什么 → 结果如何 + 2. [YYYY-MM-DD HH:MM] 又尝试了什么 → 结果如何 +- **resolution**: (解决后填写)最终解决方案 +- **dead_ends**: (解决后填写)走过的弯路及其失败原因 +- **spark**: (解决后填写)一句话启发——这个问题教会了什么 +- **distilled**: (蒸馏后填写)IN-xxx +``` + +### insights.md 条目 + +```markdown +### [IN-YYYYMMDD-NN] 一句话方法论 +- **source_problems**: [PD-xxx, PD-yyy] +- **weight**: 0 +- **promoted**: false | true | demoted +- **content**: 2-3 句精炼认知 +``` + +文件尾部保留元数据: + +```markdown +--- +last_distill_date: YYYY-MM-DD +``` + +### golden-rules.md + +```markdown +# Golden Rules + +经过实践验证(权重 >= 5)的精炼认知,每次新会话自动注入。 + +1. [GR-001] 一句话认知(来源: IN-xxx, 累计验证 N 次) +2. [GR-002] ... +``` + +硬上限 15 条。超出时按权重排序保留 top-15,被淘汰的条目降回 insights.md(promoted 改为 demoted)。 + +## 操作 A:追踪记录(问题进行中) + +### 触发条件 + +以下**任一**场景触发: + +1. **用户明确表达**问题未解决: + - "还有问题"、"问题没有解决"、"还是一样"、"又出现了" + - "试了好多次了"、"这个问题反复出现"、"不行"、"没用" + - 以及其他表达"问题反复发生、Agent 没有完全解决"的语义 +2. **Agent 自身意识到**同一问题已经尝试了 2 次以上仍未解决 + +### 适用范围 + +不限于技术问题——工作流设计、产品决策等各类反复纠结的问题均适用。 + +### 流程 + +``` +1. 提取当前问题的指纹: + - 技术问题:错误信息关键词、涉及文件/模块、症状描述 + - 非技术问题:核心矛盾点、涉及领域、反复出现的决策困境 + +2. 读取 problems.md,用指纹匹配 status=open 的条目 + +3. 匹配到已有条目 → 在 attempts 中追加本次尝试记录 + +4. 无匹配 → 创建新条目: + - 生成 ID:PD-YYYYMMDD-HHMM + - status: open + - 记录首次尝试 + +5. 告知用户:"已开始追踪这个问题 [PD-xxx]"(首次) + 或 "已更新追踪记录 [PD-xxx],这是第 N 次尝试"(后续) +``` + +### 与 pitfall-journal 的衔接 + +当 pitfall-journal 中某个条目的同一问题反复出现(用户再次报告相同症状), +Agent 应意识到这已超出 pitfall 的"一次性记录"范畴,主动触发操作 A 建立追踪。 + +## 操作 B:解决记录 + +### 触发条件 + +存在 status=open 的追踪条目,且满足以下**任一**: +- 用户确认问题已解决:"好了"、"解决了"、"终于可以了" +- Agent 判断问题已解决(测试通过、错误消失等) + +### 流程 + +``` +1. 填写 resolution:最终的解决方案 +2. 填写 dead_ends:走过的弯路及其失败原因(从 attempts 中归纳) +3. 填写 spark:一句话启发——这个问题教会了什么 +4. status 改为 resolved +5. 回复末尾附 [顽固问题已解决并记录] +``` + +### 注意 + +- 如果问题在当前会话中从发现到解决只花了 1-2 次尝试,不需要走 distillery 流程 + (那是 pitfall-journal 的范畴) +- 只有经历了"反复尝试"的问题才值得 distillery 追踪 + +## 操作 C:定期蒸馏 + +### 触发条件 + +由 `distillery-recall` Rule 在每次会话开头检查: +- 读取 insights.md 尾部的 `last_distill_date` +- 如果距今超过 7 天,且 problems.md 中有未标记 `distilled` 的 resolved 条目 +- 则提醒用户:"你有 N 个已解决的顽固问题尚未总结,要花几分钟蒸馏一下吗?" +- 每次会话最多提醒一次 + +### 流程(用户同意后) + +``` +1. 读取所有 status=resolved 且无 distilled 标记的条目 +2. Agent 分析这些问题的共性,提出归纳建议: + - 哪些问题有共同的根因模式? + - 能提炼出什么通用的认知或方法论? + - 建议的表述(2-3 句精炼认知) +3. 用户确认/修改后: + - 写入 insights.md(ID 格式:IN-YYYYMMDD-NN,NN 为当日序号) + - weight 初始为 0 + - promoted: false +4. 更新 insights.md 尾部的 last_distill_date +5. 在对应 problems.md 条目上标记 distilled: IN-xxx +``` + +## 操作 D:验证与升级 + +### 被动验证 + +当操作 A 触发时(遇到新的或再次出现的顽固问题),额外执行: + +``` +1. 读取 insights.md,对当前问题做语义匹配 +2. 如有相关条目,向用户展示: + "之前总结过一条相关经验 [IN-xxx]: [内容摘要],可能对当前问题有帮助。" +3. 在问题解决流程中,跟踪该经验是否发挥了作用: + - 用户确认"这个提示有用" + - 或 Agent 判断解决方案与该 insight 的方向一致 +4. 确认有用 → weight += 1,在 insights.md 中更新 +5. 确认无用 → 不扣分(weight 只增不减,避免偶然失误惩罚好经验) +``` + +### 升级为 Golden Rule + +``` +1. 当某条 insight 的 weight >= 5: + a. 检查 golden-rules.md 当前条目数 + b. 如 < 15 → 直接升级: + - 在 golden-rules.md 中追加条目(ID: GR-NNN,NNN 为递增序号) + - insights.md 中 promoted 改为 true + c. 如 = 15 → 比较权重: + - 新条目权重 > golden-rules.md 中最低权重条目 → 替换 + - 被替换的条目降回 insights.md(promoted 改为 demoted) + - 否则不升级 +2. 升级后告知用户: + "经验 [IN-xxx] 已累计验证 N 次,升级为 Golden Rule [GR-NNN],后续新会话将自动注入。" +``` + +### 新会话注入 + +由 `distillery-recall` Rule 负责: +- 每次会话开头读取 `golden-rules.md` +- 如果非空,将全部条目作为背景知识注入上下文 +- 注入方式与 `changelog-headlines` 同级别——轻量、不显式提及来源 + +## 容量与性能控制 + +| 文件 | 策略 | 阈值 | +|------|------|------| +| problems.md | resolved 且已 distilled 的条目超过 50 条时,归档到 problems-archive.md | 50 条 | +| insights.md | 无上限(条目本身是精炼的,每条 3-5 行) | — | +| golden-rules.md | 硬上限,按权重淘汰 | 15 条 | +| 新会话注入成本 | 只读 golden-rules.md(预期 < 30 行) | 极轻量 | + +## 与其他系统的协作 + +| 系统 | 关系 | 说明 | +|------|------|------| +| `pitfall-journal` | 上游来源 | pitfall 条目反复出现时,升级为 distillery 追踪 | +| `dev-changelog` | 不重叠 | changelog 记事实改动,distillery 记过程和认知 | +| `distillery-recall` Rule | 下游消费者 | 负责 golden-rules 注入和蒸馏提醒 | +| `epee-orchestrator` | 注册 | 在 registry.md 中注册本 Skill | + +## 自迭代日志 + +本节记录使用本 Skill 过程中发现的必要检查项。 + +### 已知必要检查 + +(暂无) diff --git a/.cursor/skills/project-launcher/SKILL.md b/.cursor/skills/project-launcher/SKILL.md index 5867fc2..8bcd03c 100644 --- a/.cursor/skills/project-launcher/SKILL.md +++ b/.cursor/skills/project-launcher/SKILL.md @@ -32,14 +32,33 @@ description: >- - 前端需要 Node.js >= 18 - **Ollama 必须在后端之前启动**:Mem0 记忆系统依赖 Ollama 提供本地 embedding 服务(`nomic-embed-text` 模型),端口 `11434` -## 环境 PATH 须知 +## 本地环境配置(local-env.json) -本机 Node.js 安装在 `C:\Program Files\nodejs\` 但**未加入系统 PATH**。 -新开的终端窗口默认找不到 `node` / `npm` 命令。 +设备绑定的路径和配置统一存放在 `.cursor/local-env.json`(已 gitignored),不硬编码到 Skill 中。 -**解决方式**:在启动前端的命令中,先将 Node.js 路径注入到当前会话的 `$env:PATH` 中。 +**启动前必须先读取该文件**,从中获取 `nodejs_path` 和 `shell` 字段。 -> 如果后续 Node.js 路径发生变化(如用户重新安装或使用 nvm),需要更新此处。 +| 字段 | 用途 | 示例值 | +|------|------|--------| +| `nodejs_path` | Node.js 安装目录(含 node/npm) | `C:\Users\xxx\AppData\Local\nodejs` | +| `shell` | 当前设备使用的 shell 类型 | `powershell` / `bash` / `zsh` | + +**文件不存在时的自动探测流程**: + +``` +1. 创建空的 JSON 对象 {} +2. 探测 Node.js 路径: + - 运行 `where.exe node`(Windows)或 `which node`(macOS/Linux) + - 取其父目录作为 nodejs_path + - 如果找不到,将 nodejs_path 设为 null,后续启动前端时提醒用户手动配置 +3. 探测 shell 类型: + - Windows 默认 "powershell" + - macOS/Linux 读取 $SHELL 环境变量,提取末尾(bash/zsh/fish 等) +4. 写入 .cursor/local-env.json +5. 告知用户已自动生成本地配置,可手动调整 +``` + +**Node.js 不在系统 PATH 时**:启动前端的命令中需要将 `nodejs_path` 注入到当前会话的 PATH。 ## 核心操作:启动服务 @@ -48,23 +67,25 @@ description: >- ``` 1. 确定项目根目录(workspace 根目录下的 art-agent/) -2. 确定操作系统和 Shell 类型(Windows / macOS / Linux) +2. 读取 .cursor/local-env.json: + - 存在 → 解析 nodejs_path 和 shell + - 不存在 → 执行自动探测流程(见上方),生成后再读取 3. 检查并启动 Ollama: - 检测 Ollama 是否已在运行(请求 http://localhost:11434/api/tags) - 未运行 → 启动 Ollama 服务,等待就绪 - 已运行 → 跳过 -4. 启动后端(在独立可见终端窗口中): - - Windows(PowerShell 或 CMD 均适用): - 使用 `Start-Process` 或 `start cmd` 打开新的终端窗口 - - macOS/Linux: - 使用对应的终端打开方式 +4. 根据 shell 字段选择对应的启动命令模板: + - powershell → 使用 PowerShell 命令 + - bash/zsh → 使用 macOS/Linux 命令 -5. 启动前端(在另一个独立可见终端窗口中): - - 同样在新的终端窗口中启动 +5. 启动后端(在独立可见终端窗口中) -6. 确认三个服务正在运行,告知用户访问地址 +6. 启动前端(在另一个独立可见终端窗口中): + - 如果 nodejs_path 不为 null,先注入到 PATH + +7. 确认三个服务正在运行,告知用户访问地址 ``` ### Ollama 启动(跨平台通用) @@ -87,44 +108,36 @@ try { > Ollama 启动后会常驻后台,不需要独立终端窗口。如果用户系统已将 Ollama 设为开机自启, > 则检测会直接通过,不会重复启动。 -### Windows 启动命令 +### 启动命令模板 **关键要求**:必须在**新的、可见的终端窗口**中启动,不能在 Cursor 内置终端后台运行。 -#### PowerShell 环境 +以下模板中的变量说明: +- `BACKEND_PATH` / `FRONTEND_PATH`:替换为实际绝对路径 +- `NODEJS_PATH`:从 `local-env.json` 的 `nodejs_path` 字段读取 + +#### shell = "powershell" 启动后端: ```powershell Start-Process powershell -ArgumentList '-NoExit', '-Command', "cd 'BACKEND_PATH'; .\venv\Scripts\Activate.ps1; uvicorn app.main:app --reload --host 0.0.0.0 --port 8000" -WindowStyle Normal ``` -启动前端(注意注入 Node.js PATH): +启动前端(注入从 local-env.json 读取的 NODEJS_PATH): ```powershell -Start-Process powershell -ArgumentList '-NoExit', '-Command', "& { `$env:PATH = 'C:\Program Files\nodejs;' + `$env:PATH; `$Host.UI.RawUI.WindowTitle = 'Art Agent Frontend'; cd 'FRONTEND_PATH'; npm run dev }" -WindowStyle Normal +Start-Process powershell -ArgumentList '-NoExit', '-Command', "& { `$env:PATH = 'NODEJS_PATH;' + `$env:PATH; `$Host.UI.RawUI.WindowTitle = 'Art Agent Frontend'; cd 'FRONTEND_PATH'; npm run dev }" -WindowStyle Normal ``` -#### CMD 环境 +#### shell = "bash" / "zsh"(macOS / Linux) 启动后端: -```cmd -start "Art Agent Backend" cmd /k "cd /d BACKEND_PATH && venv\Scripts\activate && uvicorn app.main:app --reload --host 0.0.0.0 --port 8000" -``` - -启动前端(注意注入 Node.js PATH): -```cmd -start "Art Agent Frontend" cmd /k "set PATH=C:\Program Files\nodejs;%PATH% && cd /d FRONTEND_PATH && npm run dev" -``` - -### macOS / Linux 启动命令 - -根据用户终端环境选择: - ```bash -# 后端 osascript -e 'tell application "Terminal" to do script "cd BACKEND_PATH && source venv/bin/activate && uvicorn app.main:app --reload --host 0.0.0.0 --port 8000"' +``` -# 前端 -osascript -e 'tell application "Terminal" to do script "cd FRONTEND_PATH && npm run dev"' +启动前端(NODEJS_PATH 为 null 时跳过 PATH 注入): +```bash +osascript -e 'tell application "Terminal" to do script "export PATH=NODEJS_PATH:$PATH && cd FRONTEND_PATH && npm run dev"' ``` ### 执行注意事项 @@ -156,5 +169,6 @@ osascript -e 'tell application "Terminal" to do script "cd FRONTEND_PATH && npm ### 已知必要检查 -1. **Node.js PATH 注入** — 本机 Node.js (`C:\Program Files\nodejs\`) 未加入系统 PATH,新开的终端窗口默认找不到 `npm`。启动前端时必须先将此路径注入到会话 PATH 中。 -2. **Ollama 必须先于后端启动** — Mem0 记忆系统依赖 Ollama 的 `nomic-embed-text` 模型做本地 embedding(端口 11434)。Ollama 未运行时后端能启动但对话会报 502 错误。启动流程必须在后端之前检测并启动 Ollama。 +1. **先读 local-env.json** — 启动前必须读取 `.cursor/local-env.json` 获取 `nodejs_path` 和 `shell`。文件不存在时执行自动探测并生成。绝不在 Skill 中硬编码设备相关的路径。 +2. **Node.js PATH 注入** — 如果 `nodejs_path` 不为 null,说明 Node.js 未在系统 PATH 中,启动前端时必须将该路径注入到会话 PATH。 +3. **Ollama 必须先于后端启动** — Mem0 记忆系统依赖 Ollama 的 `nomic-embed-text` 模型做本地 embedding(端口 11434)。Ollama 未运行时后端能启动但对话会报 502 错误。启动流程必须在后端之前检测并启动 Ollama。 diff --git a/.gitignore b/.gitignore index 7d85189..03a70d4 100644 --- a/.gitignore +++ b/.gitignore @@ -34,10 +34,15 @@ out/ art-agent/backend/uploads/ art-agent/backend/generated/ art-agent/backend/data/ +# 本地下载的 cloudflared CLI(体积大,见 README / start-tunnel.ps1) +art-agent/cloudflared.exe # ─── 工具二进制 ────────────────────────────────────────── *.exe +# ─── 本地环境配置(设备绑定,不入版本控制)────────────── +.cursor/local-env.json + # ─── IDE / 编辑器 ──────────────────────────────────────── .vscode/ .idea/ diff --git a/art-agent/ENVIRONMENT.md b/art-agent/ENVIRONMENT.md new file mode 100644 index 0000000..9a0d79c --- /dev/null +++ b/art-agent/ENVIRONMENT.md @@ -0,0 +1,206 @@ +# EPEEKit 开发环境说明 + +> 最后更新:2026-04-16 + +## 系统要求 + +| 项目 | 最低版本 | 备注 | +|------|---------|------| +| OS | Windows 10+ / macOS / Linux | 跨平台支持 | +| Python | 3.12+ | 用于后端 | +| Node.js | 22+ LTS | 用于前端 | +| npm | 10+ | 随 Node.js 安装 | +| Ollama | latest | Mem0 embedding 服务依赖,需启动 `nomic-embed-text` 模型 | + +## 本地环境配置(local-env.json) + +设备绑定的路径(如 Node.js 安装位置)**不硬编码在任何 Skill 或配置中**, +统一通过 `.cursor/local-env.json` 管理。 + +| 字段 | 说明 | 示例 | +|------|------|------| +| `nodejs_path` | Node.js 安装目录(含 node/npm),为 `null` 表示已在系统 PATH | `C:\Users\xxx\AppData\Local\nodejs` | +| `shell` | 当前设备的 shell 类型 | `powershell` / `bash` / `zsh` | + +- **首次使用**:Agent 会自动探测并生成,也可手动复制 `.cursor/local-env.example.json` 并修改 +- **已 gitignored**:不进入版本控制,每台设备独立维护 +- **模板参考**:`.cursor/local-env.example.json` + +### Node.js PATH 注意事项 + +如果 `nodejs_path` 不为 null,说明 Node.js 未在系统 PATH 中,启动前端前需注入: + +```powershell +# PowerShell(路径来自 local-env.json 的 nodejs_path) +$env:PATH = "你的nodejs_path;" + $env:PATH +``` + +```bash +# bash/zsh +export PATH="你的nodejs_path:$PATH" +``` + +## 项目结构 + +``` +EPEEAIKit/ +├── .cursor/ +│ ├── local-env.json # 本地环境配置(gitignored,设备绑定) +│ ├── local-env.example.json # 本地环境配置模板(git tracked) +│ ├── hooks.json # Cursor hooks 配置 +│ └── hooks/ # Hook 脚本(.ps1 + .sh 双版本) +├── art-agent/ +│ ├── backend/ # FastAPI 后端(Python) +│ │ ├── app/ # 应用代码 +│ │ ├── venv/ # Python 虚拟环境(gitignored) +│ │ ├── requirements.txt # 直接依赖(锁定版本) +│ │ └── requirements-lock.txt # 完整依赖树(pip freeze) +│ ├── frontend/ # Next.js 前端(TypeScript) +│ │ ├── src/ # 应用代码 +│ │ ├── node_modules/ # npm 依赖(gitignored) +│ │ ├── package.json # 直接依赖 +│ │ └── package-lock.json # 完整依赖树锁定 +│ └── ENVIRONMENT.md # 本文件 +``` + +## 后端依赖(Python) + +### 直接依赖 + +| 包名 | 版本 | 用途 | +|------|------|------| +| fastapi | 0.135.3 | Web 框架 | +| uvicorn | 0.44.0 | ASGI 服务器 | +| openai | 2.32.0 | OpenAI / 向量引擎 LLM & 生图 API | +| replicate | 1.0.7 | Replicate 生图 API(Flux/SDXL/Kolors 等) | +| sse-starlette | 3.3.4 | Server-Sent Events 流式响应 | +| python-multipart | 0.0.26 | 文件上传处理 | +| httpx | 0.28.1 | 异步 HTTP 客户端 | +| python-dotenv | 1.2.2 | 环境变量 `.env` 加载 | +| Pillow | 12.2.0 | 图像处理 | +| mem0ai | 1.0.11 | Mem0 长期记忆系统 | +| ollama | 0.6.1 | Ollama 本地 LLM/embedding 客户端 | +| sqlmodel | 0.0.38 | SQLModel ORM(SQLite 用户表) | +| pwdlib[argon2,bcrypt] | 0.3.0 | 密码哈希(Argon2id + bcrypt 兼容) | +| python-jose[cryptography] | 3.5.0 | JWT 认证 | + +### 间接依赖(关键) + +| 包名 | 版本 | 来源 | +|------|------|------| +| pydantic | 2.13.1 | fastapi 依赖 | +| starlette | 1.0.0 | fastapi 依赖 | +| SQLAlchemy | 2.0.49 | sqlmodel / mem0ai 依赖 | +| qdrant-client | 1.17.1 | mem0ai 依赖(向量数据库) | +| numpy | 2.4.4 | qdrant-client 依赖 | +| grpcio | 1.80.0 | qdrant-client 依赖 | +| cryptography | 46.0.7 | python-jose[cryptography] 依赖 | +| argon2-cffi | 25.1.0 | pwdlib[argon2] 依赖 | +| bcrypt | 5.0.0 | pwdlib[bcrypt] 依赖 | + +完整依赖树见 `requirements-lock.txt`(共 61 个包)。 + +## 前端依赖(Node.js) + +### 直接依赖(dependencies) + +| 包名 | 版本 | 用途 | +|------|------|------| +| next | 15.5.15 | React 全栈框架 | +| react | 19.2.5 | UI 库 | +| react-dom | 19.2.5 | React DOM 渲染 | + +### 开发依赖(devDependencies) + +| 包名 | 版本 | 用途 | +|------|------|------| +| typescript | 5.9.3 | TypeScript 编译器 | +| tailwindcss | 4.2.2 | CSS 工具框架 | +| @tailwindcss/postcss | 4.2.2 | Tailwind PostCSS 插件 | +| postcss | 8.5.9 | CSS 后处理器 | +| @types/node | 22.19.17 | Node.js 类型定义 | +| @types/react | 19.2.14 | React 类型定义 | +| @types/react-dom | 19.2.3 | ReactDOM 类型定义 | + +完整依赖树见 `package-lock.json`(共 46 个包)。 + +## 外部服务依赖 + +| 服务 | 端口 | 用途 | 必需 | +|------|------|------|------| +| Ollama | 11434 | 本地 embedding(nomic-embed-text)| 是(Mem0 依赖) | +| Qdrant | 6333 | 本地向量数据库(Mem0 存储) | 是(Mem0 依赖) | + +## 环境变量 + +后端通过 `.env` 文件加载配置,主要包含: +- LLM API Key(向量引擎 / DeepSeek) +- Replicate API Token +- 代理配置(HTTPS_PROXY) +- Gemini 超时参数(VECTORENGINE_GEMINI_*_TIMEOUT) +- JWT Secret + +详见 `art-agent/backend/.env`(gitignored,不纳入版本控制)。 + +## 环境搭建步骤 + +### 0. 本地环境配置(首次) + +复制 `.cursor/local-env.example.json` 为 `.cursor/local-env.json`,修改为本机实际值。 +或者启动 Cursor Agent 会话,Agent 会自动探测并生成。 + +### 1. 后端 + +```bash +cd art-agent/backend + +# 创建虚拟环境 +python -m venv venv + +# 激活虚拟环境 +# Windows PowerShell: +.\venv\Scripts\Activate.ps1 +# macOS/Linux: +source venv/bin/activate + +# 安装依赖 +pip install -r requirements.txt + +# 启动(需要先启动 Ollama) +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 +``` + +### 2. 前端 + +```bash +cd art-agent/frontend + +# 如果 Node.js 未在系统 PATH 中(参考 local-env.json 的 nodejs_path): +# PowerShell: $env:PATH = "你的nodejs_path;" + $env:PATH +# bash/zsh: export PATH="你的nodejs_path:$PATH" + +# 安装依赖 +npm install + +# 启动开发服务器 +npm run dev +``` + +### 3. Ollama + +```bash +# 启动 Ollama 服务 +ollama serve + +# 拉取 embedding 模型(首次) +ollama pull nomic-embed-text +``` + +## 访问地址 + +| 服务 | 地址 | +|------|------| +| 前端页面 | http://localhost:3000 | +| 后端 API | http://localhost:8000 | +| API 文档 | http://localhost:8000/docs | +| Ollama | http://localhost:11434 | diff --git a/art-agent/backend/app/agent/loop.py b/art-agent/backend/app/agent/loop.py index e89ebea..06d07c6 100644 --- a/art-agent/backend/app/agent/loop.py +++ b/art-agent/backend/app/agent/loop.py @@ -46,6 +46,7 @@ SYSTEM_PROMPT = """\ - 如果用户提供了参考图(支持多张),将参考图的风格元素融入生成 prompt - 理解多张参考图各自的角色(如"图1的主体 + 图2的风格/视角"),并在 prompt 中准确传达 - 理解用户在图片上的标注(框选区域 + 文字批注),精准定位需要修改的部分 +- 将一张图片转换为多个不同视角(使用 transform_view 工具),适合建筑、物体等需要从不同角度查看的场景 ## 工作流程 1. 理解用户需求,必要时追问细节(尺寸、风格、用途等) @@ -268,9 +269,13 @@ async def run_agent_loop( tc_data = tool_calls_data[idx] tool_name = tc_data["name"] + tool_message = ( + "正在进行视角变换..." if tool_name == "transform_view" + else "正在生成图片..." + ) yield { "type": "tool_start", - "data": {"tool": tool_name, "message": "正在生成图片..."}, + "data": {"tool": tool_name, "message": tool_message}, } try: @@ -292,7 +297,29 @@ async def run_agent_loop( }, } - if result.get("images"): + if result.get("error") and not result.get("success"): + yield { + "type": "tool_error", + "data": { + "tool": tool_name, + "errors": [result["error"]], + "model_name": used_model, + }, + } + + if tool_name == "transform_view" and result.get("images"): + # transform_view 返回带视角信息的图片列表 + image_urls = [v["url"] for v in result["images"]] + yield { + "type": "image_result", + "data": { + "images": image_urls, + "prompt_used": "", + "model_name": used_model, + "view_info": result["images"], + }, + } + elif result.get("images"): yield { "type": "image_result", "data": { diff --git a/art-agent/backend/app/agent/tools.py b/art-agent/backend/app/agent/tools.py index 829a2e5..c95540e 100644 --- a/art-agent/backend/app/agent/tools.py +++ b/art-agent/backend/app/agent/tools.py @@ -1,6 +1,7 @@ """Agent 可调用的工具定义和实现。""" from app.services.image_gen import generate_images +from app.services.view_transform import transform_view # OpenAI Function Calling 格式的工具定义 TOOL_DEFINITIONS = [ @@ -32,7 +33,27 @@ TOOL_DEFINITIONS = [ "required": ["prompt"], }, }, - } + }, + { + "type": "function", + "function": { + "name": "transform_view", + "description": ( + "将一张图片转换为多个不同视角。" + "使用 Zero123++ 模型从单张图片生成 6 个固定视角的图片。" + "适用于建筑、物体等需要从不同角度查看的场景。" + "输入图片必须是正方形(或会被自动裁切为正方形),建议分辨率 >= 320x320。" + "输出 6 个视角:方位角 30°/90°/150°/210°/270°/330°," + "仰角交替为 30°/-20°(正俯视/微仰视)。" + "此工具需要参考图作为输入——必须先有用户上传的图片才能使用。" + ), + "parameters": { + "type": "object", + "properties": {}, + "required": [], + }, + }, + }, ] @@ -65,4 +86,13 @@ async def execute_tool( "model_id": result.model_id, } + if tool_name == "transform_view": + if not ref_image_urls: + return { + "success": False, + "error": "视角变换需要一张输入图片,请先上传参考图", + } + result = await transform_view(ref_image_urls[0]) + return result + return {"success": False, "error": f"未知工具: {tool_name}"} diff --git a/art-agent/backend/app/config.py b/art-agent/backend/app/config.py index 8bbd431..07d731f 100644 --- a/art-agent/backend/app/config.py +++ b/art-agent/backend/app/config.py @@ -228,6 +228,50 @@ IMAGE_MODELS: dict[str, dict[str, Any]] = { } +# ─── 视角变换模型注册表 ───────────────────────────────── +# +# 视角变换模型不用于生图,而是将已有图片转换为不同视角。 +# 输入通常只需一张图,输出为多个固定视角的图片。 + +VIEW_TRANSFORM_MODELS: dict[str, dict[str, Any]] = { + "zero123plus": { + "id": "zero123plus", + "name": "Zero123++", + "provider": "replicate", + "model_id": "jd7h/zero123plusplus:c69c6559a29011b576f1ff0371b3bc1add2856480c60520c7e9ce0b40a6e9052", + "description": "单图生成 6 个固定视角,适合建筑/物体的多角度预览", + "output_views": [ + {"azimuth": 30, "elevation": 30}, + {"azimuth": 90, "elevation": -20}, + {"azimuth": 150, "elevation": 30}, + {"azimuth": 210, "elevation": -20}, + {"azimuth": 270, "elevation": 30}, + {"azimuth": 330, "elevation": -20}, + ], + "grid_layout": {"cols": 3, "rows": 2}, + }, +} + + +def get_view_transform_model_config(model_id: str | None = None) -> dict[str, Any]: + """获取视角变换模型配置,默认返回 zero123plus。""" + if model_id and model_id in VIEW_TRANSFORM_MODELS: + return VIEW_TRANSFORM_MODELS[model_id] + return VIEW_TRANSFORM_MODELS["zero123plus"] + + +def get_view_transform_models_list() -> list[dict]: + """返回前端下拉列表所需的视角变换模型摘要信息。""" + return [ + { + "id": cfg["id"], + "name": cfg["name"], + "description": cfg["description"], + } + for cfg in VIEW_TRANSFORM_MODELS.values() + ] + + def get_default_image_model_id() -> str: """返回 .env 中配置的默认模型短 ID,若不在注册表中则回退到 flux-schnell。""" env_model = os.getenv("IMAGE_MODEL", "flux-schnell") diff --git a/art-agent/backend/app/services/view_transform.py b/art-agent/backend/app/services/view_transform.py new file mode 100644 index 0000000..6065d08 --- /dev/null +++ b/art-agent/backend/app/services/view_transform.py @@ -0,0 +1,157 @@ +"""视角变换服务 — 调用 Zero123++ 等模型,将单张图片转为多视角图片。""" + +import base64 +import logging +import mimetypes +import os +import uuid +from pathlib import Path +from typing import Any + +import httpx +from PIL import Image + +from app.config import get_view_transform_model_config +from app.services.image_gen import _make_replicate_client, GENERATED_DIR, to_data_uri + +logger = logging.getLogger(__name__) + +_replicate_client = _make_replicate_client() + + +def _split_grid_image( + image_path: Path, + cols: int, + rows: int, + view_labels: list[dict[str, int]], +) -> list[dict[str, Any]]: + """将 Zero123++ 输出的 grid 拼接图切分为独立的视角图片。 + + 返回格式:[{"url": "/generated/xxx.png", "azimuth": 30, "elevation": 30}, ...] + Grid 布局从左到右、从上到下依次对应 view_labels 中的视角。 + """ + img = Image.open(image_path) + w, h = img.size + cell_w = w // cols + cell_h = h // rows + + results: list[dict[str, Any]] = [] + for idx, view in enumerate(view_labels): + row_idx = idx // cols + col_idx = idx % cols + left = col_idx * cell_w + upper = row_idx * cell_h + right = left + cell_w + lower = upper + cell_h + + cropped = img.crop((left, upper, right, lower)) + filename = f"view_{uuid.uuid4().hex[:8]}_az{view['azimuth']}_el{view['elevation']}.png" + filepath = GENERATED_DIR / filename + cropped.save(filepath, "PNG") + + results.append({ + "url": f"/generated/{filename}", + "azimuth": view["azimuth"], + "elevation": view["elevation"], + }) + + return results + + +async def _download_to_local(url: str) -> Path: + """下载远程图片到本地 generated/ 目录,返回本地路径。""" + filename = f"grid_{uuid.uuid4().hex}.png" + filepath = GENERATED_DIR / filename + proxy = os.environ.get("HTTPS_PROXY") or os.environ.get("HTTP_PROXY") + async with httpx.AsyncClient(proxy=proxy, timeout=httpx.Timeout(120.0)) as client: + resp = await client.get(url, follow_redirects=True) + resp.raise_for_status() + filepath.write_bytes(resp.content) + return filepath + + +async def transform_view( + image_path: str, + model_id: str | None = None, +) -> dict[str, Any]: + """调用视角变换模型,返回多视角图片列表。 + + Args: + image_path: 输入图片路径(本地路径、data URI 或 URL) + model_id: 视角变换模型 ID,默认 zero123plus + + Returns: + { + "success": bool, + "images": [{"url": str, "azimuth": int, "elevation": int}, ...], + "grid_image": str, # 原始拼接图的本地 URL + "model_name": str, + "error": str | None, + } + """ + config = get_view_transform_model_config(model_id) + model_name = config["name"] + replicate_model_id = config["model_id"] + grid_layout = config["grid_layout"] + output_views = config["output_views"] + + GENERATED_DIR.mkdir(parents=True, exist_ok=True) + + try: + image_uri = to_data_uri(image_path) + input_params = {"image": image_uri} + + logger.info("调用 %s 进行视角变换...", model_name) + output = await _replicate_client.async_run( + replicate_model_id, input=input_params, wait=False + ) + + # Zero123++ 返回单张 grid 拼接图 + items = output if isinstance(output, list) else [output] + if not items: + return { + "success": False, + "images": [], + "grid_image": None, + "model_name": model_name, + "error": "模型未返回任何输出", + } + + grid_url = str(items[0]) + if not (grid_url.startswith("http://") or grid_url.startswith("https://")): + return { + "success": False, + "images": [], + "grid_image": None, + "model_name": model_name, + "error": f"模型返回非图片内容: {grid_url[:200]}", + } + + grid_local_path = await _download_to_local(grid_url) + grid_local_url = f"/generated/{grid_local_path.name}" + + view_images = _split_grid_image( + grid_local_path, + cols=grid_layout["cols"], + rows=grid_layout["rows"], + view_labels=output_views, + ) + + return { + "success": True, + "images": view_images, + "grid_image": grid_local_url, + "model_name": model_name, + "error": None, + } + + except Exception as e: + detail = str(e) or f"{type(e).__name__}: {repr(e)}" + logger.error("视角变换失败: %s", detail, exc_info=True) + return { + "success": False, + "images": [], + "grid_image": None, + "model_name": model_name, + "error": detail, + } diff --git a/art-agent/backend/requirements-lock.txt b/art-agent/backend/requirements-lock.txt new file mode 100644 index 0000000..64ed7d3 Binary files /dev/null and b/art-agent/backend/requirements-lock.txt differ diff --git a/art-agent/backend/requirements.txt b/art-agent/backend/requirements.txt index 34bb58b..6ecfa0a 100644 --- a/art-agent/backend/requirements.txt +++ b/art-agent/backend/requirements.txt @@ -1,14 +1,14 @@ -fastapi>=0.115.0 -uvicorn>=0.32.0 -openai>=1.55.0 -replicate>=1.0.0 -sse-starlette>=2.1.0 -python-multipart>=0.0.12 -httpx>=0.27.0 -python-dotenv>=1.0.0 -Pillow>=10.4.0 -mem0ai -ollama -sqlmodel>=0.0.22 -pwdlib[argon2,bcrypt]>=0.3.0 -python-jose[cryptography]>=3.3.0 +fastapi==0.135.3 +uvicorn==0.44.0 +openai==2.32.0 +replicate==1.0.7 +sse-starlette==3.3.4 +python-multipart==0.0.26 +httpx==0.28.1 +python-dotenv==1.2.2 +Pillow==12.2.0 +mem0ai==1.0.11 +ollama==0.6.1 +sqlmodel==0.0.38 +pwdlib[argon2,bcrypt]==0.3.0 +python-jose[cryptography]==3.5.0 diff --git a/art-agent/frontend/package-lock.json b/art-agent/frontend/package-lock.json index e7a0310..14ee88b 100644 --- a/art-agent/frontend/package-lock.json +++ b/art-agent/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "epeekit-frontend", "version": "0.1.0", "dependencies": { + "framer-motion": "^12.38.0", "next": "^15.1.0", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -138,9 +139,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -157,9 +155,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -176,9 +171,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -195,9 +187,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -214,9 +203,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -233,9 +219,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -252,9 +235,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -271,9 +251,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -290,9 +267,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -315,9 +289,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -340,9 +311,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -365,9 +333,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -390,9 +355,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -415,9 +377,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -440,9 +399,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -465,9 +421,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -654,9 +607,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -673,9 +623,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -692,9 +639,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -711,9 +655,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -897,9 +838,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -917,9 +855,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -937,9 +872,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -957,9 +889,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1134,6 +1063,33 @@ "node": ">=10.13.0" } }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1294,9 +1250,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1318,9 +1271,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1342,9 +1292,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1366,9 +1313,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1434,6 +1378,21 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", diff --git a/art-agent/frontend/package.json b/art-agent/frontend/package.json index 758a5c6..62a9042 100644 --- a/art-agent/frontend/package.json +++ b/art-agent/frontend/package.json @@ -9,17 +9,18 @@ "lint": "next lint" }, "dependencies": { + "framer-motion": "^12.38.0", "next": "^15.1.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { + "@tailwindcss/postcss": "^4.0.0", "@types/node": "^22.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", - "typescript": "^5.7.0", + "postcss": "^8.5.0", "tailwindcss": "^4.0.0", - "@tailwindcss/postcss": "^4.0.0", - "postcss": "^8.5.0" + "typescript": "^5.7.0" } } diff --git a/art-agent/frontend/src/app/characters/[id]/page.tsx b/art-agent/frontend/src/app/characters/[id]/page.tsx new file mode 100644 index 0000000..13e764e --- /dev/null +++ b/art-agent/frontend/src/app/characters/[id]/page.tsx @@ -0,0 +1,511 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useParams, useRouter } from "next/navigation"; +import Link from "next/link"; +import { TopNav } from "@/components/layout/top-nav"; +import { AmbientParticles } from "@/components/ui/ambient-particles"; +import { useApp } from "@/lib/app-context"; +import type { CharacterCard, CharacterRefImage } from "@/lib/types"; + +const ANGLES = [ + "正面", + "半侧面", + "侧面", + "背面", + "半身", + "全身", + "表情·微笑", + "表情·愤怒", + "表情·惊讶", + "服饰细节", +]; + +export default function CharacterDetailPage() { + const params = useParams<{ id: string }>(); + const router = useRouter(); + const isNew = params.id === "new"; + const { + characters, + upsertCharacter, + deleteCharacter, + setSessionCharacterId, + projects, + activeProjectId, + assets, + } = useApp(); + + const [c, setC] = useState(null); + + useEffect(() => { + if (isNew) { + const now = Date.now(); + setC({ + id: `char-${now.toString(36)}`, + name: "", + description: "", + personality: [], + visualHooks: [], + consistency: "medium", + refImages: [], + projectId: activeProjectId, + createdAt: now, + updatedAt: now, + }); + } else { + const found = characters.find((x) => x.id === params.id); + if (found) setC(found); + } + }, [params.id, characters, isNew, activeProjectId]); + + if (!c) { + return ( +
+ +
+

加载中...

+
+
+ ); + } + + const handleSave = () => { + if (!c.name.trim()) { + alert("请填写角色名称"); + return; + } + upsertCharacter(c); + alert("已保存(占位)"); + if (isNew) router.push(`/characters/${c.id}`); + }; + + const handleApply = () => { + setSessionCharacterId(c.id); + alert(`已将角色 "${c.name}" 应用到当前会话(占位)`); + router.push("/"); + }; + + const handleDelete = () => { + if (!confirm(`确认删除角色 "${c.name}"?`)) return; + deleteCharacter(c.id); + router.push("/characters"); + }; + + const addRef = (angle: string) => { + setC({ + ...c, + refImages: [ + ...c.refImages, + { id: `r-${Date.now()}`, url: "", angle, caption: "占位参考图" }, + ], + }); + }; + + const removeRef = (id: string) => { + setC({ ...c, refImages: c.refImages.filter((r) => r.id !== id) }); + }; + + const addPersonality = (kw: string) => { + if (!kw || c.personality.includes(kw)) return; + setC({ ...c, personality: [...c.personality, kw] }); + }; + + const addHook = (kw: string) => { + if (!kw || c.visualHooks.includes(kw)) return; + setC({ ...c, visualHooks: [...c.visualHooks, kw] }); + }; + + const relatedAssets = assets + .filter((a) => + c.visualHooks.some((h) => a.prompt.toLowerCase().includes(h.toLowerCase())) || + a.prompt.toLowerCase().includes(c.name.toLowerCase()) + ) + .slice(0, 6); + + return ( +
+ + + +
+
+
+ + 角色库 + + / + + {isNew ? "新建角色" : c.name || "未命名"} + +
+ +
+
+
+ setC({ ...c, name: e.target.value })} + placeholder="角色名称" + className="text-2xl font-bold bg-transparent border-none + text-[var(--text-primary)] placeholder:text-[var(--text-secondary)]/60 + focus:outline-none flex-1" + /> + Phase 1 +
+
+
+ + + {!isNew && ( + <> + + 训练角色 LoRA + P2 + + + + )} +
+
+ +
+
+ {/* 基本信息 */} +
+

基本信息

+