增加语音对话功能
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user