Files
chatpapers/src/modules/lecture/ui/registerSelectionExplainPopup.ts
T

125 lines
3.9 KiB
TypeScript

import { config } from "../../../../package.json";
import { getString } from "../../../utils/locale";
import { cacheReaderSelection, getCachedReaderSelectionPosition } from "../../pdf/selection";
import { highlightTextInPdf } from "../../pdf/readerSync";
import { getRegularItem, resolvePaperAttachment } from "../infrastructure/zotero/adapter";
import { paperIdFromCacheKey } from "../infrastructure/pdf/fileHash";
import { explainSelectionLecture } from "../application/explainSelectionLecture";
import {
beginSelectionExplainSession,
isSelectionExplainSessionActive,
playSelectionExplainExclusive,
} from "../application/playSelectionExplain";
import { lectureEvents } from "../infrastructure/events";
function tryString(value: unknown): string {
if (typeof value === "string") return value.trim();
if (value == null) return "";
return String(value).trim();
}
function itemFromReader(reader: any): Zotero.Item | undefined {
const attachmentId = reader?.itemID ?? reader?._itemID;
if (!attachmentId) return undefined;
const attachment = Zotero.Items.get(attachmentId);
if (!attachment) return undefined;
return getRegularItem(attachment);
}
function showHeadline(text: string): void {
try {
const pw = new Zotero.ProgressWindow({ closeOnClick: true });
pw.changeHeadline(text);
pw.show();
pw.startCloseTimer(4000);
} catch {
// ignore
}
}
export async function runSelectionExplainFromReader(
reader: any,
selectedText: string,
): Promise<void> {
const text = tryString(selectedText);
if (!text) return;
const item = itemFromReader(reader);
if (!item) {
showHeadline(getString("lecture-selection-no-item"));
return;
}
cacheReaderSelection(text);
const sessionId = beginSelectionExplainSession();
showHeadline(getString("lecture-selection-explaining"));
try {
const ctx = await resolvePaperAttachment(item);
const result = await explainSelectionLecture({
item,
selectedText: text,
onProgress: (message) => showHeadline(message),
});
if (!isSelectionExplainSessionActive(sessionId)) return;
await highlightTextInPdf(item, {
text: result.original,
page: result.page,
position: getCachedReaderSelectionPosition(result.original),
});
lectureEvents.emit("lecture:selection_explain", {
paperId: paperIdFromCacheKey(ctx.cacheKey),
cacheKey: ctx.cacheKey,
itemId: String(getRegularItem(item).id),
result,
});
if (!isSelectionExplainSessionActive(sessionId)) return;
await playSelectionExplainExclusive(result);
showHeadline(getString("lecture-selection-done"));
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
ztoolkit.log("[ChatPapers:Lecture] selection explain failed", e);
showHeadline(msg);
}
}
export function registerSelectionExplainPopup(): void {
try {
Zotero.Reader.registerEventListener(
"renderTextSelectionPopup",
(event: any) => {
try {
const { doc, params, append, reader } = event;
const text =
tryString(params?.annotation?.text) || tryString(params?.text);
if (!text) return;
cacheReaderSelection(text);
const btn = doc.createElement("button");
btn.type = "button";
btn.className =
"toolbar-button wide-button chatpapers-selection-explain-btn";
btn.textContent = getString("lecture-explain-selection-short");
btn.title = getString("lecture-explain-selection");
btn.addEventListener("click", (ev: Event) => {
ev.preventDefault();
ev.stopPropagation();
void runSelectionExplainFromReader(reader, text);
}, { once: true });
append(btn);
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] selection popup button failed", e);
}
},
config.addonID,
);
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] registerSelectionExplainPopup failed", e);
}
}