修复无法获得pdf的问题
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user