Imported from suziping123/Dixiyang (
AGENTS.md). Install upstream withnpx skills add suziping123/Dixiyang. Copyright stays with the author.
OpenCode Global Rules
你是一个顶级的程序员。在每次对话中,你必须严格遵循以下“准则”:
核心准则
- 绝对中文:必须使用简体中文进行交流(除非是代码、专有名词等特殊情况)。
- 言简意赅:解释或评论代码时,必须附带清晰且极为精简的中文文档。
- 先查后问:遇到代码问题,优先在本项目的
**/docs/目录或相关代码文件中寻找答案,找不到再向我提问。 - 规划先行:开始写代码前,必须先简要说明你的计划,并清晰扼要地分析其利弊。
- 环境:默认使用本目录下的环境
- 实现必须写文档:每次完成代码实现后,必须在对应
docs/目录新增或更新 MD 文档,记录:需求、方案、改动文件、已知问题、验证方式。文档索引若存在docs/README.md须同步登记。前端改动写入docs/(根目录)或dixiyang-vue/docs/;后端写入对应**/docs/。无文档视为任务未完成。 - 大文本存文件系统:所有用户生成的"大文本"内容(对话、分析、长描述等)必须存为服务器本地 JSON 文件,数据库只存文件路径/引用。路径约定
storage/{module}/{userId}/{sessionId}/{recordId}.json。此规则适用于:- AI 聊天历史(content / thinking)
- 后续任何长文本字段
- 或者文件,视频,日志等大文件或者可能会变成的大文件
- Java版本:java17,sdkman
.env文件禁令:禁止查看、读取、修改、搜索根目录的.env文件(内含数据库密码、API Key 等真实凭据)。需要了解环境变量结构时,只允许查看示例文件.env.example(占位符模板,不含真实值)。确因任务必须接触.env时,须先向我明确说明原因并获得一次性授权(授权仅限当次对话,不可复用)。- motion-web 技能本地化:技能目录
motion-web-main/、.agents/、.trae/已移出版本库,仅本地保留(GitHub 上不再有)。协作者克隆/拉取前必须先执行一次git config core.hooksPath .githooks(或powershell -File scripts/setup-hooks.ps1),否则git pull会删除本地技能文件;一旦文件丢失,运行powershell -File scripts/restore-skills.ps1从 git 历史恢复。详见docs/技能库出库与同步.md。 - 禁止死等,命令必设最长时间:每次执行 shell 命令必须显式传
timeout(毫秒),按任务给上限——查状态 ≤30s、起服务验证 ≤60s、跑测试 ≤300s;等待就绪用短轮询(每 1-2s 查一次状态,如端口/进程/日志),到达上限立即放弃并汇报"超时+当前状态",严禁无脑Start-Sleep大段秒数死等(超时是常态,死等浪费你的等待时间)。任务级总时限 ≤5 分钟:验证最多做一轮(一次命令内完成 启动→检查→取结果),失败先汇报再议,禁止反复重启/反复验证拖时间——多次短轮询叠加同样会耗掉 10 分钟以上,与死等无异。 - 开发顺序:实际 → 剪裁 → 展示(2026-10-18 用户指出,问题本质=展示页面与实际开发页面功能/表现对不上;原话:"先做好开发页面的实际的,再去把开发的不必要部分剪裁掉,再丢去展示部分,你顺序完全错了"。例证:"展示页面的条形图没有局限在规定好的范围内,超出部分直接超出屏幕;上面的页签导航溢出了也没有滚动条"):
- 第一步做实际的:先把实际开发页面的功能与表现做对做全——真实后端、真实数据,布局/滚动/溢出等细节同样算"功能",在实际页面逐项达标;
- 第二步剪裁:功能站住后,再剪掉开发过程中产生的不必要部分(调试残留、冗余分支、为截图验收临时加的结构、过度设计);
- 第三步展示:最后才产出展示部分(演示 mock、gh-pages 展示、截图验收),展示 = 剪裁后的同一份代码,不是另做一套;
- 禁止两套并行:同一件事只允许一份实现。一旦发现自己在做两套不同的东西(为展示另写一套、或只顾一端把另一端做坏),立即停下,回到本顺序返工——先修"实际",最后才碰"展示"。
- 验证登录一律用测试账号(禁止伪造 token):本地需要登录态的验证(CDP 探针 / UI 验收 / 接口回归)必须用测试账号
admin123/123456真实登录(POST /api/auth/login→ 写入token/username/userId/nickname/email/role);严禁注入伪造 token(假 JWT 骗得过守卫,但一次 401 就被拦截器踢下线,还会把假数据写进 localStorage 缓存污染后续验收)。取 token、写入哪些键、风控与顶号注意(10 分钟 3 次密码登录后强制邮箱验证码;单点登录会顶号,别用他人在用账号)见docs/开发环境与测试账号.md。
前端四大必读(高频坑与适配规范,动前端前必看)
坑1:页面背景被"吃掉"(背景层叠坑)
- 症状:配置了背景图/背景色,进入某页面却不显示或被卡片盖住。
- 根因:页面根容器缺
position: relative,子层position: absolute逃逸出层叠上下文,跑到背景层之上。 - 修法:根容器加
position: relative(如IdeaLibraryView .idea-page、PostDetailDialog .detail-body),背景层z-index置底。新建带背景层的页面/弹窗必须先查这一条。 - 详见
docs/点子库Demo实施记录.md§5 踩坑记录。
坑2:输入控件"模糊蒙板"(backdrop-filter 坑)
- 症状:弹窗内输入框、下拉(选择小说/对话/标签/角色)点击聚焦时出现白雾毛玻璃,失焦消失。
- 根因(两层叠加,实测 CDP 定位):
main.css全局规则input, textarea, select { backdrop-filter: blur(10px) }+ 控件 5% 半透明底;- EP filterable select 的原生输入框
input.el-select__input聚焦时展开、命中该全局规则,把下层 placeholder 模糊成白雾(失焦塌缩所以"消失")。
- 修法(三层,缺一不可,均在
dixiyang-vue/Dixiyang-vue3/src/assets/main.css):- 弹窗内控件实底:
html .el-dialog作用域background: #191c27 !important——必须!important(压文件尾部全局.el-input__wrapper { background: ... !important },同级靠特异性取胜); - 弹窗内全部原生输入关闭 blur:
html .el-dialog input, textarea, select { backdrop-filter: none !important }——.el-select__input靠这条,漏掉它就是本轮"下拉仍蒙板"复燃; - 页面级 select 同病同治:
input.el-select__input, .el-input-number__input { backdrop-filter: none !important }。
- 弹窗内控件实底:
- 易错点:EP 浮层(popper/dropdown)一律 teleport 到
body,不在.el-dialog内,须用html .el-select__popper/html .el-popper全局前缀;html .el-dialog .el-option是错误选择器(EP 实际类为.el-select-dropdown__item)。 - 验证套路:CDP 打开弹窗聚焦控件 → dump
getComputedStyle(el).backdropFilter须为none→ 截图确认无白雾。
坑3:getNovelOptions 返回 {records:[...]} 分页结构(unwrap 口径坑)
- 症状:来源/小说下拉恒为空、级联回显不了(用户话术"后端发来的数据显示不了")。
- 根因:
GET /novel/listall的data是{ records: [...], total };按数组 unwrap →null ?? []恒空 → 角色/对话二级列表全灭。 - 修法:
unwrap<{ records: T[] }>(res)?.records ?? []。注意同页混用两种口径:getCharacters/getChatSessions是直接数组,getNovelOptions是分页对象——复制粘贴前先对齐。 - 详见
docs/点子库Demo实施记录.md§12.2(下架编辑回显修复)。
移动端适配(Agent 必读,新建/改动页面必须逐条过)
- 断点统一 768:
@media (max-width: 768px)。弹窗走 main.css 全局贴底抽屉(html .el-dialog宽 100%、margin:auto auto 0贴底、body 滚动)——新弹窗不要自设宽度/高度(会被全局!important压掉,直接复用即可)。 - 避让浮动层(第三轮真机教训,原"padding-left 88"写法是错的):FAB 是 FloatingNav 右侧 46px 悬浮球(触屏/≤1024px 切 FAB 模式,右锚条 74px = 球 46 + 右缘 14),页面应在右侧避让且精算宽度:
padding-right: 64px(14+46+4 缓冲,球左缘 315 / 内容右界 311 互不重叠)。禁止盲目避让 88——375 视口下内容被压到 273px,双列minmax(150px)放不下退化单列,观感 = "完全没有适配"(用户原话暴怒点);双列列宽用minmax(140px, 1fr)(内容 297 → 列 143.5 ✓)。 - 筛选行分组横滑:chips 组与排序组各自独占一行(
flex-wrap: wrap)+ 组内overflow-x: auto——整行单行横滑会把"最新/热门"排序挤出首屏,整行flex-shrink:0长组又会撑破 375 视口(实测 572),两者都是错的。滚动必须下沉到组自身(组 100% 宽不撑文档)。 - 横向溢出防呆(本轮实测坑):筛选行/chip/tag 行等横滑组必须收在块级容器内
overflow-x: auto,组内元素flex-shrink允许压缩或整行滚动;禁止整行flex-shrink: 0+white-space: nowrap长组——实测把 375 视口撑到 572、整页横向滚动。nowrap只可用于单行标签头(如.tag-row-label),不可用于整行组。 - 表单:弹窗内双列选择行(
.source-row等)移动端改 1 列;操作按钮行.d-actions加flex-wrap: wrap。 - 卡片列表:双列
grid或瀑布流column-width: 150px,图片横滑区独立。 - 输入字号 ≥16px:否则 iOS Safari 聚焦时放大整页(main.css 已全局给
.el-dialog .el-input__inner/.el-textarea__inner,新控件自查)。 - 验证套路(升级:光断言属性不够,必须肉眼复核):CDP
Emulation.setDeviceMetricsOverride {375, 812, 2, mobile:true}→ 断言document.documentElement.scrollWidth === 375(无横向溢出)、卡片确为双列(瀑布流用left分两档判定,不能用top——CSS columns 按列填充相邻卡 top 必不同)、tab-bar.scrollWidth <= clientWidth(Tab 全见)、弹窗宽 375、控件单列;最后必须 375 截图肉眼看一遍(列数/截断/空白带),只跑数字断言会漏掉"单列大卡=没适配"这类观感问题。
聊天编辑 & 重新生成设计
编辑 AI 回答(edits.json 方案)
- 后端核心:
ChatContentFileService.java-addEditRecord()/readEdits()/buildEditsPrompt()/mergeEdits() - 编辑存储:
storage/chat/{userId}/{sessionId}/edits.json,与链文件同级 - AI 学习:每次
/chat/stream请求携带sessionId时,自动从edits.json构建修正记录注入 system prompt - 编辑端点:
PUT /chatHistory/message/{sessionId}→ChatSessionServiceImpl.editMessage() - 链内修改:
ChatContentFileService.replaceMessageInChain()- 直接修改链文件中指定索引的消息内容;同时记录到 edits.json 供 AI 学习 - 前端触发:
ChatMessage.vue编辑按钮 →EditMessageModal.vue(ElDialog 弹窗) → 原文对照 + textarea 编辑 → 保存
重新生成 AI 回答
- SSE 端点:
POST /chat/regenerate→ChatController.regenerate()(真流式) - 流程:前端指定
messageIndex→ 后端截断链文件 → 用截断后历史 + edits 构建 prompt → Flux 逐 token 推 SSE → 完成时追加新回答到链 - 链截断:
ChatContentFileService.truncateChain(headPath, keepCount)- 保留前 N 条,删除后续文件 - 前端触发:
ChatMessage.vue重新生成按钮 →useChatStream.regenerateMessage()
流式输出(真流式)
- 端点:
POST /chat/stream→ChatController.chatStream()(SseEmitter 实现) - 核心:
chatClient.prompt(fullPrompt).stream().content()返回Flux<String>,通过CountDownLatch桥接到 Servlet 线程,逐 token 推 SSE - JSON 文件修改:
chatClient.prompt(fullPrompt).stream().content()改为chatClient.prompt(fullPrompt).call().content() - thinking 标签:后端 Flux 中实时检测
<thinking>/</thinking>标签边界,拆分 SSE type=thinking/content - 前/后端取消:前端 AbortController.abort(),后端自动检测
- 思考过程渲染:前端
useChatStream.ts区分 type 渲染
修改 AI 行为的提示词位置
修改以下文件中的 prompt 字符串以控制 AI 行为:
ChatController.java:171-buildFullPrompt()方法(主对话 prompt 构建,真流式stream().content())ChatController.java-/chat/regenerate端点中的 prompt 构建逻辑ChatSessionServiceImpl.java:171- 标题生成 prompt(generateAndUpdateTitle())ChatContentFileService.java:326-buildEditsPrompt()方法(编辑修正记录的 prompt 模板,注入到每次对话)
存储结构
统一存储根目录:/home/lijiajia/项目/Dixiyang/uploads/
uploads/
├── backgrounds/ # 背景图(实际图片文件)
│ ├── {md5hash}.jpg # 上传的背景图
│ └── {md5hash}.md5 # MD5 去重映射
├── covers/ # 封面图(实际图片文件)
│ ├── {md5hash}.jpg
│ └── {md5hash}.md5
└── storage/ # JSON 数据存储(DB 只存路径引用)
├── chat/{userId}/{sessionId}/
│ ├── {timestamp}_{rand}.json # 链文件(next 指针连接,纯文件名)
│ ├── edits.json # 编辑修正记录
│ └── summary.json # 历史摘要
├── character/{id}.json # 角色设定
└── user/
├── customBgs/{userId}.json # 自定义背景元数据
└── fontColors/{userId}.json # 字体颜色配置
文件引用格式(统一约定)
所有 DB 中的文件引用字段使用 __file__: 前缀 + 相对路径:
| 字段 | 格式示例 | 说明 |
|---|---|---|
extra |
__file__:character/{id}.json |
角色设定 |
head_path |
__file__:chat/{userId}/{sessionId}/{file}.json |
聊天链头文件 |
custom_bgs |
__file__:user/customBgs/{userId}.json |
用户自定义背景 |
font_colors_json |
__file__:user/fontColors/{userId}.json |
字体颜色配置 |
cover_url |
/api/uploads/covers/{filename}.png |
例外:HTTP URL,前端直接使用 |
读取规则:
- Java:
StorageService.loadJson(subdir, id, dbValue)→ 自动判断__file__:前缀 - Python:
storage_service.load_json(subdir, id, db_value)→ 同上
链文件 next 指针格式(统一约定)
链文件的 next 字段存储纯文件名(不含路径前缀):
{ "messages": [...], "next": "1783271834717_61b2d203.json" }
- 遍历时基于当前文件的父目录拼接:
Paths.get(currentFile).getParent().resolve(next) - 不使用
./前缀,不使用绝对路径
motion-web 技能(动效网页)
- 凡涉及做网页/落地页/官网/作品集/动效/复刻参考站的任务,先加载
motion-web技能:入口motion-web-main/SKILL.md(软链~/.agents/skills/motion-web、.trae/skills/motion-web同源),按其 Build Order 执行。 - 详见
docs/motion-web技能接入.md。
