markpdfdown 0.4.2 → 0.4.5
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.
package/dist/main/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { app, ipcMain, dialog, shell, safeStorage, protocol,
|
|
1
|
+
import { app, ipcMain, dialog, clipboard, nativeImage, shell, safeStorage, protocol, BrowserWindow } from "electron";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs, { promises } from "fs";
|
|
4
4
|
import isDev from "electron-is-dev";
|
|
@@ -1653,7 +1653,14 @@ class ConverterWorker extends WorkerBase {
|
|
|
1653
1653
|
}
|
|
1654
1654
|
const task = await tx.task.findUnique({
|
|
1655
1655
|
where: { id: page.task },
|
|
1656
|
-
select: {
|
|
1656
|
+
select: {
|
|
1657
|
+
status: true,
|
|
1658
|
+
pages: true,
|
|
1659
|
+
completed_count: true,
|
|
1660
|
+
failed_count: true,
|
|
1661
|
+
provider: true,
|
|
1662
|
+
model: true
|
|
1663
|
+
}
|
|
1657
1664
|
});
|
|
1658
1665
|
if (!task) {
|
|
1659
1666
|
throw new Error("Task not found");
|
|
@@ -1672,7 +1679,9 @@ class ConverterWorker extends WorkerBase {
|
|
|
1672
1679
|
completed_at: /* @__PURE__ */ new Date(),
|
|
1673
1680
|
worker_id: null,
|
|
1674
1681
|
// Release worker
|
|
1675
|
-
error: null
|
|
1682
|
+
error: null,
|
|
1683
|
+
provider: task.provider,
|
|
1684
|
+
model: task.model
|
|
1676
1685
|
}
|
|
1677
1686
|
});
|
|
1678
1687
|
const updatedTask = await tx.task.update({
|
|
@@ -1737,7 +1746,14 @@ class ConverterWorker extends WorkerBase {
|
|
|
1737
1746
|
}
|
|
1738
1747
|
const task = await tx.task.findUnique({
|
|
1739
1748
|
where: { id: page.task },
|
|
1740
|
-
select: {
|
|
1749
|
+
select: {
|
|
1750
|
+
status: true,
|
|
1751
|
+
pages: true,
|
|
1752
|
+
completed_count: true,
|
|
1753
|
+
failed_count: true,
|
|
1754
|
+
provider: true,
|
|
1755
|
+
model: true
|
|
1756
|
+
}
|
|
1741
1757
|
});
|
|
1742
1758
|
if (!task) {
|
|
1743
1759
|
throw new Error("Task not found");
|
|
@@ -1751,8 +1767,10 @@ class ConverterWorker extends WorkerBase {
|
|
|
1751
1767
|
status: PageStatus.FAILED,
|
|
1752
1768
|
error: errorMessage,
|
|
1753
1769
|
completed_at: /* @__PURE__ */ new Date(),
|
|
1754
|
-
worker_id: null
|
|
1770
|
+
worker_id: null,
|
|
1755
1771
|
// Release worker
|
|
1772
|
+
provider: task.provider,
|
|
1773
|
+
model: task.model
|
|
1756
1774
|
}
|
|
1757
1775
|
});
|
|
1758
1776
|
const updatedTask = await tx.task.update({
|
|
@@ -2620,6 +2638,7 @@ const IPC_CHANNELS = {
|
|
|
2620
2638
|
GET_ALL: "task:getAll",
|
|
2621
2639
|
GET_BY_ID: "task:getById",
|
|
2622
2640
|
UPDATE: "task:update",
|
|
2641
|
+
RETRY: "task:retry",
|
|
2623
2642
|
DELETE: "task:delete",
|
|
2624
2643
|
HAS_RUNNING: "task:hasRunningTasks"
|
|
2625
2644
|
},
|
|
@@ -2635,6 +2654,7 @@ const IPC_CHANNELS = {
|
|
|
2635
2654
|
FILE: {
|
|
2636
2655
|
GET_IMAGE_PATH: "file:getImagePath",
|
|
2637
2656
|
DOWNLOAD_MARKDOWN: "file:downloadMarkdown",
|
|
2657
|
+
COPY_IMAGE_TO_CLIPBOARD: "file:copyImageToClipboard",
|
|
2638
2658
|
SELECT_DIALOG: "file:selectDialog",
|
|
2639
2659
|
UPLOAD: "file:upload",
|
|
2640
2660
|
UPLOAD_FILE_CONTENT: "file:uploadFileContent"
|
|
@@ -3122,6 +3142,123 @@ function registerTaskHandlers() {
|
|
|
3122
3142
|
}
|
|
3123
3143
|
}
|
|
3124
3144
|
);
|
|
3145
|
+
ipcMain.handle(
|
|
3146
|
+
IPC_CHANNELS.TASK.RETRY,
|
|
3147
|
+
async (_, params) => {
|
|
3148
|
+
try {
|
|
3149
|
+
const payload = typeof params === "string" ? { taskId: params } : params;
|
|
3150
|
+
const taskId = payload?.taskId;
|
|
3151
|
+
if (!taskId) {
|
|
3152
|
+
return { success: false, error: "Task ID is required" };
|
|
3153
|
+
}
|
|
3154
|
+
const hasProviderOverride = payload?.providerId !== void 0;
|
|
3155
|
+
const hasModelOverride = payload?.modelId !== void 0;
|
|
3156
|
+
const hasAnyModelOverride = hasProviderOverride || hasModelOverride;
|
|
3157
|
+
if (hasAnyModelOverride && (!hasProviderOverride || !hasModelOverride)) {
|
|
3158
|
+
return { success: false, error: "providerId and modelId must be provided together" };
|
|
3159
|
+
}
|
|
3160
|
+
const updatedTask = await prisma.$transaction(async (tx) => {
|
|
3161
|
+
const task = await tx.task.findUnique({
|
|
3162
|
+
where: { id: taskId }
|
|
3163
|
+
});
|
|
3164
|
+
if (!task) {
|
|
3165
|
+
throw new Error("Task not found");
|
|
3166
|
+
}
|
|
3167
|
+
const retryableStatuses = [
|
|
3168
|
+
TaskStatus.FAILED,
|
|
3169
|
+
TaskStatus.COMPLETED,
|
|
3170
|
+
TaskStatus.CANCELLED,
|
|
3171
|
+
TaskStatus.PARTIAL_FAILED
|
|
3172
|
+
];
|
|
3173
|
+
if (!retryableStatuses.includes(task.status)) {
|
|
3174
|
+
throw new Error("Task is not retryable");
|
|
3175
|
+
}
|
|
3176
|
+
let targetProvider = task.provider;
|
|
3177
|
+
let targetModel = task.model;
|
|
3178
|
+
let targetModelName = task.model_name;
|
|
3179
|
+
if (hasAnyModelOverride) {
|
|
3180
|
+
const providerId = payload.providerId;
|
|
3181
|
+
const modelId = payload.modelId;
|
|
3182
|
+
const provider = await tx.provider.findUnique({
|
|
3183
|
+
where: { id: providerId },
|
|
3184
|
+
select: { id: true, name: true, status: true }
|
|
3185
|
+
});
|
|
3186
|
+
if (!provider || provider.status !== 0) {
|
|
3187
|
+
throw new Error("Provider not found or disabled");
|
|
3188
|
+
}
|
|
3189
|
+
const model = await tx.model.findUnique({
|
|
3190
|
+
where: {
|
|
3191
|
+
id_provider: {
|
|
3192
|
+
id: modelId,
|
|
3193
|
+
provider: providerId
|
|
3194
|
+
}
|
|
3195
|
+
},
|
|
3196
|
+
select: { id: true, name: true, provider: true }
|
|
3197
|
+
});
|
|
3198
|
+
if (!model) {
|
|
3199
|
+
throw new Error("Model not found for provider");
|
|
3200
|
+
}
|
|
3201
|
+
targetProvider = providerId;
|
|
3202
|
+
targetModel = modelId;
|
|
3203
|
+
targetModelName = `${model.name} | ${provider.name}`;
|
|
3204
|
+
}
|
|
3205
|
+
const detailCount = await tx.taskDetail.count({
|
|
3206
|
+
where: { task: taskId }
|
|
3207
|
+
});
|
|
3208
|
+
if (detailCount > 0) {
|
|
3209
|
+
await tx.taskDetail.updateMany({
|
|
3210
|
+
where: { task: taskId },
|
|
3211
|
+
data: {
|
|
3212
|
+
status: PageStatus.PENDING,
|
|
3213
|
+
retry_count: 0,
|
|
3214
|
+
error: null,
|
|
3215
|
+
worker_id: null,
|
|
3216
|
+
started_at: null,
|
|
3217
|
+
completed_at: null,
|
|
3218
|
+
input_tokens: 0,
|
|
3219
|
+
output_tokens: 0,
|
|
3220
|
+
conversion_time: 0,
|
|
3221
|
+
content: "",
|
|
3222
|
+
provider: targetProvider,
|
|
3223
|
+
model: targetModel
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
}
|
|
3227
|
+
return await tx.task.update({
|
|
3228
|
+
where: { id: taskId },
|
|
3229
|
+
data: {
|
|
3230
|
+
provider: targetProvider,
|
|
3231
|
+
model: targetModel,
|
|
3232
|
+
model_name: targetModelName,
|
|
3233
|
+
status: detailCount > 0 ? TaskStatus.PROCESSING : TaskStatus.PENDING,
|
|
3234
|
+
progress: 0,
|
|
3235
|
+
completed_count: 0,
|
|
3236
|
+
failed_count: 0,
|
|
3237
|
+
error: null,
|
|
3238
|
+
merged_path: null,
|
|
3239
|
+
worker_id: null
|
|
3240
|
+
}
|
|
3241
|
+
});
|
|
3242
|
+
}, {
|
|
3243
|
+
isolationLevel: "Serializable"
|
|
3244
|
+
});
|
|
3245
|
+
eventBus.emitTaskEvent(TaskEventType.TASK_UPDATED, {
|
|
3246
|
+
taskId,
|
|
3247
|
+
task: updatedTask,
|
|
3248
|
+
timestamp: Date.now()
|
|
3249
|
+
});
|
|
3250
|
+
eventBus.emitTaskEvent(TaskEventType.TASK_STATUS_CHANGED, {
|
|
3251
|
+
taskId,
|
|
3252
|
+
task: { status: updatedTask.status },
|
|
3253
|
+
timestamp: Date.now()
|
|
3254
|
+
});
|
|
3255
|
+
return { success: true, data: updatedTask };
|
|
3256
|
+
} catch (error) {
|
|
3257
|
+
console.error("[IPC] task:retry error:", error);
|
|
3258
|
+
return { success: false, error: error.message };
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
);
|
|
3125
3262
|
ipcMain.handle(IPC_CHANNELS.TASK.DELETE, async (_, id) => {
|
|
3126
3263
|
try {
|
|
3127
3264
|
await fileLogic.deleteTaskFiles(id);
|
|
@@ -3232,11 +3369,19 @@ function registerTaskDetailHandlers() {
|
|
|
3232
3369
|
);
|
|
3233
3370
|
ipcMain.handle(
|
|
3234
3371
|
IPC_CHANNELS.TASK_DETAIL.RETRY,
|
|
3235
|
-
async (_,
|
|
3372
|
+
async (_, params) => {
|
|
3236
3373
|
try {
|
|
3374
|
+
const payload = typeof params === "number" ? { pageId: params } : params;
|
|
3375
|
+
const pageId = payload?.pageId;
|
|
3237
3376
|
if (!pageId) {
|
|
3238
3377
|
return { success: false, error: "Page ID is required" };
|
|
3239
3378
|
}
|
|
3379
|
+
const hasProviderOverride = payload?.providerId !== void 0;
|
|
3380
|
+
const hasModelOverride = payload?.modelId !== void 0;
|
|
3381
|
+
const hasAnyModelOverride = hasProviderOverride || hasModelOverride;
|
|
3382
|
+
if (hasAnyModelOverride && (!hasProviderOverride || !hasModelOverride)) {
|
|
3383
|
+
return { success: false, error: "providerId and modelId must be provided together" };
|
|
3384
|
+
}
|
|
3240
3385
|
const result = await prisma.$transaction(async (tx) => {
|
|
3241
3386
|
const page = await tx.taskDetail.findUnique({
|
|
3242
3387
|
where: { id: pageId }
|
|
@@ -3256,6 +3401,33 @@ function registerTaskDetailHandlers() {
|
|
|
3256
3401
|
if (task.status === TaskStatus.CANCELLED) {
|
|
3257
3402
|
throw new Error("Task is cancelled, cannot retry");
|
|
3258
3403
|
}
|
|
3404
|
+
let targetProvider = page.provider;
|
|
3405
|
+
let targetModel = page.model;
|
|
3406
|
+
if (hasAnyModelOverride) {
|
|
3407
|
+
const providerId = payload.providerId;
|
|
3408
|
+
const modelId = payload.modelId;
|
|
3409
|
+
const provider = await tx.provider.findUnique({
|
|
3410
|
+
where: { id: providerId },
|
|
3411
|
+
select: { id: true, status: true }
|
|
3412
|
+
});
|
|
3413
|
+
if (!provider || provider.status !== 0) {
|
|
3414
|
+
throw new Error("Provider not found or disabled");
|
|
3415
|
+
}
|
|
3416
|
+
const model = await tx.model.findUnique({
|
|
3417
|
+
where: {
|
|
3418
|
+
id_provider: {
|
|
3419
|
+
id: modelId,
|
|
3420
|
+
provider: providerId
|
|
3421
|
+
}
|
|
3422
|
+
},
|
|
3423
|
+
select: { id: true }
|
|
3424
|
+
});
|
|
3425
|
+
if (!model) {
|
|
3426
|
+
throw new Error("Model not found for provider");
|
|
3427
|
+
}
|
|
3428
|
+
targetProvider = providerId;
|
|
3429
|
+
targetModel = modelId;
|
|
3430
|
+
}
|
|
3259
3431
|
const updatedPage = await tx.taskDetail.update({
|
|
3260
3432
|
where: { id: pageId },
|
|
3261
3433
|
data: {
|
|
@@ -3268,7 +3440,9 @@ function registerTaskDetailHandlers() {
|
|
|
3268
3440
|
input_tokens: 0,
|
|
3269
3441
|
output_tokens: 0,
|
|
3270
3442
|
conversion_time: 0,
|
|
3271
|
-
content: ""
|
|
3443
|
+
content: "",
|
|
3444
|
+
provider: targetProvider,
|
|
3445
|
+
model: targetModel
|
|
3272
3446
|
}
|
|
3273
3447
|
});
|
|
3274
3448
|
const decrementField = page.status === PageStatus.FAILED ? "failed_count" : "completed_count";
|
|
@@ -3444,6 +3618,20 @@ function registerTaskDetailHandlers() {
|
|
|
3444
3618
|
);
|
|
3445
3619
|
console.log("[IPC] TaskDetail handlers registered");
|
|
3446
3620
|
}
|
|
3621
|
+
async function createImageFromSource(imageSource) {
|
|
3622
|
+
const normalizedSource = imageSource.trim();
|
|
3623
|
+
const lowerSource = normalizedSource.toLowerCase();
|
|
3624
|
+
if (lowerSource.startsWith("data:image/")) {
|
|
3625
|
+
return nativeImage.createFromDataURL(normalizedSource);
|
|
3626
|
+
}
|
|
3627
|
+
if (lowerSource.startsWith("http://") || lowerSource.startsWith("https://")) {
|
|
3628
|
+
throw new Error("Remote image URLs are not allowed");
|
|
3629
|
+
}
|
|
3630
|
+
if (lowerSource.startsWith("file://")) {
|
|
3631
|
+
return nativeImage.createFromPath(fileURLToPath(normalizedSource));
|
|
3632
|
+
}
|
|
3633
|
+
return nativeImage.createFromPath(normalizedSource);
|
|
3634
|
+
}
|
|
3447
3635
|
function registerFileHandlers() {
|
|
3448
3636
|
ipcMain.handle(
|
|
3449
3637
|
IPC_CHANNELS.FILE.GET_IMAGE_PATH,
|
|
@@ -3506,6 +3694,25 @@ function registerFileHandlers() {
|
|
|
3506
3694
|
}
|
|
3507
3695
|
}
|
|
3508
3696
|
);
|
|
3697
|
+
ipcMain.handle(
|
|
3698
|
+
IPC_CHANNELS.FILE.COPY_IMAGE_TO_CLIPBOARD,
|
|
3699
|
+
async (_, imageSource) => {
|
|
3700
|
+
try {
|
|
3701
|
+
if (!imageSource) {
|
|
3702
|
+
return { success: false, error: "Image source is required" };
|
|
3703
|
+
}
|
|
3704
|
+
const image = await createImageFromSource(imageSource);
|
|
3705
|
+
if (image.isEmpty()) {
|
|
3706
|
+
return { success: false, error: "Image data is empty or invalid" };
|
|
3707
|
+
}
|
|
3708
|
+
clipboard.writeImage(image);
|
|
3709
|
+
return { success: true, data: { copied: true } };
|
|
3710
|
+
} catch (error) {
|
|
3711
|
+
console.error("[IPC] file:copyImageToClipboard error:", error);
|
|
3712
|
+
return { success: false, error: error.message || "Failed to copy image" };
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
);
|
|
3509
3716
|
ipcMain.handle(IPC_CHANNELS.FILE.SELECT_DIALOG, async (_, allowOffice) => {
|
|
3510
3717
|
try {
|
|
3511
3718
|
const pdfAndImageExtensions = ["pdf", "jpg", "jpeg", "png", "bmp", "gif"];
|
|
@@ -4373,6 +4580,88 @@ class CloudService {
|
|
|
4373
4580
|
static instance;
|
|
4374
4581
|
constructor() {
|
|
4375
4582
|
}
|
|
4583
|
+
extractDownloadFileName(contentDisposition, fallback) {
|
|
4584
|
+
const rfc5987Name = this.parseRFC5987Filename(contentDisposition);
|
|
4585
|
+
if (rfc5987Name) {
|
|
4586
|
+
return this.sanitizeDownloadFileName(rfc5987Name, fallback);
|
|
4587
|
+
}
|
|
4588
|
+
const plainName = this.parsePlainFilename(contentDisposition);
|
|
4589
|
+
if (!plainName) {
|
|
4590
|
+
return this.sanitizeDownloadFileName(fallback, fallback);
|
|
4591
|
+
}
|
|
4592
|
+
const repairedName = this.tryRepairUtf8Mojibake(plainName);
|
|
4593
|
+
return this.sanitizeDownloadFileName(repairedName || plainName, fallback);
|
|
4594
|
+
}
|
|
4595
|
+
parseRFC5987Filename(contentDisposition) {
|
|
4596
|
+
const match = contentDisposition.match(/filename\*\s*=\s*([^;]+)/i);
|
|
4597
|
+
if (!match) return null;
|
|
4598
|
+
const rawValue = match[1]?.trim();
|
|
4599
|
+
if (!rawValue) return null;
|
|
4600
|
+
const unquoted = rawValue.replace(/^"(.*)"$/, "$1");
|
|
4601
|
+
const parts = unquoted.match(/^([^']*)'[^']*'(.*)$/);
|
|
4602
|
+
if (!parts) return null;
|
|
4603
|
+
const charset = (parts[1] || "utf-8").trim().toLowerCase();
|
|
4604
|
+
const encodedValue = parts[2] || "";
|
|
4605
|
+
try {
|
|
4606
|
+
if (charset === "utf-8" || charset === "utf8") {
|
|
4607
|
+
return decodeURIComponent(encodedValue);
|
|
4608
|
+
}
|
|
4609
|
+
const bytes = this.percentDecodeToBytes(encodedValue);
|
|
4610
|
+
if (charset === "iso-8859-1" || charset === "latin1") {
|
|
4611
|
+
return Buffer.from(bytes).toString("latin1");
|
|
4612
|
+
}
|
|
4613
|
+
return Buffer.from(bytes).toString("utf8");
|
|
4614
|
+
} catch {
|
|
4615
|
+
return null;
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
parsePlainFilename(contentDisposition) {
|
|
4619
|
+
const match = contentDisposition.match(/filename\s*=\s*("(?:\\.|[^"])*"|[^;]+)/i);
|
|
4620
|
+
if (!match) return null;
|
|
4621
|
+
let value = match[1]?.trim();
|
|
4622
|
+
if (!value) return null;
|
|
4623
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
4624
|
+
value = value.slice(1, -1).replace(/\\"/g, '"');
|
|
4625
|
+
}
|
|
4626
|
+
return value;
|
|
4627
|
+
}
|
|
4628
|
+
percentDecodeToBytes(input) {
|
|
4629
|
+
const bytes = [];
|
|
4630
|
+
for (let i = 0; i < input.length; i++) {
|
|
4631
|
+
const ch = input[i];
|
|
4632
|
+
if (ch === "%" && i + 2 < input.length) {
|
|
4633
|
+
const hex = input.slice(i + 1, i + 3);
|
|
4634
|
+
const parsed = Number.parseInt(hex, 16);
|
|
4635
|
+
if (!Number.isNaN(parsed)) {
|
|
4636
|
+
bytes.push(parsed);
|
|
4637
|
+
i += 2;
|
|
4638
|
+
continue;
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
bytes.push(input.charCodeAt(i));
|
|
4642
|
+
}
|
|
4643
|
+
return bytes;
|
|
4644
|
+
}
|
|
4645
|
+
tryRepairUtf8Mojibake(input) {
|
|
4646
|
+
const hasCjk = /[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/.test(input);
|
|
4647
|
+
if (hasCjk) return null;
|
|
4648
|
+
const latinSupplementCount = Array.from(input).filter((ch) => {
|
|
4649
|
+
const code = ch.charCodeAt(0);
|
|
4650
|
+
return code >= 192 && code <= 255;
|
|
4651
|
+
}).length;
|
|
4652
|
+
if (latinSupplementCount < 2) return null;
|
|
4653
|
+
const repaired = Buffer.from(input, "latin1").toString("utf8");
|
|
4654
|
+
if (!repaired) return null;
|
|
4655
|
+
const repairedHasCjk = /[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/.test(repaired);
|
|
4656
|
+
const roundTrip = Buffer.from(repaired, "utf8").toString("latin1") === input;
|
|
4657
|
+
if (repairedHasCjk && roundTrip) {
|
|
4658
|
+
return repaired;
|
|
4659
|
+
}
|
|
4660
|
+
return null;
|
|
4661
|
+
}
|
|
4662
|
+
sanitizeDownloadFileName(input, fallback) {
|
|
4663
|
+
return path.basename(input).replace(/[\u0000-\u001f<>:"|?*]/g, "_") || fallback;
|
|
4664
|
+
}
|
|
4376
4665
|
normalizeCheckoutStatus(data) {
|
|
4377
4666
|
if (!data || typeof data !== "object") {
|
|
4378
4667
|
return null;
|
|
@@ -4600,11 +4889,17 @@ class CloudService {
|
|
|
4600
4889
|
/**
|
|
4601
4890
|
* Retry an entire task (creates a new task)
|
|
4602
4891
|
*/
|
|
4603
|
-
async retryTask(id) {
|
|
4892
|
+
async retryTask(id, model) {
|
|
4604
4893
|
try {
|
|
4605
|
-
const
|
|
4606
|
-
|
|
4607
|
-
|
|
4894
|
+
const hasModelOverride = typeof model === "string" && model.length > 0;
|
|
4895
|
+
const res = await authManager.fetchWithAuth(
|
|
4896
|
+
`${API_BASE_URL}/api/v1/tasks/${encodeURIComponent(id)}/retry`,
|
|
4897
|
+
hasModelOverride ? {
|
|
4898
|
+
method: "POST",
|
|
4899
|
+
headers: { "Content-Type": "application/json" },
|
|
4900
|
+
body: JSON.stringify({ model })
|
|
4901
|
+
} : { method: "POST" }
|
|
4902
|
+
);
|
|
4608
4903
|
if (!res.ok) {
|
|
4609
4904
|
const errorBody = await res.json().catch(() => null);
|
|
4610
4905
|
return {
|
|
@@ -4628,11 +4923,16 @@ class CloudService {
|
|
|
4628
4923
|
/**
|
|
4629
4924
|
* Retry a single page
|
|
4630
4925
|
*/
|
|
4631
|
-
async retryPage(taskId, pageNumber) {
|
|
4926
|
+
async retryPage(taskId, pageNumber, model) {
|
|
4632
4927
|
try {
|
|
4928
|
+
const hasModelOverride = typeof model === "string" && model.length > 0;
|
|
4633
4929
|
const res = await authManager.fetchWithAuth(
|
|
4634
4930
|
`${API_BASE_URL}/api/v1/tasks/${encodeURIComponent(taskId)}/pages/${encodeURIComponent(String(pageNumber))}/retry`,
|
|
4635
|
-
|
|
4931
|
+
hasModelOverride ? {
|
|
4932
|
+
method: "POST",
|
|
4933
|
+
headers: { "Content-Type": "application/json" },
|
|
4934
|
+
body: JSON.stringify({ model })
|
|
4935
|
+
} : { method: "POST" }
|
|
4636
4936
|
);
|
|
4637
4937
|
if (!res.ok) {
|
|
4638
4938
|
const errorBody = await res.json().catch(() => null);
|
|
@@ -4694,9 +4994,8 @@ class CloudService {
|
|
|
4694
4994
|
};
|
|
4695
4995
|
}
|
|
4696
4996
|
const contentDisposition = res.headers.get("Content-Disposition") || "";
|
|
4697
|
-
const
|
|
4698
|
-
const
|
|
4699
|
-
const fileName = path.basename(rawName).replace(/[\u0000-\u001f<>:"|?*]/g, "_") || `task-${id}.pdf`;
|
|
4997
|
+
const fallbackName = `task-${id}.pdf`;
|
|
4998
|
+
const fileName = this.extractDownloadFileName(contentDisposition, fallbackName);
|
|
4700
4999
|
const buffer = await res.arrayBuffer();
|
|
4701
5000
|
return { success: true, data: { buffer, fileName } };
|
|
4702
5001
|
} catch (error) {
|
|
@@ -5314,9 +5613,10 @@ function registerCloudHandlers() {
|
|
|
5314
5613
|
};
|
|
5315
5614
|
}
|
|
5316
5615
|
});
|
|
5317
|
-
ipcMain.handle("cloud:retryTask", async (_,
|
|
5616
|
+
ipcMain.handle("cloud:retryTask", async (_, params) => {
|
|
5318
5617
|
try {
|
|
5319
|
-
|
|
5618
|
+
const payload = typeof params === "string" ? { id: params } : params;
|
|
5619
|
+
return await cloudService.retryTask(payload.id, payload.model);
|
|
5320
5620
|
} catch (error) {
|
|
5321
5621
|
console.error("[IPC] cloud:retryTask error:", error);
|
|
5322
5622
|
return {
|
|
@@ -5338,7 +5638,7 @@ function registerCloudHandlers() {
|
|
|
5338
5638
|
});
|
|
5339
5639
|
ipcMain.handle("cloud:retryPage", async (_, params) => {
|
|
5340
5640
|
try {
|
|
5341
|
-
return await cloudService.retryPage(params.taskId, params.pageNumber);
|
|
5641
|
+
return await cloudService.retryPage(params.taskId, params.pageNumber, params.model);
|
|
5342
5642
|
} catch (error) {
|
|
5343
5643
|
console.error("[IPC] cloud:retryPage error:", error);
|
|
5344
5644
|
return {
|
package/dist/preload/index.js
CHANGED
|
@@ -25,6 +25,7 @@ electron.contextBridge.exposeInMainWorld("api", {
|
|
|
25
25
|
getAll: (params) => electron.ipcRenderer.invoke("task:getAll", params),
|
|
26
26
|
getById: (id) => electron.ipcRenderer.invoke("task:getById", id),
|
|
27
27
|
update: (id, data) => electron.ipcRenderer.invoke("task:update", id, data),
|
|
28
|
+
retry: (params) => electron.ipcRenderer.invoke("task:retry", params),
|
|
28
29
|
delete: (id) => electron.ipcRenderer.invoke("task:delete", id),
|
|
29
30
|
hasRunningTasks: () => electron.ipcRenderer.invoke("task:hasRunningTasks")
|
|
30
31
|
},
|
|
@@ -32,7 +33,7 @@ electron.contextBridge.exposeInMainWorld("api", {
|
|
|
32
33
|
taskDetail: {
|
|
33
34
|
getByPage: (taskId, page) => electron.ipcRenderer.invoke("taskDetail:getByPage", taskId, page),
|
|
34
35
|
getAllByTask: (taskId) => electron.ipcRenderer.invoke("taskDetail:getAllByTask", taskId),
|
|
35
|
-
retry: (
|
|
36
|
+
retry: (params) => electron.ipcRenderer.invoke("taskDetail:retry", params),
|
|
36
37
|
retryFailed: (taskId) => electron.ipcRenderer.invoke("taskDetail:retryFailed", taskId)
|
|
37
38
|
},
|
|
38
39
|
// ==================== File APIs ====================
|
|
@@ -41,7 +42,8 @@ electron.contextBridge.exposeInMainWorld("api", {
|
|
|
41
42
|
upload: (taskId, filePath) => electron.ipcRenderer.invoke("file:upload", taskId, filePath),
|
|
42
43
|
uploadFileContent: (taskId, fileName, fileBuffer) => electron.ipcRenderer.invoke("file:uploadFileContent", taskId, fileName, fileBuffer),
|
|
43
44
|
getImagePath: (taskId, page) => electron.ipcRenderer.invoke("file:getImagePath", taskId, page),
|
|
44
|
-
downloadMarkdown: (taskId) => electron.ipcRenderer.invoke("file:downloadMarkdown", taskId)
|
|
45
|
+
downloadMarkdown: (taskId) => electron.ipcRenderer.invoke("file:downloadMarkdown", taskId),
|
|
46
|
+
copyImageToClipboard: (imageSource) => electron.ipcRenderer.invoke("file:copyImageToClipboard", imageSource)
|
|
45
47
|
},
|
|
46
48
|
// ==================== Completion APIs ====================
|
|
47
49
|
completion: {
|
|
@@ -62,7 +64,7 @@ electron.contextBridge.exposeInMainWorld("api", {
|
|
|
62
64
|
getTaskById: (id) => electron.ipcRenderer.invoke("cloud:getTaskById", id),
|
|
63
65
|
getTaskPages: (params) => electron.ipcRenderer.invoke("cloud:getTaskPages", params),
|
|
64
66
|
cancelTask: (id) => electron.ipcRenderer.invoke("cloud:cancelTask", id),
|
|
65
|
-
retryTask: (
|
|
67
|
+
retryTask: (params) => electron.ipcRenderer.invoke("cloud:retryTask", params),
|
|
66
68
|
deleteTask: (id) => electron.ipcRenderer.invoke("cloud:deleteTask", id),
|
|
67
69
|
retryPage: (params) => electron.ipcRenderer.invoke("cloud:retryPage", params),
|
|
68
70
|
getTaskResult: (id) => electron.ipcRenderer.invoke("cloud:getTaskResult", id),
|