增加语音对话功能

This commit is contained in:
yhy
2026-08-29 22:13:55 +08:00
parent 1ffa069151
commit 3a2438f566
51 changed files with 6207 additions and 3 deletions
@@ -0,0 +1,95 @@
import type { BeatType, SummaryJson } from "../../domain/types";
import type { TextChunk } from "../pdf/chunker";
import { joinPath } from "../platform/paths";
import { ensureLectureDirs, lectureDataRoot } from "./paths";
export interface StoredBeat {
id: string;
orderIndex: number;
title?: string;
type: BeatType;
script: string;
refs: string[];
audioPath?: string;
ttsStatus: "pending" | "ready" | "failed";
}
export interface LectureData {
paperId: string;
cacheKey: string;
/** 1 = summary only, 2 = beats + TTS (Step 1) */
phase?: 1 | 2;
summary?: SummaryJson;
chunks?: TextChunk[];
beats?: StoredBeat[];
extractChars?: number;
extractSource?: string;
updatedAt: number;
}
function lectureDataPath(cacheKey: string): string {
const safe = cacheKey.replace(/[^a-zA-Z0-9_|.-]/g, "_");
return joinPath(lectureDataRoot(), "data", `${safe}.json`);
}
export async function loadLectureData(
cacheKey: string,
): Promise<LectureData | undefined> {
await ensureLectureDirs();
const path = lectureDataPath(cacheKey);
try {
if (!(await IOUtils.exists(path))) return undefined;
const raw = await IOUtils.readUTF8(path);
return JSON.parse(raw) as LectureData;
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] loadLectureData failed", e);
return undefined;
}
}
export async function saveLectureData(data: LectureData): Promise<void> {
await ensureLectureDirs();
const dir = joinPath(lectureDataRoot(), "data");
await IOUtils.makeDirectory(dir, {
createAncestors: true,
ignoreExisting: true,
});
data.updatedAt = Date.now();
await IOUtils.writeUTF8(
lectureDataPath(data.cacheKey),
JSON.stringify(data, null, 2),
);
}
export async function mergeLectureData(
cacheKey: string,
paperId: string,
patch: Partial<LectureData>,
): Promise<LectureData> {
const existing =
(await loadLectureData(cacheKey)) ??
({
paperId,
cacheKey,
updatedAt: Date.now(),
} satisfies LectureData);
const merged: LectureData = {
...existing,
...patch,
paperId,
cacheKey,
updatedAt: Date.now(),
};
await saveLectureData(merged);
return merged;
}
export function isStep1Ready(data: LectureData | undefined): boolean {
if (!data?.beats?.length) return false;
return data.beats.some((b) => b.ttsStatus === "ready" && b.audioPath);
}
export function playableBeats(data: LectureData | undefined): StoredBeat[] {
if (!data?.beats) return [];
return data.beats.filter((b) => b.ttsStatus === "ready" && b.audioPath);
}