chore: devdocs 移出版本控制
设计/协作文档只保留在本地,不再随仓库分发。
This commit is contained in:
1 parent
253913ad82
commit
0587f5a45f
5 files changed
+3
-589
No files matched your search
@@ -7,3 +7,6 @@ local.properties
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
|
||||
# 本地设计/协作文档,不进仓库
|
||||
devdocs/
|
||||
@@ -1,90 +0,0 @@
|
||||
# 01 — 功能拆解与分步路线(Cyrene-For-Android 讨论稿 v1)
|
||||
|
||||
> 配套 `DESIGN.md`(总览)。本文按**分步**展开:每模块给出「做什么 / 交互 / 数据流 / 依赖 / 验收 / 边界 / Miuix 组件映射」。状态:**讨论稿**,与主人在本会话逐模块细化后定稿。
|
||||
> 前提(已定案):主界面=常规 AI APP;长按电源键=系统默认语音助手(VOICE_INTERACTION_SERVICE,全屏,无悬浮球);ASR/TTS 后端化;先纯文本版;UI 严格 Miuix(compose-miuix-ui)。
|
||||
|
||||
---
|
||||
|
||||
## 阶段 0 · 工程骨架
|
||||
**S0.1 工程搭建**
|
||||
- Gradle(版本目录)、Kotlin + Compose、`compose-miuix-ui` 依赖、`BuildConfig.BASE_URL`(debug `http://<host>:8080`,release 换 HTTPS)、单 Module `app`、包 `com.askaeth.cyrene.{app,core,data,domain,platform,ui}`。
|
||||
- 依赖:Retrofit+OkHttp、kotlinx-serialization、okhttp-WebSocket、EncryptedSharedPreferences、Hilt(可选)、Coil(图片)。
|
||||
- 验收:`./gradlew :app:assembleDebug` 通过,App 能起空壳 Miuix 首页。
|
||||
- 边界:暂不接后端。
|
||||
|
||||
## 阶段 1 · P0 纯文本聊天(零后端改动,契约现成)
|
||||
### S1.1 登录(已定稿 2026-09-08)
|
||||
- **服务器地址可配**(开源,各人部署地址不同):登录卡放「服务器地址」折叠项/入口,默认 `http://<host>:8080`,用户可改;存本地(EncryptedPrefs/DataStore)。
|
||||
- **仅 `.env` 单管理员**(`ADMIN_USERNAME/PASSWORD`)可登录,无注册/找回;成功 `role=owner`。
|
||||
- **自动登录**:默认记住 token(EncryptedPrefs),重启免登;仅 `401/auth_expired` 才清 token 回登录。
|
||||
- **报错**:**已建立连接**(服务端有响应)时用服务端 `{error.message}`;**本地错误**(连不上/证书/DNS/超时)**由客户端本地文案负责**——不把本机错误误当服务端文案。
|
||||
- **密码默认隐藏**(可切换明文)。
|
||||
- **数据流**:`AuthManager.login(baseUrl,username,password)` → `POST {base}/api/v1/auth/login` → `{token,expiresAt,role}` → `EncryptedPrefs` 存(baseUrl+token+role+expiresAt)→ `HomeScreen`。
|
||||
- **状态机**:`Idle→Loading→Success(Home)|Error(msg)`(`error.from==="server"` 用后端 `message`,`"local"` 用本地文案);登录中禁用按钮防连点;登录失败不清已存 token。
|
||||
|
||||
### S1.2 会话列表(已定稿 2026-09-08)
|
||||
- **主结构 = A**:启动**直接进主会话「与昔涟」(`web`) 聊天**;**顶栏汉堡 → 侧边栏**含【会话列表(切主/次级)+ IoT + App 设置 + 登出】。
|
||||
- **列表显示**:主会话="与昔涟",次级="会话 2/N"(按序号,title **自动**,零后端改动);无预览/时间(后端 `/sessions` 只有 id)。
|
||||
- **新建**:侧栏"+"新建次级(`POST /sessions` → `sessionId` → 切进去)。
|
||||
- **删除**:次级长按/滑出 → 确认 `Dialog` → `DELETE /sessions/{id}`;主会话禁删(提示"主会话不可删")。
|
||||
- **切换**:侧栏会话列表点选 → 切聊天页。
|
||||
- **数据流**:`GET /api/v1/sessions`(主会话 `web` 恒首位)。**依赖**:S1.1。**验收**:启动即主会话聊天;侧栏可切/建/删次级;主会话不可删;重启记住上次会话(可后续)。
|
||||
|
||||
### S1.3 聊天页:历史加载(已定稿 2026-09-08)
|
||||
- **数据流**:`GET /sessions/{id}/messages?limit=50&before=<ms>&platform=` **倒序**返回,客户端**逆序渲染**(最新在底)。**`platform` 不传 = 看全量主会话**(a,能同步 web/kook/微信所有主会话消息)。
|
||||
- **分页**:初始 50 条;**上拉**加载更多用 `before=<最早时间>` 再往前翻(**50 步进**),追加顶部。
|
||||
- **消息来源标识**:每条消息**默认显示来源**(`platform`/`deviceId` 小字角标,如"来自 web" / "来自 kook" / "来自微信" / "来自安卓(voiceassist)")——主人既定诉求。
|
||||
- **时间**:**完全隐藏**(不显示时间戳/分组,对话沉浸)。
|
||||
- **空态**:主会话无消息 → 昔涟欢迎语。
|
||||
- **依赖**:S1.2。**验收**:进会话拉 50 条倒序展示;上拉翻 50/页;每条标来源;无时间戳。
|
||||
|
||||
### S1.4 聊天页:发消息 + 流式(已定稿 2026-09-08)
|
||||
- **发消息**:WS `/ws/chat` 上行 `chat {sessionId,text,deviceId}`(**stream 流式**);本地乐观追加自己消息。
|
||||
- **流式渲染**:下行 `message.start` 开气泡 → `message.delta` **逐段追加**(气泡尾+光标)→ `message.end` 收口。
|
||||
- **流式期间新消息 = 两种模式(用户可切,默认「追加队列」)**:
|
||||
① **追加队列**(新消息进队列,流式结束后依次处理——豆包式);
|
||||
② **打断**(新消息触发 `interrupt`,保留残缺上下文 + 显示"打断"提示)。
|
||||
- **思维链 `thinking` / 工具卡 `tool_call`**:**默认展开**(方便观察思维;可折叠 + "总是收起"开关)。
|
||||
- **旁白 `<action>/<narration>`**:灰斜体小字,**按出现位置依附气泡上/下**。
|
||||
- **`mood`**:显示情绪标签(可关)。
|
||||
- **渲染**:`<p>` 拆气泡;图片;忙态 `session_busy/pending_*` 提示;`409/202` 兜底。
|
||||
|
||||
### S1.5 侧边栏(汉堡抽屉)
|
||||
- **做什么**:顶栏左上汉堡 → `Drawer` 侧边栏:当前会话/聊天、**[预留] IoT 入口**、**[预留] App 设置入口**、登出。
|
||||
- **数据流**:登出 → 清 token + 断开 WS → 回登录。
|
||||
- **验收**:汉堡展开侧栏;IoT/设置点开显示"规划中"(Miuix 空态);登出回登录页。
|
||||
- **边界**:侧栏入口预留(P0 占位不真做)。
|
||||
|
||||
### S1.6 401 / token 过期
|
||||
- **做什么**:REST 401 → `auth_expired` 清 token 停 WS → 回登录;WS 握手失败同理。
|
||||
- **验收**:JWT 过期或轮换后,下条请求自动登出重登,不无限 reconnect。
|
||||
|
||||
## 阶段 2 · P1 语音助手(后端 ASR/TTS,依赖本会话 TTS/ASR 进程接通后端)
|
||||
### S2.1 系统默认语音助手
|
||||
- **做什么**:Manifest 声明 `VOICE_INTERACTION_SERVICE`/`AssistantService`(`android:role="Assist"`),用户在系统「语音助手/电源键」设本 App 默认 → 长按电源键由系统拉起本 App `AssistantActivity`(全屏)。
|
||||
- **依赖**:系统设置入口(MIUI 自家更顺);Android 版本/厂商适配。
|
||||
- **验收**:长按电源键 → 本 App 全屏助手打开(无悬浮球)。
|
||||
|
||||
### S2.2 全屏助手 UI(AssistantActivity)
|
||||
- **做什么**:聆听动画 → 已识别文本(ASR 结果)→ 昔涟回复(流式)→ 重听/TTS;按钮:结束/放弃/再次说话。全程单页全屏,Miuix 玻璃质感。
|
||||
- **验收**:说话 → 文本上屏 → 昔涟回复 + 朗读。
|
||||
|
||||
### S2.3 语音闭环(后端 ASR/TTS)
|
||||
- **链路**:录音(`MediaRecorder`/`AudioRecord`)→ **REST 上传到后端**(音频→ASR)→ 文本 → `POST /api/v1/chat {text,deviceId}`(或 WS)→ 昔涟回复 → **后端 TTS** 返回音频 → 本端播放。`voice_output`(WS)也可拿音频。
|
||||
- **依赖**:**后端 ASR/TTS 真接通**(本会话 TTS/ASR 进程:真实 STT/TTS provider + REST 语音端点绕 4KB + `voice_input.deviceId`)。
|
||||
- **验收**:对话 3 轮语音一问一答;IM 渠道(微信/KOOK)语音也走同一后端 ASR/TTS。
|
||||
|
||||
## 阶段 3 · P2 智能
|
||||
- **IoT**:设备/场景列表(Miuix;预留接口 `GET /api/v1/iot/*`)。
|
||||
- **App 设置**:主题(Miuix 深/浅)、通知开关、语音偏好(ASR/TTS 后端已定)、关于。
|
||||
- **主动推送**:后端 proactive/backthink → 系统通知;前台服务保活(可选)。
|
||||
- **多角色**:`GET /personas` 只读展示;切换人格。
|
||||
- **记忆/子代理/自主思考**:只读面板(`memory`/`subagent`/`backthink` 过滤器)。
|
||||
|
||||
---
|
||||
|
||||
## 跨模块约定
|
||||
- **deviceId**:`voiceassist:myapp:v1`(design/16 §4.5),所有 `chat`/`voice_input` 携带 → 落库标来源、跨端可辨。
|
||||
- **主会话同步**:Android 加入 `web` 主会话,与 Web/IM 共享上下文;渲染按 `platform` 区分"本端/他端"(design/16 §4.4)。
|
||||
- **Miuix 映射**:`TopAppBar`(汉堡/标题)、`Scaffold`+`Drawer`(侧栏)、气泡(自定义 `BubbleView` 或 Miuix 卡片+毛玻璃)、`TextField`、`Button`(发送/语音)、`Snackbar`/`Dialog`(确认/错误)。**禁原生 Material**。
|
||||
- **安全红线**:token 加密存储、401 即登出、权限最小化、无悬浮球、不打日志敏感。
|
||||
@@ -1,207 +0,0 @@
|
||||
# 02 — 前后端协同事项(Cyrene-For-Android ⇄ Cyrene v2)
|
||||
|
||||
> **用途**:Android 端「要做/想做」,但**后端当前不支持、或契约未明确**的事项统一登记在这里。主人(AskaEth)按此去后端做适配与开发。
|
||||
> **状态机**:`待确认` → `已确认` → `已排期` → `已完成` / `不采纳`
|
||||
> **依据**:`Cyrene_v2/Docs/api-reference.md`、`Docs/api-client.md`、`DevDocs/design/12,14,15,16,21`(调研中发现多处文档与实现不一致,见 §E2)
|
||||
> **最后更新**:2026-09-08
|
||||
|
||||
---
|
||||
|
||||
## 登记规范(开发过程中持续维护)
|
||||
|
||||
**触发条件**——开发 / 联调中遇到以下任一情况,**当场**追加到本文并立即向主人反馈,不攒到最后:
|
||||
|
||||
1. 调用后端**直接失败**(501 / 404 / 500 / 字段缺失导致功能做不了);
|
||||
2. **契约或设计文档写了、但实现没做**(例如 `mode` 不落库、`voice_input` 不落库)——即「理论上应该支持但没有」;
|
||||
3. 后端行为与文档不符,导致客户端必须绕行。
|
||||
|
||||
**每条要写清**:
|
||||
|
||||
- **现象**:可复现的请求 / 响应或代码位置(`文件:行`);
|
||||
- **客户端为什么要**:不补会怎样;
|
||||
- **后端建议方案**:客户端期望的接口 / 字段 / 语义;
|
||||
- **当前绕行**:客户端现在怎么扛过去(没有就写「无,功能阻塞」);
|
||||
- **优先级**:阻塞 P0 / 影响体验 / P1 / P2 / 文档治理。
|
||||
|
||||
**流程**:`待确认` → 主人确认 → `已排期` → 后端完成 → `已完成`(注明后端版本 / commit);不做的标 `不采纳` 并写明理由。
|
||||
|
||||
**反馈节奏**:每轮开发结束时,在对话里列「本轮新增协同事项」(编号 + 一句话),主人直接照着修后端。
|
||||
|
||||
---
|
||||
|
||||
## A. 阻塞 P0 的(不解决就得改客户端设计)
|
||||
|
||||
### A1. 结构化流帧不带 `sessionId` → 多会话流式无法归属
|
||||
- **现象**:`message.start/delta/end`、`thinking.*`、`tool_call.*`、`tool_execution.update` 的 payload 只有 `{index}` / `{id,index,delta}`,**没有 `sessionId`**(`model.StreamEvent` 无会话上下文)。
|
||||
- **客户端为什么要**:WS 是全局一条连接,多会话同时流式时无法判断 delta 属于哪个会话,切会话会串台。
|
||||
- **后端建议**:这些帧补 `sessionId`,或至少补 `turnId`(客户端用 turnId 反查会话)。
|
||||
- **当前绕行**:客户端硬约束「同一时刻只有一个会话在流式」(已拍板接受)。
|
||||
- **优先级**:中高(有绕行,不阻塞 P0)
|
||||
|
||||
### A2. `interrupt` 清空 pending → 「打断」模式连发丢消息
|
||||
- **现象**:`interrupt {sessionId}` 取消在跑 turn **并清空 pending**,下行 `session_interrupted{dropped}`。
|
||||
- **客户端为什么要**:S1.4 定了「追加队列 / 打断」两种模式;用户选「打断」时连发多条会被静默丢弃。
|
||||
- **后端建议**:`interrupt` 增加 `scope`(`current` = 只取消当前 turn / `all` = 连 pending 一起清),默认维持现状。
|
||||
- **当前绕行**:打断模式下 UI 限制一次只能发一条 + 明确提示。
|
||||
- **优先级**:中
|
||||
|
||||
### A3. 消息 `mode` 不落库 → 渲染形态丢失
|
||||
- **现象**:design/16 §5.1 写「消息体带 `mode`」,但 Web 路径(`session_persist.go`)与 IM 路径(`channelbridge/persist.go`)**都不写 `Mode`**,字段 `omitempty` → 实际恒缺。
|
||||
- **客户端为什么要**:9 值渲染 mode(chat/code/home/work/docs/voice/vision/agents/settings)决定气泡/卡片形态,且「只有 chat 才解析 `<p>`」这条规则依赖它。
|
||||
- **后端建议**:持久化时写入 `mode`(design/16 已约定,属实现缺失)。
|
||||
- **当前绕行**:客户端一律回落 `chat`。
|
||||
- **优先级**:中
|
||||
|
||||
### A4. 会话 `title` 恒空 → 列表没名字
|
||||
- **现象**:`historySessionSummary.title` 有字段,但 `store.go` 恒写 `""`,无生成规则/接口。
|
||||
- **后端建议**:后端生成(截首条用户消息),或提供 `PATCH /sessions/{id}` 让客户端改名。
|
||||
- **当前绕行**:客户端本地生成「会话 N」+ 序号持久化。
|
||||
- **优先级**:低
|
||||
|
||||
### A5. WS 断线无补发 / 无 resume cursor
|
||||
- **现象**:无 `since` / `lastEventId`;`seq` 只用于排序,未规定可续传;重连仅有 `history` 回放主会话最近 50 条。
|
||||
- **客户端为什么要**:断线期间超过 50 条就永久丢失,只能整页重拉。
|
||||
- **后端建议**:`/ws/chat?since=<seq 或 createdAt>` 补发,或提供 `GET /sessions/{id}/messages?after=<ms>` 正序增量接口。
|
||||
- **当前绕行**:重连后 REST 按 `before` 补差 + 按 `id` 去重。
|
||||
- **优先级**:中
|
||||
|
||||
---
|
||||
|
||||
## B. 影响体验 / 客户端需绕行
|
||||
|
||||
### B1. 无图片上传端点 → 大图只能 base64 塞进 `chat.image`
|
||||
- **现象**:无 `/upload`;`image` 只接受 base64 data URI 或 http(s) URL;WS 上行帧限 **4096 字节**。
|
||||
- **客户端为什么要**:主人已定「发图先做着」,但没有上传端点就只能把压缩后的 base64 走 REST,大图会撑大请求体(WS 完全走不通)。
|
||||
- **后端建议**:`POST /api/v1/upload`(multipart)→ 返回 URL;`chat.image` 收 URL。
|
||||
- **当前绕行**:客户端压缩后再 base64,限制单图尺寸。
|
||||
- **优先级**:中高(主人已要求做发图)
|
||||
|
||||
### B2. token 24h 且无 refresh 端点
|
||||
- **现象**:`tokenTTL=24h`,无 refresh/renew;`rotate-jwt` 让全部 token 立即失效。
|
||||
- **客户端为什么要**:主人选了「静默重登」,但静默重登需要长期保存账号密码。
|
||||
- **后端建议**:提供 refresh token,或拉长 TTL + 支持「记住登录」的长期凭据。
|
||||
- **当前绕行**:存密码做静默重登(安全上不理想)。
|
||||
- **优先级**:中高
|
||||
|
||||
### B3. `GET /sessions` 信息太少(无未读、无最后一条预览)
|
||||
- **现象**:只有 `{id,type,title?,createdAt}`。
|
||||
- **客户端为什么要**:会话列表做不了预览和未读红点;多端同步时无法标记已读。
|
||||
- **后端建议**:补 `lastMessage{content,role,createdAt}`、`unreadCount`,或提供已读游标接口。
|
||||
- **优先级**:低(P0 按序号展示即可)
|
||||
|
||||
### B4. `platform` / `mood` / `mode` 取值未穷举
|
||||
- **现象**:文档只给示例(`web`/`telegram`/`weixin-ilink`/`qq-obv11`/`kook`;`calm`;`normal`)。
|
||||
- **客户端为什么要**:来源角标和情绪标签需要**稳定映射**(显示「来自微信」而不是原始字符串)。
|
||||
- **后端建议**:给出权威枚举表 + 未知值回落约定。
|
||||
- **当前绕行**:客户端维护映射表 + 未知值原样显示。
|
||||
- **优先级**:低
|
||||
|
||||
### B5. 管理员端点 5 个返回 501
|
||||
- **清单**:`POST /admin/users`、`POST /admin/iot/scenarios`、`POST /admin/iot/rules`、`POST /admin/plugins/install`、`POST /admin/rag/delete`。
|
||||
- **客户端为什么要**:管理员页面要决定这些入口做不做、做了怎么降级。
|
||||
- **当前绕行**:客户端硬编码「已知未实现」清单,入口置灰。
|
||||
- **优先级**:低
|
||||
|
||||
### B6. `proactive_message` 只在 `api-reference.md` 有,`api-client.md` 摘要缺失
|
||||
- **后端建议**:补进 `Docs/api-client.md`。
|
||||
- **优先级**:低(文档治理)
|
||||
|
||||
---
|
||||
|
||||
## C. P1 语音相关
|
||||
|
||||
### C1. `voice_input` 轮次不落库、不过编排器
|
||||
- **现象**:`runVoiceTurn` 直调 `runner.Run`,无 `history.AppendFull`、不经 `session_orchestrator` → **无 `message_persisted`、无 busy/pending、不跨端同步**;与 `api-client.md` §3.1「随语音轮次落库」不符。
|
||||
- **客户端为什么要**:语音问过的事必须进历史、跨端可见,否则在 Web 上查不到。
|
||||
- **后端建议**:把语音轮次接入编排器与持久化(与文本轮次同一路径),或在文档里明确「语音不留痕」。
|
||||
- **当前绕行(客户端已定)**:**不用 `voice_input` 提交对话**,改为 `REST /audio/transcribe` 转写 → **WS `chat`**(落库 + 流式 + 编排)→ `REST /audio/speech` 播放。
|
||||
- **优先级**:中
|
||||
|
||||
### C2. 语音降级到 silent 时客户端无法感知
|
||||
- **现状**:cloud 档已接阿里百炼(STT `qwen-audio-3.0-asr-flash` / TTS `cosyvoice-v3-flash`,需 `DASHSCOPE_API_KEY`);`edge-tts`/`espeak` 未实现;`local` 默认不注入;`silent` 兜底返回空音频。
|
||||
- **客户端为什么要**:没有 key 时语音会「静默成功但没声音」,客户端无法提示。
|
||||
- **后端建议**:降级到 silent 时给出可识别信号(响应头 `X-Voice-Provider: silent` 或专门错误码)。
|
||||
- **优先级**:中
|
||||
|
||||
### C3. `voice_output` 是否带 `sessionId` / `deviceId` 未规定
|
||||
- **客户端为什么要**:多端同时说话时无法区分归属。
|
||||
- **优先级**:低
|
||||
|
||||
---
|
||||
|
||||
## D. P2 / 路线缺失(客户端想做,后端尚无能力)
|
||||
|
||||
### D1. 移动端推送通道(系统通知)—— 最大缺口
|
||||
- **现象**:主动推送只有 WS `proactive_message`(仅 owner/admin 连接收到),**App 不在前台就收不到**。
|
||||
- **客户端为什么要**:design/21 §5 的 P2 明确要「主动推送 → 系统通知」,目前没有任何移动推送通道。
|
||||
- **后端建议**:接 FCM / 个推 / 自建长连接保活,并在 WS 之外提供推送投递。
|
||||
- **优先级**:P2,但属**路线级缺失**,建议早登记。
|
||||
|
||||
### D2. 消息搜索 / 会话搜索
|
||||
- **现象**:无搜索接口(`/admin/memories/search` 是记忆,不是消息)。
|
||||
- **优先级**:P2
|
||||
|
||||
### D3. 消息编辑 / 删除 / 重发
|
||||
- **现象**:无接口。
|
||||
- **优先级**:P2
|
||||
|
||||
### D4. 未读状态 / 已读游标
|
||||
- 见 B3。
|
||||
- **优先级**:P2
|
||||
|
||||
### D5. 会话改名 / 置顶 / 排序
|
||||
- 见 A4;`GET /sessions` 次级按 `created_at ASC`,改名/置顶需要新接口。
|
||||
- **优先级**:P2
|
||||
|
||||
---
|
||||
|
||||
## E. 文档与契约治理
|
||||
|
||||
### E1. `X-API-Version` 形同虚设
|
||||
- **现象**:服务端不校验该头;CORS `Allow-Headers` 只有 `Authorization, Content-Type`(未放开 `X-API-Version`)。
|
||||
- **建议**:要么真正校验,要么从契约里去掉,避免客户端误以为存在版本协商。
|
||||
|
||||
### E2. 文档与实现不一致(调研发现)
|
||||
| 事项 | 文档说法 | 实现 |
|
||||
|---|---|---|
|
||||
| 真实 ASR/TTS | design/21 §4「未接通」 | 已接百炼并实测 |
|
||||
| `voice_input.deviceId` | design/21「待补」 | 已有 |
|
||||
| REST 语音端点 | design/21「待新增」 | 已有 |
|
||||
| 语音轮次落库 | api-client「随轮次落库」 | 未落库(见 C1) |
|
||||
| 消息 `mode` | design/16 §5.1「消息体带 mode」 | 不落库(见 A3) |
|
||||
| 来源标识注入 | design/16 §4.6.1「文本前缀」 | 独立 system 消息 + 结构化 `SenderName` |
|
||||
- **建议**:以 `api-reference.md` + 代码为准,`api-client.md` 随实现同步(AGENTS.md 已有此红线)。
|
||||
|
||||
### E3. `thinking` 可达性确认
|
||||
- **现状**:WS 实时/回放已剥离,只有 `GET /sessions/{id}/messages`(owner/admin)返回。
|
||||
- **客户端影响**:历史消息的思维链需要额外请求。
|
||||
- **待确认**:这是有意为之(隐私)还是可优化?
|
||||
|
||||
---
|
||||
|
||||
## 附:状态一览
|
||||
|
||||
| 编号 | 事项 | 状态 | 优先级 |
|
||||
|---|---|---|---|
|
||||
| A1 | 流帧缺 `sessionId` | 待确认 | 中高 |
|
||||
| A2 | `interrupt` 清 pending | 待确认 | 中 |
|
||||
| A3 | `mode` 不落库 | 待确认 | 中 |
|
||||
| A4 | `title` 恒空 | 待确认 | 低 |
|
||||
| A5 | 无 resume cursor | 待确认 | 中 |
|
||||
| B1 | 无图片上传端点 | 待确认 | 中高 |
|
||||
| B2 | 无 token refresh | 待确认 | 中高 |
|
||||
| B3 | `sessions` 信息少 | 待确认 | 低 |
|
||||
| B4 | 枚举未穷举 | 待确认 | 低 |
|
||||
| B5 | admin 501 清单 | 待确认 | 低 |
|
||||
| B6 | `proactive_message` 文档缺失 | 待确认 | 低 |
|
||||
| C1 | 语音轮次不落库 | 待确认 | 中 |
|
||||
| C2 | 语音降级无信号 | 待确认 | 中 |
|
||||
| C3 | `voice_output` 字段 | 待确认 | 低 |
|
||||
| D1 | 无移动推送通道 | 待确认 | P2(路线缺失) |
|
||||
| D2 | 无消息搜索 | 待确认 | P2 |
|
||||
| D3 | 无编辑/删除/重发 | 待确认 | P2 |
|
||||
| D4 | 无未读状态 | 待确认 | P2 |
|
||||
| D5 | 会话改名/置顶 | 待确认 | P2 |
|
||||
| E1 | `X-API-Version` 虚设 | 待确认 | 低 |
|
||||
| E2 | 文档与实现不一致 | 待确认 | 中 |
|
||||
| E3 | `thinking` 可达性 | 待确认 | 低 |
|
||||
@@ -1,197 +0,0 @@
|
||||
# 03 — 客户端细化开发方案(Cyrene-For-Android)
|
||||
|
||||
> 配套:`DESIGN.md`(总览)、`01-功能拆解与分步路线.md`(分步路线)、`02-前后端协同事项.md`(后端缺口登记)
|
||||
> 本文是**决策固化 + 可施工的细化方案**,随讨论更新。
|
||||
> 最后更新:2026-09-08
|
||||
|
||||
---
|
||||
|
||||
## 0. 已定决策(截至 2026-09-08)
|
||||
|
||||
| # | 决策 | 备注 |
|
||||
|---|---|---|
|
||||
| D1 | 主界面 = 常规 AI APP(会话列表 + 聊天流) | |
|
||||
| D2 | 长按电源键 = 系统默认语音助手(`VOICE_INTERACTION_SERVICE`),全屏、无悬浮球 | **需先验 ROM 能否把本 App 设为默认助手** |
|
||||
| D3 | UI 严格 Miuix;**气泡自研、侧边栏自研** | Miuix 无 Drawer / 气泡组件 |
|
||||
| D4 | **minSdk 33** | 解锁 `miuix-blur`(硬下限 33) |
|
||||
| D5 | 先纯文本 P0,语音 P1 | |
|
||||
| D6 | 顶栏汉堡 → 侧边栏;含 IoT、App 设置、管理入口 | |
|
||||
| D7 | 服务器地址可配;单管理员登录;自动登录;401 登出 | |
|
||||
| D8 | 会话列表 = **本地回退快照 + 服务端良好时自动刷新** | §3.1 |
|
||||
| D9 | 消息历史缓存 = **方案 B**(每会话最近 50 条快照,LRU 5 个会话) | §3.2 |
|
||||
| D10 | 发图先做(REST `chat.image`,客户端压缩) | 后端无上传端点 → `02/B1` |
|
||||
| D11 | 历史思维链做(按需 REST `/messages`) | |
|
||||
| D12 | 静默重登 | 需存密码;后端无 refresh → `02/B2` |
|
||||
| D13 | 打断语义待后端适配 | `02/A2` |
|
||||
| D14 | 接受「同一时刻只有激活会话在流式」 | 流帧缺 `sessionId` → `02/A1` |
|
||||
| D15 | 管理功能独立管理员页,**不进聊天界面** | 渠道配置等归 Web 后台 |
|
||||
| D16 | 管理员页只做**只读监控**(概览 / 后台思考 / 记忆 / 人格) | 其余全部归 Web 后台 |
|
||||
| D17 | 会话标题本地生成「会话 N」+ 序号持久化 | 后端 `title` 恒空 → `02/A4` |
|
||||
| D18 | 会话列表回退时**显示上次同步时间** | §3.1 |
|
||||
| D19 | 配色用 **Miuix 默认配色**(`ColorSchemeMode.System`,跟随系统深浅色) | 不用 Monet 动态取色 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 工程骨架
|
||||
|
||||
- **版本钉死**(Miuix 0.9.3 牵引):Kotlin 2.4.0 / Compose Multiplatform 1.11.1 / AGP 9.2.1 / JDK 21。Miuix 以 `api` 暴露 `compose.foundation`,版本会被它牵制,不要自行升级。
|
||||
- **单 module `app`**,包结构 `com.askaeth.cyrene.{app,core,data,domain,platform,ui}`;包内分层,为 P2 拆 `:core:ui` / `:feature:chat` 留边界。
|
||||
- **DI:手写 `AppContainer`**(不引 Hilt;`miuix-nav` 与 Hilt 有已知冲突 issue #407)。
|
||||
- **minSdk 33 / compileSdk & targetSdk 跟 Miuix(37)**。
|
||||
- **网络**:Retrofit + OkHttp + kotlinx-serialization。
|
||||
- `X-API-Version: 1` 照发(服务端不校验,CORS 也未放开该头 → `02/E1`)。
|
||||
- debug 用 `networkSecurityConfig` 放行明文 HTTP;自部署支持**用户显式信任单个自签服务器**,绝不全局信任。
|
||||
- 超时:连接 10s / 读 60s(LLM 慢);WS `pingInterval(30s)`。
|
||||
- `HttpLoggingInterceptor` 仅 debug,**脱敏 `Authorization` 与 image base64**。
|
||||
- **R8**:`@Serializable` 类与 Retrofit 泛型签名保留规则。
|
||||
- **`BASE_URL` 只是默认值**,真实服务器地址运行时用户可改(D7)。
|
||||
- **主题**:`MiuixTheme(controller)`,默认 `MonetSystem`;设置页提供 跟随系统 / 浅色 / 深色 / 取色。
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据层与契约封装
|
||||
|
||||
- **DTO 不用 enum**:`mode` / `platform` / `mood` / `role` / `stopReason` / `error.code` 后端全部「未穷举」。用 `String` + 客户端映射表,未知值走默认(`mode` → `chat`)。`ignoreUnknownKeys = true`,可选字段全 nullable。
|
||||
- **统一错误模型**:`Server(code, message, httpStatus)` vs `Local(kind)`(无网 / DNS / TLS / 超时 / 解析)。S1.1 已定「已建连接用服务端文案,本地错误用本地文案」;401 单独一类,触发全局登出。
|
||||
- **分页与去重**:WS 首帧 `history` 与 REST 首屏**必然重叠**,且文档未规定 `history` 排序。规则:以 `id` 为唯一键 upsert;本地按 `createdAt` 排序(同毫秒用 `id` 兜底);`history` 到达时只 merge 不重置;次级会话无回放,一律走 REST。
|
||||
- **乐观消息合并**:上行 `chat` **无 clientMsgId**,`reqId` 服务端不回填 → 靠「同 role + 时间窗 + 文本匹配」把本地临时气泡归一为后端 `id`;匹配不上则保留两条(宁重复不丢)。
|
||||
- **deviceId**:首次生成后持久化,形如 `voiceassist:cyrene:v1`,所有 `chat` / `voice_input` 携带。
|
||||
- **token**:存 `token` / `expiresAt` / `role` / `baseUrl` 到 EncryptedSharedPreferences;24h 过期、无 refresh(`02/B2`),采用静默重登(D12);运行中 401 → 清 token、断 WS、回登录。
|
||||
- **契约漂移防护**:维护 `CONTRACT.md` 记录「客户端实际依赖的字段与行为 + 对应后端版本」;关键 JSON 用 MockWebServer 写契约测试。
|
||||
|
||||
---
|
||||
|
||||
## 3. 本地持久化
|
||||
|
||||
### 3.1 会话列表回退快照(D8)
|
||||
|
||||
**目标**:网络波动时列表不白屏;服务端可用时以服务端为准。
|
||||
|
||||
- **快照内容**:`List<SessionSummary>`(`id` / `type` / `title` / `createdAt`)+ `fetchedAt` + **指纹**(`baseUrl` + `username`)。
|
||||
- **存储**:app 私有目录单文件 `sessions.json`(或 DataStore)。
|
||||
- **启动流程**:读快照 → 立即渲染 → 并发 `GET /sessions` → 成功后整体替换并回写快照。
|
||||
- **失败流程**:保留快照,列表顶部显示轻量条「网络异常,显示上次结果 · 同步于 <相对时间>」+ 重试。相对时间由本地 `fetchedAt` 算(刚刚 / N 分钟前 / N 小时前 / 日期),不依赖服务端时间。
|
||||
- **合并策略**:**服务端结果整体替换,不做 merge**(避免已删除会话变幽灵)。服务端请求失败时**不清空快照**。
|
||||
- **指纹不匹配**(换了服务器地址 / 账号)→ 丢弃快照。
|
||||
- **排序**:主会话恒首位,次级按 `createdAt ASC`(与后端一致)。
|
||||
- **当前会话 id** 一并持久化,冷启动直接进上次会话。
|
||||
- **序号映射** `sessionId → N` 独立持久化:新建时取「当前最大序号 + 1」,删除会话时同步删映射。
|
||||
- **写入时机**:每次成功拉取后;新建 / 删除后以服务端为准回写。
|
||||
|
||||
### 3.2 消息历史快照 —— 方案 B(D9)
|
||||
|
||||
- **粒度**:每会话一个文件,文件名用 `sessionId` 的 hash(`web:<uuid>` 不能直接做文件名)。
|
||||
- **内容**:最近 50 条 `historyMessage`(`id`/`role`/`content`/`mode?`/`mood?`/`image?`/`platform?`/`deviceId?`/`senderName?`/`createdAt`)。**不含 `thinking`**(WS 与列表接口都拿不到)。
|
||||
- **LRU**:最多保留 **5 个会话** 的文件,按最后访问时间淘汰。
|
||||
- **读取**:进会话 → 立即渲染快照 → 并发拉 REST 最新 50 条 → 按 `id` 合并(服务端优先)→ 回写。
|
||||
- **写入**:收到 `message_persisted` / 分页结果后**节流写**(500ms debounce),避免流式期间频繁 IO;流式中的临时气泡不落盘,只落 `message_persisted` 的最终态。
|
||||
- **大小上限**:单文件 2MB,超出只保留最近 20 条。
|
||||
- **图片字段**:**base64 data URI 不落盘**(体积不可控),只保留 http(s) URL;内存中可正常显示。
|
||||
- **清除时机**:登出、切换服务器地址、指纹不匹配时清空全部缓存。
|
||||
- **隐私**:文件放 app 私有目录 + `android:allowBackup="false"`(或 backup rules 排除)。若要更强,用与 EncryptedSharedPreferences 同族的 `EncryptedFile`。
|
||||
|
||||
### 3.3 其他 DataStore 项
|
||||
|
||||
| 数据 | 用途 |
|
||||
|---|---|
|
||||
| `deviceId` | 跨会话稳定标识 |
|
||||
| 会话序号映射 | D17 |
|
||||
| 用户偏好 | 主题、旁白开关、mood 显示、思维链默认展开、打断模式、通知开关 |
|
||||
| 每会话草稿 | 防进程被杀丢输入 |
|
||||
| 上次会话 id | 冷启动直达 |
|
||||
|
||||
### 3.4 不做的事
|
||||
|
||||
- **离线待发队列**:P0 不做,发送失败标红重试、内容留在输入框。
|
||||
- **WS `seq` 游标持久化**:后端无续传机制,存了没用。
|
||||
|
||||
---
|
||||
|
||||
## 4. WS 实时通道
|
||||
|
||||
- **连接状态机**:`Idle → Connecting → Connected → Reconnecting(退避 1/2/4…30s + 抖动) → Offline(需登录)`。握手 401 不再重试,直接登出。
|
||||
- **重连补全**:无 resume cursor(`02/A5`)→ 重连后主会话用新连接的 `history` + REST 补差(`before = 本地最后一条 createdAt`),次级会话走 REST,全程按 `id` 去重。
|
||||
- **事件路由**:带 `sessionId` 的按会话分发;**不带 `sessionId` 的**(`message.*` / `thinking.*` / `tool_call.*`)归属「当前 active turn」→ 因此**硬约束同一时刻只有一个会话在流式**(D14)。切会话时若正在流式:等结束或 `interrupt`。
|
||||
- **流式段落状态机**:`index` 编号规则文档未定义 → 按首次出现顺序建段,`start` 建 / `delta` 追加 / `end` 收口;`stream_end` 才算整轮结束。要容忍丢帧(服务端发送缓冲 32,满则丢)。
|
||||
- **忙碌/队列 UI 映射**:`session_busy{mode}` → 轻状态条;`pending_ack{queueDepth}` → 「已排队 N 条」;`pending_full{max}` → 队列满(默认 5);`session_interrupted{dropped}` → 「已打断,丢弃 N 条」。注意编排 `mode`(`idle/thinking/executing/subagent`)与消息渲染 `mode` 是两个命名空间。
|
||||
- **打断模式**:后端 `interrupt` 会清空 pending(`02/A2`)→ 该模式下 UI 限制一次只发一条并明确提示。
|
||||
|
||||
---
|
||||
|
||||
## 5. 渲染管线
|
||||
|
||||
- **解析器**:纯函数、可单测,`parse(raw): List<ContentPart>`。权威规则在 `configs/personas/cyrene.yaml`。
|
||||
- `<p>…</p>` 每段一个气泡;无 `<p>` 时按空行 `\n\n` 回退切分,单个 `\n` 不切。
|
||||
- `<action>` / `<narration>` → 灰斜体旁白,依附气泡:首部连续块在上、尾部连续块在下、中间块上提到 head。
|
||||
- 兼容漂移:全角 `*…*`(短于 40 字符且无换行 → action);半角 `*…*` **仅当以 动作/旁白/内心/神态/轻声 开头**才算旁白,否则保持普通文本。
|
||||
- **流式增量解析**:delta 会把标签切半(`<act` + `ion>`)→ 带缓冲的增量解析器,保留尾部未完成片段,只在能确定边界时产出;未闭合 `<p>` 不显示直到闭合。行为对齐 Web 端 `parseContent.ts` + `chat.tsx`。
|
||||
- **mode 回落**:后端不落库(`02/A3`)→ P0 全部按 `chat` 渲染。
|
||||
- **thinking / tool_call**:仅 WS 实时有;历史思维链按需请求 REST `/messages`(D11)。工具卡把 `tool_call.delta`(参数 JSON)与 `tool_execution.update`(输出)分两块渲染。
|
||||
- **图片**:Coil 加载;base64 先读尺寸再决定是否解码,避免 OOM。
|
||||
- **Markdown**:P0 不渲染,代码只给等宽字体 + 横向滚动。
|
||||
|
||||
---
|
||||
|
||||
## 6. UI / Miuix 落地
|
||||
|
||||
| 需求 | 方案 |
|
||||
|---|---|
|
||||
| 顶栏 + 汉堡 | `SmallTopAppBar(navigationIcon = { IconButton { Icon(MiuixIcons.Sidebar) } })` |
|
||||
| 侧边栏 | **自研 `MiuixDrawer`**:`AnchoredDraggableState` + scrim + `Modifier.offset` lambda 版;汉堡开、抽屉内拖拽关;**不做左边缘滑出**(与系统返回手势冲突);`BackHandler` 关抽屉 |
|
||||
| 气泡 | **自研**:`Surface` / `Card` + `RoundedCornerShape`,最大宽度 `min(0.78 × 屏宽, 600dp)` |
|
||||
| 列表 | `LazyColumn`(Foundation)+ `overScrollVertical()` + `VerticalScrollBar`,`key = id` |
|
||||
| 输入 / 按钮 / 设置 / 弹窗 / 提示 / 加载 | `TextField` / `IconButton`+`FAB` / 全套 `*Preference` / `OverlayDialog`(须在 `Scaffold` 内)/ `Snackbar`+Host / `ProgressIndicator` |
|
||||
| IME | Miuix `Scaffold` 默认只处理 `systemBars ∪ displayCutout`,需自行 `imePadding()` |
|
||||
| 毛玻璃 | 只用在顶栏 / 底栏,不大面积铺;<33 已在 D4 排除 |
|
||||
|
||||
**侧边栏信息架构**
|
||||
|
||||
```
|
||||
├─ 会话(主 / 次级)
|
||||
├─ IoT(预留)
|
||||
├─ App 设置
|
||||
├─ 管理(owner 可见)→ 独立导航图 + 独立 Scaffold,只读监控
|
||||
│ ├─ 概览(metrics / trace)
|
||||
│ ├─ 后台思考
|
||||
│ ├─ 记忆
|
||||
│ └─ 人格
|
||||
└─ 关于 / 登出
|
||||
```
|
||||
|
||||
- **管理员页范围**(D15/D16):只做**只读监控**;渠道配置、LLM、RAG、插件、用户、自动化、系统危险操作全部归 **Web 后台**。危险操作即使将来做也必须 `OverlayDialog` 二次确认。
|
||||
- P0 管理员页只放入口占位(显示「规划中」),P2 再实现。
|
||||
|
||||
---
|
||||
|
||||
## 7. P1 语音(依赖后端)
|
||||
|
||||
- **第一件事是 spike,不是写 UI**:`VOICE_INTERACTION_SERVICE` 只保证系统「助手入口」能指向本 App;**MIUI 是否允许把第三方设为默认助手、长按电源键能否唤起**必须真机验证(不写代码,10 分钟)。验不过就换触发:系统助手手势 / 桌面快捷方式 / 通知磁贴 / 蓝牙耳机键 / App 内按钮。
|
||||
- **链路(已按后端现状修正)**:`AudioRecord` 录音 → WAV → `POST /audio/transcribe`(REST,绕 4KB)→ 文本 → **WS `chat`**(落库 + 流式 + 编排)→ 回复 → `POST /audio/speech`(**返回裸音频字节,非 `{data}` 信封**)→ `AudioTrack`/ExoPlayer 播放,支持打断。
|
||||
- **不用 `voice_input` 提交对话**:该路径不落库、不过编排器、无 `message_persisted`(`02/C1`)。
|
||||
- 权限:`RECORD_AUDIO` 运行时申请;录音期间可选前台服务。
|
||||
|
||||
---
|
||||
|
||||
## 8. 里程碑与验收
|
||||
|
||||
| 里程碑 | 内容 | 验收 |
|
||||
|---|---|---|
|
||||
| **M0 骨架** | Gradle + 依赖 + Miuix 主题 + 空壳首页 | `./gradlew :app:assembleDebug` 通过,App 起得来 |
|
||||
| **M1 登录** | 服务器地址可配 + 错误分类 + EncryptedPrefs + 自动登录 + 401 登出 | 错地址有本地文案、对地址能进;杀进程重开免登;token 失效自动回登录 |
|
||||
| **M2 只读主会话** | WS 连接 + `history` + REST 分页 + 倒序渲染 + 来源角标 + 会话列表回退 | 断网重开列表不白屏;进主会话看到历史;上拉翻页无重复;每条标来源 |
|
||||
| **M3 发送 + 流式** | `chat` 上行 + 乐观消息 + 流式气泡 + 忙碌/队列状态 + 失败重试 | 发消息立即上屏;流式逐字出;Web 端同步可见;忙时提示排队 |
|
||||
| **M4 富渲染** | `<p>` 拆气泡 + 旁白 + thinking/工具卡 + 图片 | 多气泡与旁白位置正确;流式半截标签不崩;图片正常显示 |
|
||||
| **M5 多会话 + 侧边栏** | 自研 Drawer + 新建/切换/删除 + 序号持久化 | 汉堡开合顺滑;次级可建可删;主会话禁删;重启记得上次会话 |
|
||||
| **M6 打磨** | 设置页、空态/错误态、无障碍、性能 | 字体放大不炸;冷启动 < 1.5s;无内存泄漏 |
|
||||
|
||||
**P1**:语音 spike → 全屏助手 UI → 语音闭环。
|
||||
**P2**:管理员页只读监控、IoT、主动推送(依赖 `02/D1`)、多角色。
|
||||
|
||||
---
|
||||
|
||||
## 9. 待定项
|
||||
|
||||
1. 消息历史快照是否改用 `EncryptedFile`(当前建议:私有目录 + 关备份,P0 够用)。
|
||||
2. 管理员页 P0 是否只占位(当前建议:是)。
|
||||
3. `CONTRACT.md` 是否现在就建(建议 M2 之前建)。
|
||||
4. 语音 spike 的具体验证清单与机器。
|
||||
@@ -1,95 +0,0 @@
|
||||
# Cyrene-For-Android — Cyrene 安卓客户端设计文档(超详细讨论稿 v1)
|
||||
|
||||
> 配套 Cyrene v2(git.yeij.top/AskaEth/Cyrene_v2)的 Android 客户端。
|
||||
> **主人已确认的关键决策**(2026-09-08):
|
||||
> - 主界面 = **常规 AI APP 风格**(会话列表 + 聊天流)。
|
||||
> - **长按电源键唤醒 = 全屏语音助手**(系统级语音助手交互),**不要悬浮球**。
|
||||
> - UI **严格遵守 MIUIx / Miuix 设计风格**:官网 https://compose-miuix-ui.github.io/miuix/zh_CN/ ,组件库 https://compose-miuix-ui.github.io/miuix/zh_CN/components/ 。
|
||||
> - **先做纯文本版本**(P0);语音(P1)后续。
|
||||
> - 顶栏左上角**汉堡菜单**展开**侧边栏**,**预留 IoT 入口 + App 设置页入口**。
|
||||
> - 后端契约详见 `Cyrene v2` 的 `Docs/api-reference.md` + `DevDocs/design/21-安卓app语音助手.md`(本文件为端侧细化)。
|
||||
> 命名:仓库作者 `AskaEth`,日常名 `YeiJi / 叶酱`;App 名 `Cyrene`,角色 `昔涟`。
|
||||
|
||||
---
|
||||
|
||||
## 0. 目录与工程形态
|
||||
- **单 Module**:`app`(Kotlin + Jetpack Compose),Gradle 版本目录。
|
||||
- 依赖:`compose-miuix-ui`(Miuix 组件)、`androidx.lifecycle.viewmodel-compose`、`Retrofit + OkHttp + kotlinx-serialization`、`okhttp`(WebSocket)、`EncryptedSharedPreferences`(JWT)、`hilt`(DI,可选)。
|
||||
- `BuildConfig`:`BASE_URL`(默认可 `http://<host>:8080`,release 换 HTTPS)。
|
||||
- 包结构:`com.askaeth.cyrene.{app,core,data,domain,platform,ui}`(`app`=Activity/导航;`core`=Miuix 主题/Auth;`data`=Retrofit/WS/仓库;`domain`=会话/消息模型;`platform`=语音/权限/电源键;`ui`=各 Screen/组件)。
|
||||
|
||||
## 1. 总体信息架构(功能分类)
|
||||
```
|
||||
Cyrene-For-Android
|
||||
├─ A. 聊天(主界面,AI APP 风格)
|
||||
│ ├─ 会话列表:主会话「与昔涟」(web) + 次级会话(web:<uuid>),可新建/删除/恢复
|
||||
│ └─ 聊天页:气泡流(<p>多气泡、<action>/<narration> 旁白灰斜体仅chat、思维链/工具卡折叠、图片)、输入框+发送、语音按钮(P0占位)
|
||||
├─ B. 侧边栏(汉堡抽屉)
|
||||
│ ├─ 会话/聊天
|
||||
│ ├─ [预留] IoT(设备/场景)入口
|
||||
│ ├─ [预留] App 设置入口
|
||||
│ ├─ [可选] 记忆 / 子代理 / 自主思考(只读)
|
||||
│ └─ 关于 / 登出
|
||||
├─ C. 语音助手(长按电源键唤醒,P1)— 全屏,无悬浮球
|
||||
│ ├─ 展开: 聆听动画 → 已识别文本 → 昔涟回复 → 再听/TTS
|
||||
│ └─ 系统级: AccessibilityService/系统语音助手回调 → 启动助手 Activity(全屏)
|
||||
├─ D. [预留] IoT:设备/场景列表(占位)
|
||||
└─ E. [预留] App 设置:主题(Miuix 深/浅)、通知、语音偏好(ASR/TTS 端侧 vs 后端)、关于
|
||||
```
|
||||
|
||||
## 2. 页面路由(单 Activity + 导航)
|
||||
- `LoginScreen`(首启无 token)→ `HomeScreen`(会话列表,含顶栏汉堡)→ `ChatScreen({sessionId})`(聊天)
|
||||
- `AssistantActivity`(P1 全屏语音助手,长按电源键拉起;无悬浮球,就一个全屏页)
|
||||
- `IotScreen` / `SettingsScreen`(占位页,P0 展示"规划中")
|
||||
|
||||
## 3. 数据层(对照现有契约,P0 纯文本即用)
|
||||
- **认证**:`POST /api/v1/auth/login {username,password}` → `{token,expiresAt,role}`(`role=owner`,单账号)。JWT `Authorization: Bearer`+`X-API-Version: 1`;`EncryptedSharedPreferences` 存 token;**REST 401 → `auth_expired` 清 token → 回登录**;WS 握手失败同理。
|
||||
- **会话**:`GET /api/v1/sessions`(主会话 `id="web"` 恒在);`POST /api/v1/sessions` 建次级;`GET /sessions/{id}/messages?limit=50&before=<ms>&platform=`(**倒序热分页**,`platform=` 过滤);`DELETE /sessions/{id}`(主会话删=403)。
|
||||
- **发消息**:`POST /api/v1/chat {sessionId,text,deviceId,stream?}`(`stream:true`→转 WS;忙时 `202 {queued,queueDepth}`、队满 `409 chat_busy`)。
|
||||
- **WS** `/ws/chat?token=`:上行 `chat`/`interrupt`;下行 `message.start|delta|end`、`thinking.*`、`tool_call.*`、`message_persisted`、`history`(握手后首帧主会话最近 50 条)、`mood`、`session_busy`/`pending_*`/`session_idle`、`voice_output`(P1)。`message_persisted`/`history` 载荷为 `historyMessage`。
|
||||
- **消息 DTO**:`{id,sessionId,role,content,mode?,mood?,image?,thinking?,platform?,deviceId?,senderName?,adapterId?,createdAt}`;`thinking` 仅 owner 可见;主流 `mode` 有 `chat`(含 `<p>`/`<action>/<narration>` 需端侧解析)。
|
||||
- **deviceId**:`voiceassist:myapp:v1`(design/16 §4.5 允许自定可读前缀),`POST /chat`/ws `chat` 携带 → 落库标注来源;主会话 `web` 与 Web 共享上下文(multiplatform 同步)。
|
||||
|
||||
## 4. 消息渲染(聊天页核心)
|
||||
- 按 `mode` 决定形态:`chat` 用气泡;其它 `mode`(backthink/工具/审计等 refs)卡片化或折叠。
|
||||
- **`chat` 文本解析**:把 content 按 **`<p>`** 拆成多条气泡(每段一条);`<action>…</action>`/`<narration>…</narration>` 解析为**灰斜体小字旁白**(依附气泡,不独立成气泡);`*…*`/`*…*` 兼容。**非 chat 消息不解析**这些标签。
|
||||
- `thinking`(仅 owner):可折叠「思维链」面板;`tool_call` →「工具卡」(折叠,可看参数/结果)。
|
||||
- 流式:`message.delta` 追加到当前气泡末尾 + 光标;`message.end` 收口。
|
||||
- 图片:`image`(base64 data: URI 或 URL)→ Coil 加载。
|
||||
|
||||
## 5. 语音助手(P1,长按电源键 全屏,无悬浮球)— 端侧与后端协同
|
||||
- **触发**:① 系统级语音助手回调(`VOICE_INTERACTION` / `AssistantService`)或 ② `AccessibilityService` 监听 `KEYCODE_POWER` **长按**(Android 对电源键长按有限制,需在系统「电源键双击/长按」设置里把本 App 设为语音助手,或通过系统辅助「voice assistant」入口)。落地:**声明 `VOICE_INTERACTION_SERVICE`**,长按电源键由系统拉起本 App 的 `AssistantActivity`(全屏)。
|
||||
- **链路**:`AssistantActivity` 聆听(`MediaRecorder`/`AudioRecord`)→ 录音交给 **ASR**(**端侧 or 后端**,见开放问题;后端当前 ASR/TTS 是占位,需先补 design/21 后端补强或端侧 whisper)→ 出文本 → `POST /api/v1/chat {text,deviceId}`(或 WS 流式)→ 昔涟回复 → **TTS** 合成朗读(端侧 e.g. `TextToSpeech` 或后端)。全程单页无悬浮球。
|
||||
- **保活/后台**:可选前台服务(语音持续/推送),权限最小化;无需悬浮球全局浮层。
|
||||
|
||||
## 6. MIUIx / Miuix 映射(紧跟 compose-miuix-ui)
|
||||
- 用 `compose-miuix-ui` 的 `MiuixTheme` + 组件:`TopAppBar`(汉堡/标题)、`Scaffold`/`Drawer`(侧边栏)、气泡用 `BubbleView`/卡片、`TextField`(输入)、`Button`(发送)、`Switch`/`List`(设置)、`Snackbar`/`Dialog`。**禁用原生 Material 组件**,全 Miuix 风格(圆角、毛玻璃、液态玻璃、强调色)。
|
||||
- 顶栏左上汉堡 → 侧边栏(`Drawer`);侧边栏含「IoT」「App 设置」占位入口(P0 点开显示"规划中")。
|
||||
|
||||
## 7. P0(纯文本)MVP 任务清单
|
||||
1. 工程搭建:Gradle + Compose + Miuix 依赖 + `BuildConfig.BASE_URL` + Hilt(可选)。
|
||||
2. `core`:Miuix 主题、AuthManager(EncryptedPrefs 存 JWT、401→auth_expired→登出)。
|
||||
3. `data`:Retrofit(login/sessions/messages/chat)、WS `ChatSocket`(`/ws/chat?token=`,事件分发到 StateFlow)、`ApiClient`(`Authorization: Bearer`+`X-API-Version`)。
|
||||
4. `domain`:`Conference`/`ChatMessage` 模型、`HistoryMessage` 映射、`<p>`/`<action>/<narration>` 解析器。
|
||||
5. `ui`:`LoginScreen` → `HomeScreen`(会话列表)→ `ChatScreen`(气泡流 + 流式 + 思维链/工具卡 + 输入框)。顶栏汉堡侧边栏(IoT/设置/登出)。
|
||||
6. 设备标识:`deviceId=voiceassist:myapp:v1`。
|
||||
7. 验证:登录 → 拉主会话历史 → 发消息 → WS 流式收到回复 → 跨端(web/微信)同步显示。
|
||||
|
||||
## 8. 长线 roadmap
|
||||
- **P0(纯文本)**:⑦ 完成 → 可日常聊天,多端同步。
|
||||
- **P1(语音助手)**:后端补 ASR/TTS(design/21)→ 电源键长按唤起全屏助手 → 聆听/ASR/chat/TTS 朗读闭环 → 无悬浮球。
|
||||
- **P2(智能)**:IoT 入口真实现(设备/场景)、App 设置功能化(主题/通知/语音偏好)、主动推送 + 系统通知、多角色(persona)切换、记忆/子代理/自主思考只读面板、多会话管理增强。
|
||||
|
||||
## 9. 开放问题(待与主人讨论 / 后续敲定)
|
||||
1. **[已定案] 长按电源键**:设置 App 为**系统默认语音助手**(`VOICE_INTERACTION_SERVICE` / `AssistantService`),由系统在长按电源键(或系统语音助手入口)时拉起本 App 的 `AssistantActivity`(全屏),无悬浮球。厂商(MIUI)自家更顺,需在系统「语音助手 / 电源键」设置里把本 App 设为默认。
|
||||
2. **[已定案] ASR/TTS 后端化**:**不在端侧**——因第三方适配器(微信 / KOOK 等 IM 渠道语音)**无法在端侧实现**,且为大后期**语音 / 视频通话**打基础。语音闭环统一走后端真实 ASR / TTS。**依赖**:后端 `voice` 链路当前是降级占位(cloud endpoint 空 → 降级、silent 空音频),需先接通真实 ASR / TTS(本会话 TTS/ASR 进程在做)。Android 语音 P1 依赖这项完成后接入。
|
||||
3. **Miuix 组件精确映射**:到 `components` 页逐组件对照,列出"我们用哪个"。
|
||||
4. **多角色**:现在登录恒 owner,暂无双角色切换;`GET /personas` 可只读展示人格列表。
|
||||
5. **会话管理 UI**:次级会话的新建/切换/删除入口。
|
||||
6. **Hilt vs 手写 DI**:视复杂度定。
|
||||
|
||||
## 10. 红线(必须遵守)
|
||||
- JWT 存储加密(EncryptedPrefs),不落明文/不打日志;401 即登出。
|
||||
- 身份靠 actor 元数据 + owner.yaml 白名单(登录即 owner),不靠提示词。
|
||||
- 语音/音频:权限最小化、显式授权;**不做悬浮球全局浮层**。
|
||||
- 严格 Miuix,禁原生 Material;`Docs/api-reference.md` 契约随实现更新。
|
||||
Reference in new issue
Block a user