148 lines
3.9 KiB
TypeScript
148 lines
3.9 KiB
TypeScript
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 StoredSentence {
|
|
id: string;
|
|
orderIndex: number;
|
|
text: string;
|
|
translation: string;
|
|
page?: number;
|
|
searchText?: string;
|
|
audioPathEn?: string;
|
|
audioPathZh?: string;
|
|
ttsEnStatus: "pending" | "ready" | "failed" | "skipped";
|
|
ttsZhStatus: "pending" | "ready" | "failed";
|
|
}
|
|
|
|
export interface StoredParagraph {
|
|
id: string;
|
|
orderIndex: number;
|
|
originalText: string;
|
|
translation: string;
|
|
contextLink: string;
|
|
roleInPaper: string;
|
|
page?: number;
|
|
searchText?: string;
|
|
sentences?: StoredSentence[];
|
|
audioPathEn?: string;
|
|
audioPathZh?: string;
|
|
ttsEnStatus: "pending" | "ready" | "failed" | "skipped";
|
|
ttsZhStatus: "pending" | "ready" | "failed";
|
|
genStatus: "pending" | "ready" | "failed";
|
|
}
|
|
|
|
export interface LectureData {
|
|
paperId: string;
|
|
cacheKey: string;
|
|
/** 1 = summary, 2 = beats+TTS, 3 = paragraphs+note */
|
|
phase?: 1 | 2 | 3;
|
|
summary?: SummaryJson;
|
|
chunks?: TextChunk[];
|
|
beats?: StoredBeat[];
|
|
paragraphs?: StoredParagraph[];
|
|
noteItemId?: string;
|
|
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 isStep2Ready(data: LectureData | undefined): boolean {
|
|
if (!data?.paragraphs?.length) return false;
|
|
return data.paragraphs.some(
|
|
(p) => p.genStatus === "ready" && p.ttsZhStatus === "ready" && p.audioPathZh,
|
|
);
|
|
}
|
|
|
|
export function isLectureComplete(data: LectureData | undefined): boolean {
|
|
return isStep1Ready(data) && isStep2Ready(data);
|
|
}
|
|
|
|
export function playableBeats(data: LectureData | undefined): StoredBeat[] {
|
|
if (!data?.beats) return [];
|
|
return data.beats.filter((b) => b.ttsStatus === "ready" && b.audioPath);
|
|
}
|
|
|
|
export function playableParagraphs(
|
|
data: LectureData | undefined,
|
|
): StoredParagraph[] {
|
|
if (!data?.paragraphs) return [];
|
|
return data.paragraphs.filter(
|
|
(p) => p.genStatus === "ready" && p.ttsZhStatus === "ready" && p.audioPathZh,
|
|
);
|
|
}
|