| 1 | /** Pure utility functions used across IPC handlers. */ |
| 2 | |
| 3 | export const parseJsonObject = (value: unknown): Record<string, unknown> => { |
| 4 | if (value && typeof value === "object" && !Array.isArray(value)) { |
| 5 | return value as Record<string, unknown>; |
| 6 | } |
| 7 | if (typeof value !== "string" || value.trim().length === 0) return {}; |
| 8 | try { |
| 9 | const parsed = JSON.parse(value) as unknown; |
| 10 | return parsed && typeof parsed === "object" && !Array.isArray(parsed) |
| 11 | ? (parsed as Record<string, unknown>) |
| 12 | : {}; |
| 13 | } catch { |
| 14 | return {}; |
| 15 | } |
| 16 | }; |
| 17 | |
| 18 | export const normalizeSession = (session: Record<string, unknown> | null | undefined) => { |
| 19 | if (!session) return session; |
| 20 | return { |
| 21 | ...session, |
| 22 | styleId: session.styleId ?? session.style_id ?? null, |
| 23 | page_count: session.page_count ?? session.pageCount ?? null, |
| 24 | slideSizeId: session.slideSizeId ?? session.slide_size_id ?? null, |
| 25 | slideWidth: session.slideWidth ?? session.slide_width ?? null, |
| 26 | slideHeight: session.slideHeight ?? session.slide_height ?? null, |
| 27 | referenceDocumentPath: |
| 28 | session.referenceDocumentPath ?? session.reference_document_path ?? null, |
| 29 | reference_document_path: |
| 30 | session.reference_document_path ?? session.referenceDocumentPath ?? null, |
| 31 | created_at: session.created_at ?? session.createdAt ?? null, |
| 32 | updated_at: session.updated_at ?? session.updatedAt ?? null, |
| 33 | generation_duration_sec: |
| 34 | session.generation_duration_sec ?? session.generationDurationSec ?? null, |
| 35 | generated_count: session.generated_count ?? session.generatedCount ?? null, |
| 36 | failed_count: session.failed_count ?? session.failedCount ?? null, |
| 37 | }; |
| 38 | }; |
| 39 | |
| 40 | export const normalizeMessage = (message: Record<string, unknown>) => { |
| 41 | const normalizeAssetPaths = (raw: unknown, prefix: "./images/" | "./videos/") => { |
| 42 | if (Array.isArray(raw)) { |
| 43 | return raw |
| 44 | .map((item) => String(item || "").trim()) |
| 45 | .filter((item) => item.startsWith(prefix)) |
| 46 | .slice(0, 10); |
| 47 | } |
| 48 | if (typeof raw === "string" && raw.trim().length > 0) { |
| 49 | try { |
| 50 | const parsed = JSON.parse(raw) as unknown; |
| 51 | if (Array.isArray(parsed)) { |
| 52 | return parsed |
| 53 | .map((item) => String(item || "").trim()) |
| 54 | .filter((item) => item.startsWith(prefix)) |
| 55 | .slice(0, 10); |
| 56 | } |
| 57 | } catch { |
| 58 | // ignore invalid JSON payload |
| 59 | } |
| 60 | } |
| 61 | return [] as string[]; |
| 62 | }; |
| 63 | |
| 64 | const normalizedImagePaths = normalizeAssetPaths( |
| 65 | message.image_paths ?? message.imagePaths, |
| 66 | "./images/" |
| 67 | ); |
| 68 | const normalizedVideoPaths = normalizeAssetPaths( |
| 69 | message.video_paths ?? message.videoPaths, |
| 70 | "./videos/" |
| 71 | ); |
| 72 | |
| 73 | return { |
| 74 | ...message, |
| 75 | session_id: message.session_id ?? message.sessionId ?? null, |
| 76 | chat_scope: message.chat_scope ?? message.chatScope ?? "main", |
| 77 | page_id: message.page_id ?? message.pageId ?? null, |
| 78 | image_paths: normalizedImagePaths, |
| 79 | video_paths: normalizedVideoPaths, |
| 80 | tool_name: message.tool_name ?? message.toolName ?? null, |
| 81 | tool_call_id: message.tool_call_id ?? message.toolCallId ?? null, |
| 82 | token_count: message.token_count ?? message.tokenCount ?? null, |
| 83 | created_at: message.created_at ?? message.createdAt ?? null, |
| 84 | }; |
| 85 | }; |
| 86 | |
| 87 | export const sleep = (ms: number, signal?: AbortSignal) => |
| 88 | new Promise<void>((resolve, reject) => { |
| 89 | const timer = setTimeout(() => { |
| 90 | cleanup(); |
| 91 | resolve(); |
| 92 | }, ms); |
| 93 | |
| 94 | const onAbort = () => { |
| 95 | clearTimeout(timer); |
| 96 | cleanup(); |
| 97 | reject(new Error("Generation cancelled")); |
| 98 | }; |
| 99 | |
| 100 | const cleanup = () => { |
| 101 | signal?.removeEventListener("abort", onAbort); |
| 102 | }; |
| 103 | |
| 104 | if (signal) { |
| 105 | signal.addEventListener("abort", onAbort, { once: true }); |
| 106 | } |
| 107 | }); |
| 108 | |
| 109 | export const extractOutlineTitles = (prompt: string): string[] => |
| 110 | prompt |
| 111 | .split(/\r?\n/) |
| 112 | .map((line) => line.trim()) |
| 113 | .filter(Boolean) |
| 114 | .map((line) => { |
| 115 | const explicit = line.match(/^第\s*[一二三四五六七八九十百\d]+\s*页\s*[::]\s*(.+)$/i); |
| 116 | if (explicit?.[1]) return explicit[1].trim(); |
| 117 | const numbered = line.match(/^\d+\s*[.、]\s*(.+)$/); |
| 118 | if (numbered?.[1]) return numbered[1].trim(); |
| 119 | return ""; |
| 120 | }) |
| 121 | .filter((line) => line.length > 0); |
| 122 | |
| 123 | export { extractJsonBlock, extractModelText } from '../agent-runtime/model' |
| 124 |