修复无法获得pdf的问题

This commit is contained in:
yhy
2026-08-30 09:02:11 +08:00
parent 455059eebe
commit af2ee38443
8 changed files with 151 additions and 49 deletions
@@ -19,28 +19,38 @@ export function normalizeExecutablePath(input: string): string {
/** Join path segments using Zotero PathUtils (cross-platform). */
export function joinPath(...parts: string[]): string {
return PathUtils.join(...parts.filter(Boolean));
const filtered = parts.filter(Boolean);
if (!filtered.length) return "";
try {
return PathUtils.join(...filtered);
} catch (e) {
ztoolkit.log("[ChatPapers] PathUtils.join failed", filtered, e);
throw e;
}
}
/** Normalize a filesystem path for the current OS. */
/** Normalize user-provided storage directory paths (prefs). */
export function normalizeStoragePath(input: string): string {
const trimmed = (input || "").trim();
if (!trimmed) return "";
const joined = trimmed.replace(/[\\/]+/g, "/");
if (/^[A-Za-z]:\//.test(joined)) {
const drive = joined.slice(0, 2);
const rest = joined
.slice(2)
const unified = trimmed.replace(/\\/g, "/").replace(/\/+/g, "/");
const winMatch = unified.match(/^([A-Za-z]:)(\/.*)?$/);
if (winMatch) {
const drive = winMatch[1];
const rest = (winMatch[2] || "")
.split("/")
.filter((part) => part && part !== ".")
.join("\\");
return `${drive}\\${rest}`.replace(/\\+$/, "");
return rest ? `${drive}\\${rest}` : drive;
}
return joined
.split("/")
.filter((part) => part && part !== ".")
.join("/")
.replace(/\/+$/, "");
const absolute = unified.startsWith("/");
const parts = unified.split("/").filter((part) => part && part !== ".");
let result = parts.join("/");
if (absolute) result = `/${result}`;
return result.replace(/\/+$/, "") || (absolute ? "/" : "");
}
/** Safe single path segment for file or directory names (cross-platform). */
@@ -7,25 +7,10 @@ import {
} from "../platform/paths";
function resolveProfileDir(): string {
const z = Zotero as any;
const candidates: unknown[] = [
z.Profile?.dir,
typeof z.getProfileDirectory === "function" ? z.getProfileDirectory() : undefined,
];
for (const candidate of candidates) {
const path =
typeof candidate === "string"
? candidate
: candidate &&
typeof candidate === "object" &&
"path" in candidate &&
typeof (candidate as { path: unknown }).path === "string"
? (candidate as { path: string }).path
: "";
if (path.trim()) return normalizeStoragePath(path.trim());
const dir = (Zotero as any).Profile?.dir;
if (typeof dir === "string" && dir.trim()) {
return dir.trim();
}
throw new Error("Cannot resolve Zotero profile directory");
}
@@ -1,4 +1,5 @@
import { findPdfAttachment } from "../../../pdf/extractor";
import { getString } from "../../../../utils/locale";
import type { PaperAttachmentContext } from "../../domain/types";
import {
buildCacheKey,
@@ -6,12 +7,93 @@ import {
paperIdFromCacheKey,
} from "../pdf/fileHash";
function attachmentFilePath(attachment: Zotero.Item): string {
const path = attachment.getFilePath?.() as string | false;
if (!path) {
throw new Error("PDF attachment has no local file path");
async function attachmentFilePath(attachment: Zotero.Item): Promise<string> {
const att = attachment as Zotero.Item & {
getFilePath?: () => string | false;
getFilePathAsync?: () => Promise<string | false>;
};
if (typeof att.getFilePathAsync === "function") {
try {
const asyncPath = await att.getFilePathAsync();
if (asyncPath && typeof asyncPath === "string") {
return asyncPath;
}
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] getFilePathAsync failed", e);
}
}
const syncPath = att.getFilePath?.();
if (syncPath && typeof syncPath === "string") {
return syncPath;
}
const zAtt = (Zotero as any).Attachments;
if (zAtt?.getFilePath) {
try {
const path = zAtt.getFilePath(attachment);
if (path && typeof path === "string") return path;
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] Attachments.getFilePath failed", e);
}
}
throw new Error("PDF_NOT_LOCAL");
}
async function attachmentFileHash(
attachment: Zotero.Item,
filePath: string,
): Promise<string> {
try {
return await computeFileHash(filePath);
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] computeFileHash failed", filePath, e);
const att = attachment as Zotero.Item & {
attachmentHash?: Promise<string | undefined>;
};
if (att.attachmentHash) {
try {
const md5 = await att.attachmentHash;
if (md5 && typeof md5 === "string") {
return `md5:${md5}`;
}
} catch (hashErr) {
ztoolkit.log("[ChatPapers:Lecture] attachmentHash fallback failed", hashErr);
}
}
throw new Error("PDF_READ_FAILED");
}
}
async function ensureAttachmentFileReadable(filePath: string): Promise<void> {
try {
if (await IOUtils.exists(filePath)) return;
} catch (e) {
ztoolkit.log("[ChatPapers:Lecture] IOUtils.exists failed", filePath, e);
}
throw new Error("PDF_FILE_MISSING");
}
export function formatLectureAttachmentError(error: unknown): string {
if (!(error instanceof Error)) {
return getString("lecture-attach-error");
}
switch (error.message) {
case "NO_PDF":
return getString("lecture-no-pdf");
case "PDF_NOT_LOCAL":
return getString("lecture-pdf-not-local");
case "PDF_FILE_MISSING":
return getString("lecture-pdf-file-missing");
case "PDF_READ_FAILED":
return getString("lecture-pdf-read-failed");
default:
return getString("lecture-attach-error-detail", {
args: { detail: error.message },
});
}
return path;
}
export async function resolvePaperAttachment(
@@ -22,13 +104,22 @@ export async function resolvePaperAttachment(
throw new Error("NO_PDF");
}
const filePath = attachmentFilePath(attachment);
const exists = await IOUtils.exists(filePath);
if (!exists) {
throw new Error("PDF_FILE_MISSING");
let filePath: string;
try {
filePath = await attachmentFilePath(attachment);
} catch (e) {
if (e instanceof Error && e.message === "PDF_NOT_LOCAL") throw e;
ztoolkit.log("[ChatPapers:Lecture] resolve attachment path failed", {
attachmentId: attachment.id,
linkMode: (attachment as any).attachmentLinkMode,
e,
});
throw new Error("PDF_NOT_LOCAL");
}
const fileHash = await computeFileHash(filePath);
await ensureAttachmentFileReadable(filePath);
const fileHash = await attachmentFileHash(attachment, filePath);
const attachmentId = String(attachment.id);
const cacheKey = buildCacheKey(attachmentId, fileHash);
+10 -6
View File
@@ -38,6 +38,7 @@ import { TtsError } from "../infrastructure/tts/types";
import {
getRegularItem,
resolvePaperAttachment,
formatLectureAttachmentError,
} from "../infrastructure/zotero/adapter";
import { paperIdFromCacheKey } from "../infrastructure/pdf/fileHash";
import { chunkById } from "../infrastructure/pdf/chunker";
@@ -517,11 +518,8 @@ export class LecturePaneView {
}
await this.refreshPlayer();
} catch (e) {
const msg =
e instanceof Error && e.message === "NO_PDF"
? getString("lecture-no-pdf")
: getString("lecture-attach-error");
this.setStatus("failed", msg);
ztoolkit.log("[ChatPapers:Lecture] refreshFromStore failed", e);
this.setStatus("failed", formatLectureAttachmentError(e));
this.actionBtn.disabled = true;
this.ttsTestBtn.disabled = true;
}
@@ -1358,7 +1356,13 @@ export class LecturePaneView {
this.setStatus(this.paper.lectureStatus, msg);
await this.refreshPlayer();
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
ztoolkit.log("[ChatPapers:Lecture] onStartPrepare failed", e);
const msg =
e instanceof Error && e.message.startsWith("PDF_")
? formatLectureAttachmentError(e)
: e instanceof Error
? e.message
: String(e);
this.setStatus("failed", msg);
} finally {
this.preparing = false;
+2 -2
View File
@@ -1,8 +1,8 @@
import type { ChatMessage } from "../llm/types";
function sessionDir(): string {
const profile = (Zotero as any).Profile?.dir || (Zotero as any).getProfileDirectory?.()?.path;
if (!profile) {
const profile = (Zotero as any).Profile?.dir;
if (!profile || typeof profile !== "string") {
throw new Error("Cannot resolve Zotero profile directory");
}
return PathUtils.join(profile, "chatpapers", "sessions");