feat(memory,conversation): 记忆富化/证据包、时间线幂等字段与对话分段全链路

数据库
- 新增迁移 0003:timeline_events.memory_source_id 外键 → memory_sources,便于按 ingest 源做时间线幂等

后端 - 记忆
- 新增 ingest 后 LLM 富化(摘要/事实/时间线),可配置开关与最大字符数
- 新增证据包组装:合并 chunk、摘要、事实、时间线、故事等检索结果;支持空 query 时是否仍带 rolling 等开关
- repo/retriever/service/router/schemas/summarizer/timeline/extractor 等扩展;文档 memory-retrieval.md 更新

后端 - 对话 WS
- 增加 PING/PONG;分段 ASR 日志与空音频处理;转写失败与「无助手回复」错误提示更明确
- 助手多段回复持久化使用统一分隔符,与分段逻辑一致

后端 - Agent
- reply_limits:按 [SPLIT] 与段落拆段,并保证非空 fallback,供 WS 与 TTS 多段下发

后端 - 回忆录任务
- transcript ingest 记录 source_id;任务成功结?
This commit is contained in:
Kevin
2026-03-27 16:01:28 +08:00
parent 1374f6e8f5
commit e4bf0710c7
70 changed files with 3404 additions and 557 deletions

View File

@@ -1,6 +1,7 @@
import { setAudioModeAsync } from 'expo-audio';
import { setAudioModeAsync, setIsAudioActiveAsync } from 'expo-audio';
type AudioOwner = 'recorder' | 'player' | null;
type ActiveAudioOwner = Exclude<AudioOwner, null>;
type OwnerChangeListener = (owner: AudioOwner) => void;
let currentOwner: AudioOwner = null;
@@ -22,7 +23,7 @@ export const audioFocus = {
if (currentOwner === 'recorder') return true;
if (currentOwner === 'player') {
await this.release();
await this.releaseIfOwnedBy('player');
}
await setAudioModeAsync({
@@ -59,11 +60,17 @@ export const audioFocus = {
playsInSilentMode: false,
allowsRecording: false,
});
await setIsAudioActiveAsync(false);
currentOwner = null;
notify();
},
async releaseIfOwnedBy(owner: ActiveAudioOwner): Promise<void> {
if (currentOwner !== owner) return;
await this.release();
},
getCurrentOwner(): AudioOwner {
return currentOwner;
},

View File

@@ -37,6 +37,7 @@ function mapServerMessage(raw: RawServerMessage): WsEvent | null {
text: d.text as string,
index: d.index as number | undefined,
total: d.total as number | undefined,
assistantMessageId: d.assistant_message_id as string | undefined,
isTransition: d.transition as boolean | undefined,
segmentIndex: d.segment_index as number | undefined,
};

View File

@@ -53,6 +53,8 @@ export interface AgentResponseEvent {
text: string;
index?: number;
total?: number;
/** 落库后的助手消息 id多段时每段独立 WS与 TTS 的 index 一起定位气泡 */
assistantMessageId?: string;
isTransition?: boolean;
segmentIndex?: number;
}