feat(M2.1): AI 语音对话插件骨架 + 模型管理 + HTTP API + config schema 扩展
这是 M2.1(语音数字生命 V1)的后端骨架实现,不含 Web UI / Flutter / 设备部署。 新增文件: - src/plugins/ai/mod.rs: AiPlugin 薄层(持有 ChatPipeline Arc,处理消息系统联动) - src/plugins/ai/backend.rs: AiBackend trait + LocalCliBackend (whisper-cli/llama-cli/piper 子进程) + CloudBackend 占位(V1 返回"暂未支持") - src/plugins/ai/chat.rs: ChatPipeline 对话管线执行器(HTTP 层 spawn_blocking 直接调用) - src/plugins/ai/model_manager.rs: ModelManager 模型资产管理 (清单/下载/切换/删除/配额/档位守门,参照 plugin_repo + version_manager 模式) 修改文件: - src/core/message.rs: 新增 ChatRequest/ChatResponse/AiModelEvent 消息类型 - src/core/config.rs: AppConfig 新增 character 块(角色元信息+人设)+ ai 块(后端配置) 均为 #[serde(default)] 向后兼容旧配置 - src/plugins/mod.rs: 注册 ai 模块 - src/plugins/http/mod.rs: HttpState 新增 ai_pipeline + ai_models 字段及注册方法; HttpPlugin 新增 set_ai_pipeline/set_ai_models 方法 - src/plugins/http/routes.rs: 新增 6 个 AI 相关路由 - POST /api/chat/text (文字对话,Web 端主路径) - POST /api/chat/audio (语音对话,App 主路径) - GET /api/models (模型清单+水位+配额) - POST /api/models/download (下载模型,后台线程执行) - POST /api/models/switch (切换激活模型) - POST /api/models/delete (删除模型,保护当前激活) - src/main.rs: 注册 AiPlugin,连接 pipeline 到 HttpPlugin 技术决策: - 对话管线用 spawn_blocking 而非消息系统,满足 HTTP 同步响应需求 - ChatPipeline 用 Arc<Mutex> 共享,HTTP 和 AiPlugin 共用同一实例 - 互斥用 try_lock,忙时返回 409 而非阻塞 - Spike 结论固化: LLM t=2 锁大核、ctx=1024 限死、Qwen2.5-0.5B 默认档 待完成 (后续提交): - Web 控制端 UI (文字对话 + 角色切换 + 模型管理界面) - Flutter App (角色页/语音页/模型管理页) - 设备端部署 llama.cpp/whisper.cpp/piper 二进制 + 模型下载 - 画面联动 (talk/idle 状态切换) - 测试 注: Windows 开发环境缺 dbus,无法本地 cargo check;待目标机验证。
This commit is contained in:
@@ -15,6 +15,12 @@ pub struct AppConfig {
|
||||
pub remote_control: RemoteControlConfig,
|
||||
#[serde(default)]
|
||||
pub ble: BleConfig,
|
||||
/// 角色元信息 (M2.1 新增,向后兼容旧配置)
|
||||
#[serde(default)]
|
||||
pub character: CharacterConfig,
|
||||
/// AI 对话配置 (M2.1 新增)
|
||||
#[serde(default)]
|
||||
pub ai: AiConfig,
|
||||
#[serde(default)]
|
||||
pub source_path: PathBuf,
|
||||
#[serde(default)]
|
||||
@@ -298,6 +304,104 @@ fn default_ble_device_name() -> String {
|
||||
"Showen".to_string()
|
||||
}
|
||||
|
||||
// ── 角色元信息 (M2.1) ──
|
||||
|
||||
/// 角色类型
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum CharacterType {
|
||||
#[default]
|
||||
Pet,
|
||||
Human,
|
||||
Singer,
|
||||
}
|
||||
|
||||
/// 角色元信息配置块(内容包的一部分,切角色即切人设)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CharacterConfig {
|
||||
/// 角色显示名 (如 "小汪")
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
/// 角色类型
|
||||
#[serde(default)]
|
||||
pub character_type: CharacterType,
|
||||
/// 封面图相对路径 (内容包内)
|
||||
#[serde(default)]
|
||||
pub cover_image: Option<String>,
|
||||
/// 人设 prompt (LLM system prompt)
|
||||
#[serde(default)]
|
||||
pub persona_prompt: String,
|
||||
/// 最大回复 token 数
|
||||
#[serde(default = "default_character_max_tokens")]
|
||||
pub max_tokens: u16,
|
||||
/// TTS 音色标识 (piper 模型 ID,留空用默认)
|
||||
#[serde(default)]
|
||||
pub tts_voice: Option<String>,
|
||||
/// talk 状态名 (语音回合期间切换到,留空用 "talk")
|
||||
#[serde(default = "default_talk_state")]
|
||||
pub talk_state: String,
|
||||
}
|
||||
|
||||
fn default_character_max_tokens() -> u16 {
|
||||
128
|
||||
}
|
||||
fn default_talk_state() -> String {
|
||||
"talk".to_string()
|
||||
}
|
||||
|
||||
// ── AI 配置 (M2.1) ──
|
||||
|
||||
/// LLM 后端类型
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum LlmBackend {
|
||||
#[default]
|
||||
Local,
|
||||
/// 云端 (V1 仅占位,配置时返回"暂未支持")
|
||||
Cloud,
|
||||
}
|
||||
|
||||
/// AI 推理配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AiConfig {
|
||||
/// LLM 后端 (local/cloud)
|
||||
#[serde(default)]
|
||||
pub backend: LlmBackend,
|
||||
/// 云端 endpoint (预留,V1 不实现)
|
||||
#[serde(default)]
|
||||
pub cloud_endpoint: Option<String>,
|
||||
/// 云端 API key (预留)
|
||||
#[serde(default)]
|
||||
pub cloud_api_key: Option<String>,
|
||||
/// 模型存储目录 (默认 model_store/)
|
||||
#[serde(default = "default_model_store")]
|
||||
pub model_store: PathBuf,
|
||||
/// 工具目录 (whisper-cli/llama-cli/piper 二进制所在)
|
||||
#[serde(default = "default_tools_dir")]
|
||||
pub tools_dir: PathBuf,
|
||||
/// 临时文件目录
|
||||
#[serde(default = "default_tmp_dir")]
|
||||
pub tmp_dir: PathBuf,
|
||||
/// 上下文窗口轮数
|
||||
#[serde(default = "default_context_window")]
|
||||
pub context_window: usize,
|
||||
}
|
||||
|
||||
fn default_model_store() -> PathBuf {
|
||||
PathBuf::from("model_store")
|
||||
}
|
||||
fn default_tools_dir() -> PathBuf {
|
||||
PathBuf::from("tools")
|
||||
}
|
||||
fn default_tmp_dir() -> PathBuf {
|
||||
PathBuf::from("/tmp/showen_ai")
|
||||
}
|
||||
fn default_context_window() -> usize {
|
||||
5
|
||||
}
|
||||
|
||||
// ── 加载与验证 ──
|
||||
|
||||
impl AppConfig {
|
||||
|
||||
Reference in New Issue
Block a user