修复无法获得pdf的问题
This commit is contained in:
@@ -126,6 +126,10 @@ lecture-playing-zh = Playing: translation
|
|||||||
lecture-status-ready-step2 = Ready: { $count } paragraphs + note synced
|
lecture-status-ready-step2 = Ready: { $count } paragraphs + note synced
|
||||||
lecture-no-pdf = No PDF attachment on this item
|
lecture-no-pdf = No PDF attachment on this item
|
||||||
lecture-attach-error = Cannot read PDF attachment
|
lecture-attach-error = Cannot read PDF attachment
|
||||||
|
lecture-attach-error-detail = Cannot read PDF attachment: { $detail }
|
||||||
|
lecture-pdf-not-local = PDF is not available locally. Open the PDF in Zotero first, or ensure the attachment is synced or linked to a local file.
|
||||||
|
lecture-pdf-file-missing = Local PDF file is missing or the path is invalid. Check Zotero storage settings or re-link the file.
|
||||||
|
lecture-pdf-read-failed = Cannot read PDF file contents. Check that the file is valid and readable.
|
||||||
lecture-mount-error = Failed to load voice lecture pane
|
lecture-mount-error = Failed to load voice lecture pane
|
||||||
lecture-platform-hint-mac = macOS: set MinerU path under Preferences → ChatPapers → Voice lecture, or leave empty to use mineru on PATH.
|
lecture-platform-hint-mac = macOS: set MinerU path under Preferences → ChatPapers → Voice lecture, or leave empty to use mineru on PATH.
|
||||||
lecture-platform-hint-win = Windows: set the full path to mineru.exe under Preferences → ChatPapers → Voice lecture.
|
lecture-platform-hint-win = Windows: set the full path to mineru.exe under Preferences → ChatPapers → Voice lecture.
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ lecture-playing-zh = 正在播放:中文讲解
|
|||||||
lecture-status-ready-step2 = 备课完成:{ $count } 段逐段精读可播放,笔记已同步
|
lecture-status-ready-step2 = 备课完成:{ $count } 段逐段精读可播放,笔记已同步
|
||||||
lecture-no-pdf = 当前条目没有可用的 PDF 附件
|
lecture-no-pdf = 当前条目没有可用的 PDF 附件
|
||||||
lecture-attach-error = 无法读取 PDF 附件
|
lecture-attach-error = 无法读取 PDF 附件
|
||||||
|
lecture-attach-error-detail = 无法读取 PDF 附件:{ $detail }
|
||||||
|
lecture-pdf-not-local = PDF 未下载到本地。请先在 Zotero 中双击打开该 PDF,或确认附件已同步/已链接到本机文件。
|
||||||
|
lecture-pdf-file-missing = PDF 本地文件不存在或路径无效。请检查 Zotero 存储设置,或重新关联 PDF 文件。
|
||||||
|
lecture-pdf-read-failed = 无法读取 PDF 文件内容。请确认文件未损坏,且 Zotero 有读取权限。
|
||||||
lecture-mount-error = 语音伴读面板加载失败
|
lecture-mount-error = 语音伴读面板加载失败
|
||||||
lecture-platform-hint-mac = macOS:MinerU 路径可在偏好设置 → ChatPapers → 语音伴读 中配置;留空则尝试 PATH 中的 mineru。
|
lecture-platform-hint-mac = macOS:MinerU 路径可在偏好设置 → ChatPapers → 语音伴读 中配置;留空则尝试 PATH 中的 mineru。
|
||||||
lecture-platform-hint-win = Windows:请在偏好设置中填写 MinerU 可执行文件完整路径(如 mineru.exe)。
|
lecture-platform-hint-win = Windows:请在偏好设置中填写 MinerU 可执行文件完整路径(如 mineru.exe)。
|
||||||
|
|||||||
@@ -19,28 +19,38 @@ export function normalizeExecutablePath(input: string): string {
|
|||||||
|
|
||||||
/** Join path segments using Zotero PathUtils (cross-platform). */
|
/** Join path segments using Zotero PathUtils (cross-platform). */
|
||||||
export function joinPath(...parts: string[]): string {
|
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 {
|
export function normalizeStoragePath(input: string): string {
|
||||||
const trimmed = (input || "").trim();
|
const trimmed = (input || "").trim();
|
||||||
if (!trimmed) return "";
|
if (!trimmed) return "";
|
||||||
const joined = trimmed.replace(/[\\/]+/g, "/");
|
|
||||||
if (/^[A-Za-z]:\//.test(joined)) {
|
const unified = trimmed.replace(/\\/g, "/").replace(/\/+/g, "/");
|
||||||
const drive = joined.slice(0, 2);
|
|
||||||
const rest = joined
|
const winMatch = unified.match(/^([A-Za-z]:)(\/.*)?$/);
|
||||||
.slice(2)
|
if (winMatch) {
|
||||||
|
const drive = winMatch[1];
|
||||||
|
const rest = (winMatch[2] || "")
|
||||||
.split("/")
|
.split("/")
|
||||||
.filter((part) => part && part !== ".")
|
.filter((part) => part && part !== ".")
|
||||||
.join("\\");
|
.join("\\");
|
||||||
return `${drive}\\${rest}`.replace(/\\+$/, "");
|
return rest ? `${drive}\\${rest}` : drive;
|
||||||
}
|
}
|
||||||
return joined
|
|
||||||
.split("/")
|
const absolute = unified.startsWith("/");
|
||||||
.filter((part) => part && part !== ".")
|
const parts = unified.split("/").filter((part) => part && part !== ".");
|
||||||
.join("/")
|
let result = parts.join("/");
|
||||||
.replace(/\/+$/, "");
|
if (absolute) result = `/${result}`;
|
||||||
|
return result.replace(/\/+$/, "") || (absolute ? "/" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Safe single path segment for file or directory names (cross-platform). */
|
/** Safe single path segment for file or directory names (cross-platform). */
|
||||||
|
|||||||
@@ -7,25 +7,10 @@ import {
|
|||||||
} from "../platform/paths";
|
} from "../platform/paths";
|
||||||
|
|
||||||
function resolveProfileDir(): string {
|
function resolveProfileDir(): string {
|
||||||
const z = Zotero as any;
|
const dir = (Zotero as any).Profile?.dir;
|
||||||
const candidates: unknown[] = [
|
if (typeof dir === "string" && dir.trim()) {
|
||||||
z.Profile?.dir,
|
return dir.trim();
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("Cannot resolve Zotero profile directory");
|
throw new Error("Cannot resolve Zotero profile directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { findPdfAttachment } from "../../../pdf/extractor";
|
import { findPdfAttachment } from "../../../pdf/extractor";
|
||||||
|
import { getString } from "../../../../utils/locale";
|
||||||
import type { PaperAttachmentContext } from "../../domain/types";
|
import type { PaperAttachmentContext } from "../../domain/types";
|
||||||
import {
|
import {
|
||||||
buildCacheKey,
|
buildCacheKey,
|
||||||
@@ -6,12 +7,93 @@ import {
|
|||||||
paperIdFromCacheKey,
|
paperIdFromCacheKey,
|
||||||
} from "../pdf/fileHash";
|
} from "../pdf/fileHash";
|
||||||
|
|
||||||
function attachmentFilePath(attachment: Zotero.Item): string {
|
async function attachmentFilePath(attachment: Zotero.Item): Promise<string> {
|
||||||
const path = attachment.getFilePath?.() as string | false;
|
const att = attachment as Zotero.Item & {
|
||||||
if (!path) {
|
getFilePath?: () => string | false;
|
||||||
throw new Error("PDF attachment has no local file path");
|
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(
|
export async function resolvePaperAttachment(
|
||||||
@@ -22,13 +104,22 @@ export async function resolvePaperAttachment(
|
|||||||
throw new Error("NO_PDF");
|
throw new Error("NO_PDF");
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = attachmentFilePath(attachment);
|
let filePath: string;
|
||||||
const exists = await IOUtils.exists(filePath);
|
try {
|
||||||
if (!exists) {
|
filePath = await attachmentFilePath(attachment);
|
||||||
throw new Error("PDF_FILE_MISSING");
|
} 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 attachmentId = String(attachment.id);
|
||||||
const cacheKey = buildCacheKey(attachmentId, fileHash);
|
const cacheKey = buildCacheKey(attachmentId, fileHash);
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import { TtsError } from "../infrastructure/tts/types";
|
|||||||
import {
|
import {
|
||||||
getRegularItem,
|
getRegularItem,
|
||||||
resolvePaperAttachment,
|
resolvePaperAttachment,
|
||||||
|
formatLectureAttachmentError,
|
||||||
} from "../infrastructure/zotero/adapter";
|
} from "../infrastructure/zotero/adapter";
|
||||||
import { paperIdFromCacheKey } from "../infrastructure/pdf/fileHash";
|
import { paperIdFromCacheKey } from "../infrastructure/pdf/fileHash";
|
||||||
import { chunkById } from "../infrastructure/pdf/chunker";
|
import { chunkById } from "../infrastructure/pdf/chunker";
|
||||||
@@ -517,11 +518,8 @@ export class LecturePaneView {
|
|||||||
}
|
}
|
||||||
await this.refreshPlayer();
|
await this.refreshPlayer();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg =
|
ztoolkit.log("[ChatPapers:Lecture] refreshFromStore failed", e);
|
||||||
e instanceof Error && e.message === "NO_PDF"
|
this.setStatus("failed", formatLectureAttachmentError(e));
|
||||||
? getString("lecture-no-pdf")
|
|
||||||
: getString("lecture-attach-error");
|
|
||||||
this.setStatus("failed", msg);
|
|
||||||
this.actionBtn.disabled = true;
|
this.actionBtn.disabled = true;
|
||||||
this.ttsTestBtn.disabled = true;
|
this.ttsTestBtn.disabled = true;
|
||||||
}
|
}
|
||||||
@@ -1358,7 +1356,13 @@ export class LecturePaneView {
|
|||||||
this.setStatus(this.paper.lectureStatus, msg);
|
this.setStatus(this.paper.lectureStatus, msg);
|
||||||
await this.refreshPlayer();
|
await this.refreshPlayer();
|
||||||
} catch (e) {
|
} 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);
|
this.setStatus("failed", msg);
|
||||||
} finally {
|
} finally {
|
||||||
this.preparing = false;
|
this.preparing = false;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { ChatMessage } from "../llm/types";
|
import type { ChatMessage } from "../llm/types";
|
||||||
|
|
||||||
function sessionDir(): string {
|
function sessionDir(): string {
|
||||||
const profile = (Zotero as any).Profile?.dir || (Zotero as any).getProfileDirectory?.()?.path;
|
const profile = (Zotero as any).Profile?.dir;
|
||||||
if (!profile) {
|
if (!profile || typeof profile !== "string") {
|
||||||
throw new Error("Cannot resolve Zotero profile directory");
|
throw new Error("Cannot resolve Zotero profile directory");
|
||||||
}
|
}
|
||||||
return PathUtils.join(profile, "chatpapers", "sessions");
|
return PathUtils.join(profile, "chatpapers", "sessions");
|
||||||
|
|||||||
Vendored
+4
@@ -44,6 +44,7 @@ export type FluentMessageId =
|
|||||||
| 'itemmenu-compare'
|
| 'itemmenu-compare'
|
||||||
| 'itemmenu-label'
|
| 'itemmenu-label'
|
||||||
| 'lecture-attach-error'
|
| 'lecture-attach-error'
|
||||||
|
| 'lecture-attach-error-detail'
|
||||||
| 'lecture-beat-detail-empty'
|
| 'lecture-beat-detail-empty'
|
||||||
| 'lecture-beat-label'
|
| 'lecture-beat-label'
|
||||||
| 'lecture-beat-list-hint'
|
| 'lecture-beat-list-hint'
|
||||||
@@ -71,6 +72,9 @@ export type FluentMessageId =
|
|||||||
| 'lecture-para-play'
|
| 'lecture-para-play'
|
||||||
| 'lecture-para-translation'
|
| 'lecture-para-translation'
|
||||||
| 'lecture-paragraph-list-title'
|
| 'lecture-paragraph-list-title'
|
||||||
|
| 'lecture-pdf-file-missing'
|
||||||
|
| 'lecture-pdf-not-local'
|
||||||
|
| 'lecture-pdf-read-failed'
|
||||||
| 'lecture-pdf-sync-hint'
|
| 'lecture-pdf-sync-hint'
|
||||||
| 'lecture-platform-hint-mac'
|
| 'lecture-platform-hint-mac'
|
||||||
| 'lecture-platform-hint-other'
|
| 'lecture-platform-hint-other'
|
||||||
|
|||||||
Reference in New Issue
Block a user