新增: 小程序端跨端AI对话

This commit is contained in:
2026-06-24 00:19:54 +08:00
parent 0c7c5e6068
commit 698d981962
20 changed files with 5647 additions and 1242 deletions

View File

@@ -55,6 +55,8 @@ use serde::{Deserialize, Serialize};
// RiskLevel 不在此 import:PendingApproval.risk_level 删后 mod.rs 不再用(audit 流程在 audit.rs 自 import)
use df_ai::context::ContextManager;
use df_ai::context::ContextConfig;
// F-#95 扩展:AiMessageHistory 事件载荷 Vec<ChatMessage>(历史消息同步)。
use df_ai::provider::ChatMessage;
// ============================================================
// 重导出 — 保路径不变(state.rs / lib.rs / knowledge.rs 在用)
@@ -145,6 +147,24 @@ pub enum AiChatEvent {
error_type: Option<ErrorType>,
conversation_id: Option<String>,
},
/// F-260622-02 跨端用户消息同步(miniapp→device→桌面 user 气泡渲染):
///
/// 触发:remote_bridge `route_send_message` 放行后(调 ai_chat_send 前)`publish_event`,
/// 把 miniapp 发来的用户消息广播到 ai_event_bus。语义:远程入口(miniapp)发来的用户消息,
/// 专供桌面 useAiEvents 收到并补 user 气泡(桌面本地 sendMessage 是乐观渲染不发此事件)。
///
/// 根因(为何需要此事件):ai_chat_send 内部只 push session.messages,全程不 emit 用户消息,
/// 桌面用户气泡是纯本地乐观渲染(useAiSend.sendMessage 本地 push,不依赖事件)。
/// miniapp 发消息时桌面用户没在自己输入框敲字 → 桌面 store 没有这条 user 气泡 →
/// 桌面只看到 assistant 响应气泡(经 AiAgentRound/AiTextDelta 流式建),前面缺对应 user prompt
/// → **孤立响应气泡**。此事件填补该缺口。
///
/// 去重(防双气泡):前端 handleEvent 收到此事件时,若末条已是同 content 的 user(本地乐观
/// push 的),跳过。miniapp 自身乐观 push 也会收到此事件回灌,同样去重处理。
AiUserMessage {
message: String,
conversation_id: Option<String>,
},
/// Agent 循环新一轮(前端需新建 assistant 消息)
AiAgentRound { round: u32, conversation_id: Option<String> },
/// 流式心跳(静默期报活,前端 watchdog reset区分「LLM 在跑」与「真断」)
@@ -200,6 +220,36 @@ pub enum AiChatEvent {
options: Vec<String>,
conversation_id: Option<String>,
},
/// F-#95 跨端会话列表同步(Phase3,2026-06-22):device 响应 miniapp `list_conversations`
/// 命令,把本地 AiConversationRepo 列表摘要(不含消息体,省带宽)推回 miniapp。
///
/// 触发:remote_bridge `list_conversations` 路由 → `list_all()` → 映射 ConvSummary →
/// `publish_event`(跨端透传,经 EventBus→tunnel subscriber→relay→miniapp)。
///
/// 双职责:
/// 1. miniapp 会话列表渲染(conversations/index.vue 替换本地空表)
/// 2. device 在线探测(miniapp 收到此响应 = device 在线,chat/index.vue 状态文案切「已连接桌面端」)
///
/// 兼容:relay 纯透传不广播 device presence,故「device 是否在线」靠此响应隐式判定
/// (device 离线则 miniapp 发 list_conversations 无响应,文案保持「已连接中继」诚实表述)。
AiConversationList {
/// 会话摘要列表(已按 updated_at 倒序,不含 messages 全文)
conversations: Vec<ConvSummary>,
},
/// F-#95 扩展:miniapp 历史消息同步(device→miniapp 推消息列表)。
///
/// 触发:remote_bridge `load_messages` 路由 → `ai_messages.list_by_conversation` →
/// 映射 `ChatMessage`(record_to_message,对齐 ai_conversation_switch 读路径) →
/// `publish_event`(跨端透传,经 EventBus→tunnel subscriber→relay→miniapp)。
///
/// 职责:miniapp 点进对话时加载历史消息(替代 switch_conversation 后端忽略致空白)。
/// miniapp 据 conversation_id 匹配 activeConversationId 才替换,防串会话。
AiMessageHistory {
/// 对话 ID
conversation_id: String,
/// 历史消息数组(ORDER BY seq ASC,含 role/content/tool_calls 等完整字段)
messages: Vec<ChatMessage>,
},
/// L2 统一状态机(批2 1b,2026-06-22):对话生命周期状态变更通知。
///
/// 后端 `ConvState`(Idle/Generating/Stopping/Error/Compressed)经写收敛
@@ -214,6 +264,61 @@ pub enum AiChatEvent {
conv_state: ConvState,
conversation_id: Option<String>,
},
/// F-#95 跨端技能列表同步(Phase3,2026-06-23):device 响应 miniapp `list_skills` 命令,
/// 把本机 Claude 技能(skills/commands/plugins 三类,`ai_list_skills` 进程内缓存)推回 miniapp。
///
/// 触发:remote_bridge `list_skills` 路由 → `ai_list_skills().await` →
/// `publish_event`(跨端透传,经 EventBus→tunnel subscriber→relay→miniapp)。
///
/// 职责:miniapp 技能联想(输入框 `/` 触发浮层),对齐桌面端 `/` 联想体验。
/// 内嵌 `SkillInfo` 自身序列化字段(name/description/argument_hint?/source/path/duplicates?),
/// 变体整体无额外 rename_all 需求(照 AiConversationList 变体模式)。
AiSkillList {
/// 本机技能列表(ai_list_skills 返回的进程内缓存,已按 skills>commands>plugins 优先级去重)
skills: Vec<crate::commands::ai::skills::SkillInfo>,
},
/// F-#95 跨端实体列表同步(Phase3,2026-06-23):device 响应 miniapp `list_entities` 命令,
/// 把本地项目/任务/灵感全量列表(list_projects/list_tasks/list_ideas 全量等价路径)推回 miniapp。
///
/// 触发:remote_bridge `list_entities` 路由 → 并行调三 Tauri command(query/project_id/status
/// 均传 None 走 list_active/list_all 全量) → 合并打包 → `publish_event` 跨端透传。
///
/// 职责:miniapp `@` 实体联想(输入框 `@` 触发浮层选项目/任务/灵感),对齐桌面端 @ 体验。
/// 全量字段(对齐桌面端 src/api/types.ts,带宽敏感裁剪留后续批;Record 类型是
/// #[tauri::command] 返回值,必然 impl Serialize)。
AiEntityList {
/// 项目全量列表(list_active,deleted_at IS NULL)
projects: Vec<df_storage::models::ProjectRecord>,
/// 任务全量列表(list_active 等价,未删任务)
tasks: Vec<df_storage::models::TaskRecord>,
/// 灵感全量列表(list_by_query 空 query,等价 list_all)
ideas: Vec<df_storage::models::IdeaRecord>,
},
}
// ============================================================
// F-#95 跨端会话列表同步:会话摘要结构
// ============================================================
/// 会话摘要(miniapp 会话列表渲染用,对齐 apps/df-miniapp/src/types/events.ts:78 Conversation)。
///
/// 与 `AiConversationRecord` 区别:仅含列表展示必需字段,**不含 messages 全文**(省带宽,
/// miniapp MVP 不在列表展示历史消息,进入会话时按需同步)。`updated_at` 用 i64(ms 数值)
/// 便于前端直接比较排序;record 里是 ms 字符串,routing 层 parse 转。
///
/// `#[serde(rename_all = "camelCase")]` 对齐 miniapp Conversation 字段名
/// (id/title/lastMessage/updatedAt/createdAt),TS 侧无需 snake→camel 转换。
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ConvSummary {
pub id: String,
pub title: String,
/// 最近一条消息摘要(MVP 暂不取,留 None;后续批可解析 messages 末条)
pub last_message: Option<String>,
/// 最近更新时间(ms 数值)
pub updated_at: Option<i64>,
/// 创建时间(ms 数值,可选展示)
pub created_at: Option<i64>,
}
// ============================================================