增加pdf中划句或段ai伴读功能

This commit is contained in:
yhy
2026-08-29 23:07:19 +08:00
parent 1475f8b3c5
commit 0ed8f42029
22 changed files with 2045 additions and 15 deletions
@@ -0,0 +1,114 @@
import { config } from "../../../../package.json";
import { getString } from "../../../utils/locale";
import { cacheReaderSelection } 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 { playSelectionExplainStandalone } 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);
showHeadline(getString("lecture-selection-explaining"));
try {
const ctx = await resolvePaperAttachment(item);
const result = await explainSelectionLecture({
item,
selectedText: text,
onProgress: (message) => showHeadline(message),
});
await highlightTextInPdf(item, {
text: result.original,
page: result.page,
});
lectureEvents.emit("lecture:selection_explain", {
paperId: paperIdFromCacheKey(ctx.cacheKey),
cacheKey: ctx.cacheKey,
itemId: String(getRegularItem(item).id),
result,
});
await playSelectionExplainStandalone(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) => {
ev.preventDefault();
ev.stopPropagation();
void runSelectionExplainFromReader(reader, text);
});
append(btn);
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] selection popup button failed", e);
}
},
config.addonID,
);
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] registerSelectionExplainPopup failed", e);
}
}