首页 / ChatBar
页面路由:/(HomePage)
核心组件:ChatBar.jsx(AI 对话浮栏)
首页加载时
初始化用户信息
GET /api/auth/me
认证:必需(JWT Bearer Token)
后端处理:从 JWT 提取 userId → 查询 users 表 → 头像兜底生成
响应:
{
"user": {
"id": "string",
"phone": "string",
"nickname": "string",
"email": "string | null",
"avatar_url": "string",
"is_guest": 0 | 1,
"has_onboarded": 0 | 1,
"role": "user | admin | developer"
}
}
加载账单列表
GET /api/bills
认证:必需
后端处理:JOIN bills + bill_members + users(owner)→ 筛选当前用户是成员的账单 → 按 updated_at 倒序
响应:
{
"bills": [
{
"id": "uuid",
"name": "账单名",
"owner_phone": "string",
"owner_nickname": "string",
"role": "owner | editor | viewer",
"custom_prompt": "string | null"
}
]
}
加载最近活跃账单的详情
GET /api/bills/:id
认证:必需
触发条件:自动加载最近一次活跃账单的详情(从 localStorage 读取 lastActiveBillId)
后端处理:
1. 校验账单存在 + 用户是成员
2. 获取该账单所有成员
3. 获取该账单所有分类
响应:
{
"bill": { /* bills 表全部字段 */ },
"members": [ /* 成员列表 */ ],
"categories": [ /* 分类列表 */ ],
"myRole": "owner | editor | viewer",
"myPerms": { "can_edit": true, "can_delete": true }
}
加载账单交易记录
GET /api/transactions/:billId
认证:必需
后端处理:查询 transactions.bill_id = :billId → 按 tx_time ASC 排序
响应:
{
"transactions": [
{
"id": "string",
"category": "string(来自 CSV,非 category_id 关联)",
"tx_type": "支出 | 收入 | ...",
"amount": "integer(单位:分)",
"tx_time": "datetime"
}
],
"uploadMeta": { /* 最近一次 CSV 上传信息 */ }
}
切换账单时
触发条件:用户在下拉框中选择不同账单
调用:同「加载账单详情」GET /api/bills/:id,同时清空 ChatBar 消息历史
ChatBar AI 对话
页面加载 ChatBar 时
#### 加载 AI Keys(自动)
GET /api/apikeys/resolve-all/:billId
认证:必需
后端处理:查询该账单所有可用的 AI Key(个人 + 共享),按 priority 排序
响应:
{
"available": [
{
"id": "key_id",
"provider": "openai",
"model": "gpt-4o",
"owner_type": "user",
"owner_id": "user_id",
"is_self_owner": true
}
]
}
发送文本消息
触发条件:用户输入文字并点击发送(或按回车)
POST /api/ai/chat
认证:必需(Authorization Bearer Token)
Content-Type:application/json
参数:
| 参数 | 类型 | 说明 |
| billId | string | 当前账单 ID |
| provider | string | AI 提供商(openai/anthropic/gemini 等) |
| model | string | 模型名(可选,不传则自动选择) |
| owner_type | string | Key 类型(user) |
| owner_id | number | Key 所有者 ID |
| messages | array | 对话历史(最多10条,格式同 OpenAI) |
messages 格式:
[
{ "role": "system", "content": "你是一个助手..." },
{ "role": "user", "content": "分析我的账单" }
]
响应格式:SSE 流(Content-Type: text/event-stream)
data: {"content": "部分回复内容"}
data: [DONE]
错误响应:
data: [ERROR] AI 请求失败,请稍后重试
data: [DONE]
关键前端逻辑:
- System Prompt = 用户在该账单的
custom_prompt(默认空) - 最多携带最近10条消息(含 system)
- 120秒整体超时;60秒无数据则判为流中断
- 支持重试1次(
CHAT_MAX_RETRY = 1)
发送语音消息
前置条件:VOICE_INPUT_ENABLED = false(当前语音功能禁用)
POST /api/ai/transcribe
认证:必需
Content-Type:multipart/form-data
参数:
| 参数 | 类型 | 说明 |
| audio | File | 音频文件(WebM/PCM) |
后端处理:调用 Whisper API 转写音频 → 返回文本
响应:
{ "text": "转写后的文字" }
更新账单 Custom Prompt
触发条件:用户在 ChatBar 中开启 Prompt 模式并保存
PUT /api/bills/:billId/prompt
认证:必需
参数:
{ "prompt": "你是一个专业财务分析师..." }
后端处理:更新 bill_members.custom_prompt 字段
首页数据流全貌
进入首页
│
├── GET /auth/me → 获取用户信息
│
├── GET /bills → 获取账单列表
│
├── GET /bills/:id → 加载最近活跃账单详情
│ (含 members、categories)
│
├── GET /transactions/:billId → 加载交易记录
│
└── GET /apikeys/resolve-all/:billId → 加载可用 AI Keys
│
▼
ChatBar 渲染就绪
│
┌────────────────────────────┼────────────────────────────┐
▼ ▼ ▼
发送文本消息 发送语音消息 更新 Custom Prompt
POST /ai/chat POST /ai/transcribe PUT /bills/:id/prompt
(SSE 流式响应) (Whisper 转写) (保存 Prompt)
已知行为
- 金额单位:transactions 的
amount是整数(单位:分),展示时需/100 - category_id 为 NULL:CSV 导入不写
category_id,前端用category字符串字段展示分类名 - tx_year / tx_month:后端未生成这两个字段,始终为 NULL
- AI Key 兜底:若账单无任何 Key,ChatBar 无法发起对话