markpdfdown 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -516,6 +516,69 @@ class ImagePathUtil {
516
516
  this.uploadsDir = null;
517
517
  }
518
518
  }
519
+ class FileWaitUtil {
520
+ static MAX_ATTEMPTS = 5;
521
+ static DELAY_MS = 1e3;
522
+ /**
523
+ * Wait for a file to become available and non-empty.
524
+ *
525
+ * @param filePath - Full path to the file
526
+ * @param uploadsDir - Base uploads directory for diagnostic logging
527
+ * @param taskId - Task ID for diagnostic logging
528
+ * @param filename - Original filename for error messages
529
+ * @param label - Log label (e.g., 'PDFSplitter', 'ImageSplitter')
530
+ * @throws Error if the file is not found after all retries
531
+ */
532
+ static async waitForFile(filePath, uploadsDir, taskId, filename, label) {
533
+ for (let attempt = 1; attempt <= this.MAX_ATTEMPTS; attempt++) {
534
+ try {
535
+ await promises.access(filePath);
536
+ const stats = await promises.stat(filePath);
537
+ if (stats.size > 0) {
538
+ if (attempt > 1) {
539
+ console.log(
540
+ `[${label}] File became available on attempt ${attempt}: ${filePath}`
541
+ );
542
+ }
543
+ return;
544
+ }
545
+ console.warn(
546
+ `[${label}] File exists but is empty (attempt ${attempt}/${this.MAX_ATTEMPTS}): ${filePath}`
547
+ );
548
+ } catch {
549
+ console.warn(
550
+ `[${label}] File not accessible (attempt ${attempt}/${this.MAX_ATTEMPTS}): ${filePath}`
551
+ );
552
+ }
553
+ if (attempt < this.MAX_ATTEMPTS) {
554
+ await new Promise((resolve) => setTimeout(resolve, this.DELAY_MS));
555
+ }
556
+ }
557
+ await this.logDiagnostics(uploadsDir, taskId, filename, label);
558
+ throw new Error(
559
+ `${label === "PDFSplitter" ? "PDF" : "Image"} file not found: ${filename}. The file may have been moved or deleted.`
560
+ );
561
+ }
562
+ /**
563
+ * Log diagnostic information about the task directory contents.
564
+ */
565
+ static async logDiagnostics(uploadsDir, taskId, filename, label) {
566
+ const taskDir = path.join(uploadsDir, taskId);
567
+ try {
568
+ const dirExists = await promises.stat(taskDir).then(() => true).catch(() => false);
569
+ if (dirExists) {
570
+ const files = await promises.readdir(taskDir);
571
+ console.error(
572
+ `[${label}] Task directory exists but target file not found. Dir: ${taskDir}, Files in dir: [${files.join(", ")}], Expected: ${filename}`
573
+ );
574
+ } else {
575
+ console.error(`[${label}] Task directory does not exist: ${taskDir}`);
576
+ }
577
+ } catch (diagError) {
578
+ console.error(`[${label}] Diagnostic check failed:`, diagError);
579
+ }
580
+ }
581
+ }
519
582
  class PageRangeParser {
520
583
  /**
521
584
  * Regular expression for validating page range format.
@@ -684,6 +747,7 @@ class PDFSplitter {
684
747
  const taskId = task.id;
685
748
  const filename = task.filename;
686
749
  const sourcePath = path.join(this.uploadsDir, taskId, filename);
750
+ await FileWaitUtil.waitForFile(sourcePath, this.uploadsDir, taskId, filename, "PDFSplitter");
687
751
  try {
688
752
  const totalPages = await this.getPDFPageCountWithRetry(sourcePath);
689
753
  const pageNumbers = PageRangeParser.parse(task.page_range, totalPages);
@@ -855,8 +919,8 @@ class ImageSplitter {
855
919
  const taskId = task.id;
856
920
  const filename = task.filename;
857
921
  const sourcePath = path.join(this.uploadsDir, taskId, filename);
922
+ await FileWaitUtil.waitForFile(sourcePath, this.uploadsDir, taskId, filename, "ImageSplitter");
858
923
  try {
859
- await promises.access(sourcePath);
860
924
  const ext = path.extname(filename).toLowerCase();
861
925
  if (!ext) {
862
926
  throw new Error(`Image file has no extension: ${filename}`);
@@ -1119,6 +1183,22 @@ const findAll$2 = async () => {
1119
1183
  orderBy: [{ createdAt: "desc" }]
1120
1184
  });
1121
1185
  };
1186
+ const findAllIncludeDisabled = async () => {
1187
+ return await prisma.provider.findMany({
1188
+ select: {
1189
+ id: true,
1190
+ name: true,
1191
+ type: true,
1192
+ api_key: true,
1193
+ base_url: true,
1194
+ suffix: true,
1195
+ status: true,
1196
+ createdAt: true,
1197
+ updatedAt: true
1198
+ },
1199
+ orderBy: [{ createdAt: "desc" }]
1200
+ });
1201
+ };
1122
1202
  const findById$1 = async (id) => {
1123
1203
  return await prisma.provider.findUnique({
1124
1204
  where: { id },
@@ -1169,6 +1249,7 @@ const updateStatus = async (id, status) => {
1169
1249
  };
1170
1250
  const providerRepository = {
1171
1251
  findAll: findAll$2,
1252
+ findAllIncludeDisabled,
1172
1253
  findById: findById$1,
1173
1254
  create: create$2,
1174
1255
  update: update$1,
@@ -1411,25 +1492,31 @@ class ConverterWorker extends WorkerBase {
1411
1492
  /**
1412
1493
  * Claim a PENDING page for processing using optimistic locking.
1413
1494
  *
1414
- * Query conditions:
1415
- * - task.status = PROCESSING
1416
- * - task.status != CANCELLED
1417
- * - page.status = PENDING
1418
- * - page.worker_id = null
1495
+ * Strategy: First find tasks in valid states (PROCESSING/COMPLETED),
1496
+ * then find PENDING pages only within those tasks. This avoids the problem
1497
+ * where orphaned PENDING pages from terminal tasks exhaust claim attempts.
1419
1498
  *
1420
1499
  * Order: retry_count ASC, page ASC (prioritize fresh pages)
1421
1500
  */
1422
1501
  async claimPage() {
1423
1502
  const maxAttempts = 5;
1424
- const checkedTaskIds = [];
1503
+ const activeTasks = await prisma.task.findMany({
1504
+ where: {
1505
+ status: { in: [TaskStatus.PROCESSING, TaskStatus.COMPLETED] }
1506
+ },
1507
+ select: { id: true }
1508
+ });
1509
+ if (activeTasks.length === 0) {
1510
+ return null;
1511
+ }
1512
+ const activeTaskIds = activeTasks.map((t) => t.id);
1425
1513
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
1426
1514
  try {
1427
1515
  const candidate = await prisma.taskDetail.findFirst({
1428
1516
  where: {
1429
1517
  status: PageStatus.PENDING,
1430
1518
  worker_id: null,
1431
- // Exclude pages from tasks we've already checked and found not in PROCESSING state
1432
- ...checkedTaskIds.length > 0 && { task: { notIn: checkedTaskIds } }
1519
+ task: { in: activeTaskIds }
1433
1520
  },
1434
1521
  orderBy: [
1435
1522
  { retry_count: "asc" },
@@ -1439,14 +1526,6 @@ class ConverterWorker extends WorkerBase {
1439
1526
  if (!candidate) {
1440
1527
  return null;
1441
1528
  }
1442
- const task = await prisma.task.findUnique({
1443
- where: { id: candidate.task },
1444
- select: { status: true }
1445
- });
1446
- if (!task || task.status !== TaskStatus.PROCESSING && task.status !== TaskStatus.COMPLETED) {
1447
- checkedTaskIds.push(candidate.task);
1448
- continue;
1449
- }
1450
1529
  const result = await prisma.taskDetail.updateMany({
1451
1530
  where: {
1452
1531
  id: candidate.id,
@@ -2303,11 +2382,43 @@ class WorkerOrchestrator {
2303
2382
  if (orphanedMergingTasks.count > 0) {
2304
2383
  console.log(`[WorkerOrchestrator] Reset ${orphanedMergingTasks.count} orphaned MERGING tasks to READY_TO_MERGE`);
2305
2384
  }
2385
+ const terminalTaskStatuses = [
2386
+ TaskStatus.CREATED,
2387
+ TaskStatus.FAILED,
2388
+ TaskStatus.CANCELLED,
2389
+ TaskStatus.COMPLETED,
2390
+ TaskStatus.PARTIAL_FAILED
2391
+ ];
2392
+ const terminalTasks = await prisma.task.findMany({
2393
+ where: {
2394
+ status: { in: terminalTaskStatuses }
2395
+ },
2396
+ select: { id: true }
2397
+ });
2398
+ let orphanedPendingPages = 0;
2399
+ if (terminalTasks.length > 0) {
2400
+ const terminalTaskIds = terminalTasks.map((t) => t.id);
2401
+ const result2 = await prisma.taskDetail.updateMany({
2402
+ where: {
2403
+ task: { in: terminalTaskIds },
2404
+ status: PageStatus.PENDING
2405
+ },
2406
+ data: {
2407
+ status: PageStatus.FAILED,
2408
+ error: "Orphaned: parent task no longer active"
2409
+ }
2410
+ });
2411
+ orphanedPendingPages = result2.count;
2412
+ if (orphanedPendingPages > 0) {
2413
+ console.log(`[WorkerOrchestrator] Marked ${orphanedPendingPages} orphaned PENDING pages as FAILED (parent task in terminal state)`);
2414
+ }
2415
+ }
2306
2416
  const result = {
2307
2417
  orphanedPages: orphanedPages.count,
2308
2418
  orphanedSplittingTasks: orphanedSplittingTasks.count,
2309
2419
  orphanedMergingTasks: orphanedMergingTasks.count,
2310
- total: orphanedPages.count + orphanedSplittingTasks.count + orphanedMergingTasks.count
2420
+ orphanedPendingPages,
2421
+ total: orphanedPages.count + orphanedSplittingTasks.count + orphanedMergingTasks.count + orphanedPendingPages
2311
2422
  };
2312
2423
  if (result.total === 0) {
2313
2424
  console.log("[WorkerOrchestrator] No orphaned work found");
@@ -2321,6 +2432,7 @@ class WorkerOrchestrator {
2321
2432
  orphanedPages: 0,
2322
2433
  orphanedSplittingTasks: 0,
2323
2434
  orphanedMergingTasks: 0,
2435
+ orphanedPendingPages: 0,
2324
2436
  total: 0
2325
2437
  };
2326
2438
  }
@@ -2367,7 +2479,6 @@ const IPC_CHANNELS = {
2367
2479
  DOWNLOAD_MARKDOWN: "file:downloadMarkdown",
2368
2480
  SELECT_DIALOG: "file:selectDialog",
2369
2481
  UPLOAD: "file:upload",
2370
- UPLOAD_MULTIPLE: "file:uploadMultiple",
2371
2482
  UPLOAD_FILE_CONTENT: "file:uploadFileContent"
2372
2483
  },
2373
2484
  // Completion (LLM) channels
@@ -2379,7 +2490,7 @@ const IPC_CHANNELS = {
2379
2490
  function registerProviderHandlers() {
2380
2491
  ipcMain.handle(IPC_CHANNELS.PROVIDER.GET_ALL, async () => {
2381
2492
  try {
2382
- const providers = await providerRepository.findAll();
2493
+ const providers = await providerRepository.findAllIncludeDisabled();
2383
2494
  return { success: true, data: providers };
2384
2495
  } catch (error) {
2385
2496
  console.error("[IPC] provider:getAll error:", error);
@@ -2597,7 +2708,7 @@ const findById = async (id) => {
2597
2708
  const create = async (task) => {
2598
2709
  return await prisma.task.create({
2599
2710
  data: {
2600
- id: v4(),
2711
+ id: task?.id || v4(),
2601
2712
  filename: task?.filename || "",
2602
2713
  type: task?.type || "",
2603
2714
  page_range: task?.page_range || "",
@@ -2605,8 +2716,8 @@ const create = async (task) => {
2605
2716
  provider: task?.provider || 0,
2606
2717
  model: task?.model || "",
2607
2718
  model_name: task?.model_name || "",
2608
- progress: 0,
2609
- status: 0
2719
+ progress: task?.progress ?? 0,
2720
+ status: task?.status ?? 0
2610
2721
  }
2611
2722
  });
2612
2723
  };
@@ -3132,6 +3243,7 @@ function registerFileHandlers() {
3132
3243
  return { success: false, error: "Task ID and file path are required" };
3133
3244
  }
3134
3245
  if (!fs.existsSync(filePath)) {
3246
+ console.error(`[IPC] file:upload - Source file does not exist: ${filePath}`);
3135
3247
  return { success: false, error: "File does not exist" };
3136
3248
  }
3137
3249
  const baseUploadDir = fileLogic.getUploadDir();
@@ -3139,10 +3251,26 @@ function registerFileHandlers() {
3139
3251
  if (!fs.existsSync(uploadDir)) {
3140
3252
  fs.mkdirSync(uploadDir, { recursive: true });
3141
3253
  }
3254
+ try {
3255
+ fs.accessSync(uploadDir, fs.constants.W_OK);
3256
+ } catch {
3257
+ console.error(`[IPC] file:upload - Upload directory is not writable: ${uploadDir}`);
3258
+ return { success: false, error: `Upload directory is not writable: ${uploadDir}` };
3259
+ }
3142
3260
  const fileName = path.basename(filePath);
3143
3261
  const destPath = path.join(uploadDir, fileName);
3262
+ console.log(`[IPC] file:upload - Copying file: ${filePath} -> ${destPath}`);
3144
3263
  fs.copyFileSync(filePath, destPath);
3264
+ if (!fs.existsSync(destPath)) {
3265
+ console.error(`[IPC] file:upload - File copy verification failed, destination not found: ${destPath}`);
3266
+ return { success: false, error: "File copy failed: destination file not found after copy" };
3267
+ }
3145
3268
  const stats = fs.statSync(destPath);
3269
+ if (stats.size === 0) {
3270
+ console.error(`[IPC] file:upload - Copied file is empty: ${destPath}`);
3271
+ return { success: false, error: "File copy failed: destination file is empty" };
3272
+ }
3273
+ console.log(`[IPC] file:upload - File copied successfully: ${destPath} (${stats.size} bytes)`);
3146
3274
  const fileInfo = {
3147
3275
  originalName: fileName,
3148
3276
  savedName: fileName,
@@ -3157,45 +3285,6 @@ function registerFileHandlers() {
3157
3285
  }
3158
3286
  }
3159
3287
  );
3160
- ipcMain.handle(
3161
- IPC_CHANNELS.FILE.UPLOAD_MULTIPLE,
3162
- async (_, taskId, filePaths) => {
3163
- try {
3164
- if (!taskId || !Array.isArray(filePaths) || filePaths.length === 0) {
3165
- return { success: false, error: "Task ID and file path list are required" };
3166
- }
3167
- const uploadResults = [];
3168
- for (const filePath of filePaths) {
3169
- if (!fs.existsSync(filePath)) {
3170
- continue;
3171
- }
3172
- const baseUploadDir = fileLogic.getUploadDir();
3173
- const uploadDir = path.join(baseUploadDir, taskId);
3174
- if (!fs.existsSync(uploadDir)) {
3175
- fs.mkdirSync(uploadDir, { recursive: true });
3176
- }
3177
- const fileName = path.basename(filePath);
3178
- const destPath = path.join(uploadDir, fileName);
3179
- fs.copyFileSync(filePath, destPath);
3180
- const stats = fs.statSync(destPath);
3181
- uploadResults.push({
3182
- originalName: fileName,
3183
- savedName: fileName,
3184
- path: destPath,
3185
- size: stats.size,
3186
- taskId
3187
- });
3188
- }
3189
- return {
3190
- success: true,
3191
- data: { message: "Files uploaded successfully", files: uploadResults }
3192
- };
3193
- } catch (error) {
3194
- console.error("[IPC] file:uploadMultiple error:", error);
3195
- return { success: false, error: error.message };
3196
- }
3197
- }
3198
- );
3199
3288
  ipcMain.handle(
3200
3289
  IPC_CHANNELS.FILE.UPLOAD_FILE_CONTENT,
3201
3290
  async (_, taskId, fileName, fileBuffer) => {
@@ -3208,13 +3297,30 @@ function registerFileHandlers() {
3208
3297
  if (!fs.existsSync(uploadDir)) {
3209
3298
  fs.mkdirSync(uploadDir, { recursive: true });
3210
3299
  }
3211
- const destPath = path.join(uploadDir, fileName);
3300
+ try {
3301
+ fs.accessSync(uploadDir, fs.constants.W_OK);
3302
+ } catch {
3303
+ console.error(`[IPC] file:uploadFileContent - Upload directory is not writable: ${uploadDir}`);
3304
+ return { success: false, error: `Upload directory is not writable: ${uploadDir}` };
3305
+ }
3306
+ const safeName = path.basename(fileName);
3307
+ const destPath = path.join(uploadDir, safeName);
3212
3308
  const buffer = Buffer.from(fileBuffer);
3309
+ console.log(`[IPC] file:uploadFileContent - Writing file: ${destPath} (${buffer.length} bytes)`);
3213
3310
  fs.writeFileSync(destPath, buffer);
3311
+ if (!fs.existsSync(destPath)) {
3312
+ console.error(`[IPC] file:uploadFileContent - File write verification failed, not found: ${destPath}`);
3313
+ return { success: false, error: "File write failed: file not found after write" };
3314
+ }
3214
3315
  const stats = fs.statSync(destPath);
3316
+ if (stats.size === 0) {
3317
+ console.error(`[IPC] file:uploadFileContent - Written file is empty: ${destPath}`);
3318
+ return { success: false, error: "File write failed: file is empty" };
3319
+ }
3320
+ console.log(`[IPC] file:uploadFileContent - File written successfully: ${destPath} (${stats.size} bytes)`);
3215
3321
  const fileInfo = {
3216
- originalName: fileName,
3217
- savedName: fileName,
3322
+ originalName: safeName,
3323
+ savedName: safeName,
3218
3324
  path: destPath,
3219
3325
  size: stats.size,
3220
3326
  taskId
@@ -3234,7 +3340,10 @@ function registerCompletionHandlers() {
3234
3340
  async (_, providerId, modelId, url) => {
3235
3341
  try {
3236
3342
  if (!providerId || !modelId || !url) {
3237
- return { success: false, error: "providerId, modelId, and url are required" };
3343
+ return {
3344
+ success: false,
3345
+ error: "providerId, modelId, and url are required"
3346
+ };
3238
3347
  }
3239
3348
  const result = await modelLogic.completion(providerId, {
3240
3349
  model: modelId,
@@ -3266,11 +3375,15 @@ function registerCompletionHandlers() {
3266
3375
  async (_, providerId, modelId) => {
3267
3376
  try {
3268
3377
  if (!providerId || !modelId) {
3269
- return { success: false, error: "providerId and modelId are required" };
3378
+ return {
3379
+ success: false,
3380
+ error: "providerId and modelId are required"
3381
+ };
3270
3382
  }
3271
- const testImageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
3383
+ const testImageBase64 = "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";
3272
3384
  const result = await modelLogic.completion(providerId, {
3273
3385
  model: modelId,
3386
+ maxTokens: 8,
3274
3387
  messages: [
3275
3388
  {
3276
3389
  role: "user",
@@ -3283,7 +3396,7 @@ function registerCompletionHandlers() {
3283
3396
  },
3284
3397
  {
3285
3398
  type: "text",
3286
- text: "Test connection."
3399
+ text: "Please identify the largest letter in the image."
3287
3400
  }
3288
3401
  ]
3289
3402
  }
@@ -37,7 +37,6 @@ electron.contextBridge.exposeInMainWorld("api", {
37
37
  file: {
38
38
  selectDialog: () => electron.ipcRenderer.invoke("file:selectDialog"),
39
39
  upload: (taskId, filePath) => electron.ipcRenderer.invoke("file:upload", taskId, filePath),
40
- uploadMultiple: (taskId, filePaths) => electron.ipcRenderer.invoke("file:uploadMultiple", taskId, filePaths),
41
40
  uploadFileContent: (taskId, fileName, fileBuffer) => electron.ipcRenderer.invoke("file:uploadFileContent", taskId, fileName, fileBuffer),
42
41
  getImagePath: (taskId, page) => electron.ipcRenderer.invoke("file:getImagePath", taskId, page),
43
42
  downloadMarkdown: (taskId) => electron.ipcRenderer.invoke("file:downloadMarkdown", taskId)