diff --git a/dev/ai.md b/dev/ai.md index 647f4ac..75d27ea 100644 --- a/dev/ai.md +++ b/dev/ai.md @@ -89,11 +89,12 @@ ## 规则生成 -`AiRuleServiceImpl` 由用户勾选的**已打标签记录**反推分类/标签规则: +`AiRuleServiceImpl` 由用户勾选的记录反推分类/标签规则,有两个入口:规则管理页(依据**已打标签记录**)与收支记录页批量操作(依据**任意勾选账单**): -- 仅加载属于当前用户且被勾选的记录(单次 ≤ 500 条),附带其标签名称与分类名;提示词字段为 `type / remark / channel / category / tags`(不含金额与 `rawData`),并对相同组合去重。 +- 仅加载属于当前用户且被勾选的记录(单次 ≤ 500 条),附带其标签名称与分类名;提示词字段为 `type / remark / channel / category / tags`(不含金额与 `rawData`),并对相同「类型 + 备注 + 渠道 + 标签」组合去重。 - 解析并校验模型输出(分类 ID 归属与类型、标签归属、名称长度、颜色格式等)。 - **去重**:按「类型 + 归一化关键词 + 分类 + 排序标签集合」计算签名;与已有规则或本次建议内部重复的条目标记 `duplicate=true`(前端默认不勾选且禁止导入),`apply` 时后端再次校验并跳过。 +- **应用到交易**:`POST /api/ai/rule/apply-transactions`(`applyToTransactions`)按传入规则对指定交易做**本地匹配**(不调用大模型、不外发数据),命中时覆盖分类、追加标签,必要时新建分类/标签;用于收支记录页勾选「同时应用到账单」的场景。 ## 统计搜索 diff --git a/dev/api.md b/dev/api.md index 52d8b11..16db0ca 100644 --- a/dev/api.md +++ b/dev/api.md @@ -79,6 +79,7 @@ | DELETE | `/api/rule/batch` | 批量删除规则(body `{ ids }`,返回实际删除数) | | POST | `/api/ai/rule/suggest` | 依据所选已打标签记录生成规则建议(含 `duplicate` 标记) | | POST | `/api/ai/rule/apply` | 批量创建规则(自动去重、按需新建分类/标签) | +| POST | `/api/ai/rule/apply-transactions` | 按传入规则对指定交易做本地匹配并应用(命中则覆盖分类、追加标签,必要时新建;纯本地匹配,不外发数据) | | POST | `/api/ai/statistics` | 自然语言解析为统计面板参数 | | GET / PUT | `/api/setting/excluded-display-style` | 查询 / 更新「不计入统计」记录展示样式 | | GET / PUT | `/api/setting/show-hidden-data` | 查询 / 更新是否显示已隐藏备注的记录 | diff --git a/dev/build-deploy.md b/dev/build-deploy.md index 63befe4..60b87b5 100644 --- a/dev/build-deploy.md +++ b/dev/build-deploy.md @@ -122,7 +122,9 @@ jar 内置的前端为生产构建,访问时与后端同源,无需额外的 ### 安全配置 - **JWT 签名密钥**:默认使用内置密钥签发 Token(有效期 24 小时);生产环境可通过环境变量 `JWT_SECRET` 覆盖,未配置时回退内置默认值,避免个人自托管场景重启后登录态失效。 -- **CORS 放行来源**:仅放行本机与局域网来源——`localhost`、`127.0.0.1`、`[::1]`、`192.168.*`、`10.*`、`172.*`(任意端口),并允许携带凭证;生产环境前后端同源,无需额外的跨域配置。 +- **CORS 放行来源**:放开全部来源(`allowedOriginPatterns("*")` 并允许携带凭证)。因 `allowCredentials(true)` 不允许 `allowedOrigins("*")`,需使用 origin pattern 通配;接口认证基于 JWT(`Authorization` 头存于 `localStorage`,跨站脚本无法自动携带),风险可控。 +- **反向代理转发头**:`application.yml` 配置 `server.forward-headers-strategy: framework`,信任 `X-Forwarded-Proto`/`Host`;经 nginx/frp 反代时使 HTTPS 同源请求被正确识别为同源,避免被误判为跨域(否则带 `Origin` 的请求会返回 `403 Invalid CORS request`)。 +- **运行环境编码**:容器或直接运行 jar 的 JVM 需为 UTF-8 locale(`LANG`/`LC_ALL=C.UTF-8`);否则 `sun.jnu.encoding` 退化为 ASCII,含中文的文件名(如邮箱账单附件名)落盘会抛 `InvalidPathException`。 ### 发布流程 diff --git a/dev/rule-engine.md b/dev/rule-engine.md index 68f1ebf..8dc649f 100644 --- a/dev/rule-engine.md +++ b/dev/rule-engine.md @@ -38,10 +38,11 @@ ## AI 生成规则 -`AiRuleServiceImpl` 依据用户勾选的**已打标签记录**生成规则建议: +`AiRuleServiceImpl` 提供两个入口:规则管理页(生成 + 创建)与收支记录页批量操作(生成 + 可选应用到账单)。 -- 输入字段为记录的类型、备注、交易渠道、已有分类与标签(不含金额与原始账单数据)。 -- 输出规则经 `RuleService.create` 落库,必要时新建分类/标签,规则默认启用。 +- 生成建议(`suggest`,`POST /api/ai/rule/suggest`):依据用户勾选的记录生成规则建议;规则管理页限定**已打标签记录**,收支记录页为**任意勾选账单**。输入字段为记录的类型、备注、交易渠道、已有分类与标签(不含金额与原始账单数据),并按「类型 + 备注 + 渠道 + 标签」组合去重。 +- 创建规则(`apply`,`POST /api/ai/rule/apply`):输出规则经 `RuleService.create` 落库,必要时新建分类/标签,规则默认启用。 +- 应用到交易(`applyToTransactions`,`POST /api/ai/rule/apply-transactions`):**纯本地匹配、不调用大模型**,按规则的类型/关键词/金额条件匹配指定交易,命中则聚合分类与标签,委托 `AiSuggestionService.apply` 落库(覆盖分类、追加标签,必要时新建)。 - **去重**:以「类型 + 归一化关键词 + 分类 + 排序标签集合」为签名;与已有规则或同批已创建规则重复时跳过(`apply` 返回实际创建数),建议阶段则标记 `duplicate` 供前端禁止勾选。 ## 校验 diff --git a/guide/ai.md b/guide/ai.md index f789ab0..2b58af8 100644 --- a/guide/ai.md +++ b/guide/ai.md @@ -51,7 +51,7 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发 | 生成渠道 | 新增/编辑记录 → 交易渠道 → AI 新建 | 由账单描述生成渠道名称并创建 | | 推荐 Emoji | 分类管理 → AI 推荐 | 为分类推荐 Emoji 图标 | | 推荐颜色 | 标签管理 → AI 推荐 | 为标签推荐颜色 | -| 生成规则 | 规则管理 → AI 生成规则 | 由已打标签的记录归纳分类与标签规则 | +| 生成规则 | 规则管理 → AI 生成规则(已打标签记录)/收支记录 → 批量操作 → AI 生成规则(勾选账单) | 由所选记录归纳分类与标签规则,可同时应用到勾选账单 | | 统计搜索 | 统计面板 → AI 搜索 | 自然语言解析为统计筛选条件与维度 | | 生成关系名称 | 收支记录 → 账单关系 → AI 生成 | 依据关联账单的类型、渠道、分类与备注生成关系名称与备注 | | 智能查数 | 左侧菜单 → 智能查数 | 自然语言转只读 SQL 查询并展示结果 | @@ -114,7 +114,12 @@ API Key 仅保存在本地数据库中,仅用于向你配置的接口地址发 ## AI 生成规则 -在规则管理页点「AI 生成规则」,选择用于归纳的**已打标签记录**(默认全选当前页,支持搜索、筛选与分页),AI 会依据这些记录的**类型、备注、交易渠道、已有分类与标签**生成规则建议,预览编辑后批量创建。与已有规则重复的建议会标记「已存在」并禁止导入。详见[规则管理](/guide/rules)。 +有两种生成方式: + +- **规则管理页**:点「AI 生成规则」,选择用于归纳的**已打标签记录**(默认全选当前页,支持搜索、筛选与分页),AI 依据这些记录的**类型、备注、交易渠道、已有分类与标签**生成规则建议,预览编辑后批量创建。 +- **收支记录页**:在**批量操作**区勾选若干账单后点「AI 生成规则」,直接以**勾选的账单**为依据归纳规则(不要求已打标签);弹窗中还可勾选「**同时应用到当前勾选的账单**」,命中时覆盖这些账单的分类并追加标签。 + +两种方式都会对与已有规则重复的建议标记「已存在」并禁止导入。详见[规则管理](/guide/rules#ai-生成规则)与[收支记录](/guide/transactions#ai-生成规则从勾选账单)。 ## 用量统计 diff --git a/guide/email-bills.md b/guide/email-bills.md index d7c4db6..66af148 100644 --- a/guide/email-bills.md +++ b/guide/email-bills.md @@ -32,7 +32,7 @@ 行操作: - **测试连接**:验证 IMAP 配置。 -- **立即检查**:实时展示检查进度,完成后提示新增待处理账单数量。 +- **立即检查**:实时展示检查进度(每行带 `HH:mm:ss` 时间戳),完成后提示新增待处理账单数量。 - **编辑 / 删除**。 ## 发件人规则 diff --git a/guide/pages.md b/guide/pages.md index c535125..4e2e171 100644 --- a/guide/pages.md +++ b/guide/pages.md @@ -57,7 +57,7 @@ - **智能查数**:左侧菜单「智能查数」→ 输入自然语言 → 查看结果、生成的 SQL 与历史提问 - **自动归类**:规则管理配置规则 → 账单导入预览时命中并给出建议 - **邮箱账单**:邮箱配置 → 发件人规则 → 待处理账单 → 解析 → 导入到收支记录 -- **AI 辅助**:设置启用 AI → 统计面板「AI 搜索」、收支记录「一键设置 / 文字·图片记账 / AI 查询」、编辑记录「分类·标签·渠道 AI 推荐与新建」、分类与标签「AI 推荐」、规则管理「AI 生成规则」、账单关系「AI 生成」、左侧「智能查数」 +- **AI 辅助**:设置启用 AI → 统计面板「AI 搜索」、收支记录「一键设置 / 文字·图片记账 / AI 查询」、编辑记录「分类·标签·渠道 AI 推荐与新建」、分类与标签「AI 推荐」、规则管理「AI 生成规则」、收支记录批量操作「AI 生成规则」、账单关系「AI 生成」、左侧「智能查数」 - **系统维护**:顶部菜单 → 设置 / 日志 ## 页面关系说明 diff --git a/guide/rules.md b/guide/rules.md index 305169d..3a11866 100644 --- a/guide/rules.md +++ b/guide/rules.md @@ -75,6 +75,8 @@ ![AI 生成规则 · 预览规则](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/ai-rule-preview.png) +> 此外,也可在[收支记录页的批量操作区](/guide/transactions#ai-生成规则从勾选账单)勾选账单后直接生成规则(不要求已打标签),并可选「同时应用到勾选账单」。 + ::: tip 与已有规则去重 AI 建议若与**数据库中已有规则重复**(按「类型 + 关键词 + 分类 + 标签集合」判定),会标注「**已存在**」,**默认不勾选且不可勾选**,无法导入,避免产生重复规则。 ::: diff --git a/guide/transactions.md b/guide/transactions.md index 50f68c7..50e47fa 100644 --- a/guide/transactions.md +++ b/guide/transactions.md @@ -27,9 +27,20 @@ | 一键设置 | 批量设置分类、计入统计与标签;**有勾选时作用于勾选记录,未勾选时作用于当前页全部记录**,详见[一键设置(批量设置)](#一键设置-批量设置) | | 批量删除 | 勾选至少一条后出现,确认后删除所选记录 | | 建立关系 | 勾选至少两条后出现,为所选记录[建立账单关系](/guide/relations) | +| AI 生成规则 | 启用 AI 后出现;勾选至少一条账单,由 AI 归纳分类/标签规则,可在弹窗中勾选「同时应用到当前勾选的账单」,详见 [AI 生成规则](#ai-生成规则从勾选账单) | > 翻页、刷新或退出批量模式都会清空已勾选记录。**批量删除**与**建立关系**只作用于勾选记录;**一键设置**在未勾选时才作用于当前页全部记录,且当次勾选范围以打开弹窗时的选择为准。 +## AI 生成规则(从勾选账单) + +启用 AI 后,批量操作区会出现「**AI 生成规则**」按钮。勾选若干账单后点击,AI 会依据这些账单的**类型、备注、交易渠道、已有分类与标签**归纳出「关键词 → 分类/标签」规则;在弹窗中预览、勾选要创建的规则后点击「创建勾选规则」批量创建,系统会自动创建建议中不存在的新分类与标签。 + +弹窗底部可勾选「**同时应用到当前勾选的 N 条账单**」——命中规则时会**覆盖这些账单的分类并追加标签**(不改动备注与金额)。生成与「同时应用」全程不发送金额、原始账单数据等敏感字段。 + +![AI 生成规则(收支记录)](https://adcarac.oss-cn-beijing.aliyuncs.com/finance-app-docs/screenshots/elements/ai-rule-transactions.png) + +> 与[规则管理](/guide/rules#ai-生成规则)页的「AI 生成规则」(依据**已打标签记录**归纳)不同,此入口直接以**勾选的账单**为依据,不要求已打标签;两处都会对与已有规则重复的建议标记「已存在」并禁止导入。 + ## 账单导入 「收支记录」页支持把支付宝、微信、美团、淘宝、京东、招商银行、交通银行的账单文件解析并导入为收支记录。 diff --git a/scripts/ts/capture-elements.ts b/scripts/ts/capture-elements.ts index d69a3bc..0b01a45 100644 --- a/scripts/ts/capture-elements.ts +++ b/scripts/ts/capture-elements.ts @@ -442,6 +442,44 @@ export async function captureAiRule(page: Page): Promise { } } +/** + * 截取「收支记录 → 批量操作 → AI 生成规则」弹窗(基于勾选账单生成规则)。 + * + *

仅触发规则建议生成(只读),不点击「创建勾选规则」,不修改数据。 + * 需目标环境已启用 AI 且存在可勾选账单。

+ * + * @param page Page 实例 + */ +export async function captureAiRuleFromTransactions(page: Page): Promise { + try { + await page.setViewportSize({ width: 1440, height: 1000 }) + await page.goto(BASE_URL + '/transactions') + await waitPage(page, { selector: PAGE_READY }) + + // 进入批量操作模式,勾选前若干条账单 + if (!(await clickText(page, '批量操作'))) throw new Error('未找到「批量操作」按钮') + await page.waitForTimeout(500) + const checkboxes = page.locator('.n-data-table-tbody .n-checkbox') + const count = Math.min(await checkboxes.count(), 5) + for (let i = 0; i < count; i++) { + await checkboxes.nth(i).click({ force: true }).catch(() => undefined) + } + await page.waitForTimeout(300) + + // 点击「AI 生成规则」,等待弹窗与规则建议表(真实 AI 调用) + if (!(await clickText(page, 'AI 生成规则'))) throw new Error('未找到「AI 生成规则」按钮') + const modal = page.locator('.n-modal').first() + await modal.waitFor({ state: 'visible', timeout: 30000 }) + await modal.locator('.n-data-table-tbody tr').first().waitFor({ state: 'visible', timeout: 90000 }) + await page.waitForLoadState('networkidle', { timeout: 90000 }).catch(() => undefined) + await page.waitForTimeout(1000) + await modal.screenshot({ path: resolve(ELEM_DIR, 'ai-rule-transactions.png') }) + console.log('[OK] modal/ai-rule-transactions') + } catch (err) { + console.log(`[FAIL] modal/ai-rule-transactions: ${err}`) + } +} + /** * 截取系统设置的配置面板。 * @@ -641,6 +679,7 @@ async function main(): Promise { await captureEditRecord(page) await captureInlineCreate(page) await captureAiRule(page) + await captureAiRuleFromTransactions(page) await captureAiRecordImage(page) await captureBillDetail(page) await captureChannelAi(page)