export function getApiHeaders(): Record { const saved = localStorage.getItem("patent_api_settings"); if (!saved) return {}; try { const settings = JSON.parse(saved); if (settings) { return { "x-user-api-provider": settings.defaultProvider || settings.provider || "gemini", "x-user-gemini-api-key": settings.geminiApiKey || "", "x-user-gemini-model": settings.geminiModel || "", "x-user-deepseek-api-key": settings.deepseekApiKey || "", "x-user-deepseek-base-url": settings.deepseekBaseUrl || "", "x-user-deepseek-model": settings.deepseekModel || "", // Backward compatibility "x-user-api-key": settings.apiKey || "", "x-user-api-base-url": settings.baseUrl || "", "x-user-api-model": settings.model || "", }; } } catch (e) { console.error("Failed to load custom API settings", e); } return {}; } export async function customFetch(input: RequestInfo | URL, init?: RequestInit & { timeout?: number }): Promise { const url = typeof input === "string" ? input : (input instanceof URL ? input.href : (input && "url" in input ? input.url : "")); const timeoutMs = init?.timeout ?? (url.includes("/api/") ? 40000 : undefined); let controller: AbortController | null = null; let timeoutId: any = null; if (timeoutMs) { controller = new AbortController(); timeoutId = setTimeout(() => { if (controller) controller.abort(); }, timeoutMs); init = init || {}; init.signal = controller.signal; } if (url.includes("/api/")) { const customHeaders = getApiHeaders(); if (Object.keys(customHeaders).length > 0) { init = init || {}; const newHeaders = new Headers(init.headers || {}); Object.entries(customHeaders).forEach(([key, val]) => { if (val) { newHeaders.set(key, val); } }); init.headers = newHeaders; } } try { const response = await fetch(input, init); if (timeoutId) clearTimeout(timeoutId); return response; } catch (error: any) { if (timeoutId) clearTimeout(timeoutId); if (error.name === "AbortError") { throw new Error("请求超时:AI 接口响应时间过长。请稍后重试,或检查页面右上角「API设置」以配置更稳定的专属 API Key。"); } throw error; } } /** * Parses PDF files entirely in the client browser using pdfjs-dist via CDN. * This completely avoids sending large binary files through the reverse proxy, * bypassing any "413 Request Entity Too Large" errors. */ export async function parsePdfClient(file: File): Promise { return new Promise((resolve, reject) => { const executeParsing = async () => { try { const pdfjsLib = (window as any).pdfjsLib; if (!pdfjsLib) { throw new Error("浏览器中未加载 pdfjsLib 组件"); } // Use matching worker CDN link pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js"; const arrayBuffer = await file.arrayBuffer(); const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer }); const pdfDoc = await loadingTask.promise; let textContent = ""; for (let i = 1; i <= pdfDoc.numPages; i++) { const page = await pdfDoc.getPage(i); const textObj = await page.getTextContent(); const pageText = textObj.items .map((item: any) => item.str) .join(" "); textContent += pageText + "\n"; } if (textContent.trim().length < 5) { throw new Error("未在 PDF 中提取到有效文本。该文件可能为扫描件、图片、或已加密。建议在页面右上角「API设置」中配置您个人的专属 Google Gemini Key 启用多模态 OCR 识别,或直接复制文本发送。"); } resolve(textContent); } catch (err: any) { reject(err); } }; if ((window as any).pdfjsLib) { executeParsing(); } else { const script = document.createElement("script"); script.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"; script.onload = () => { executeParsing(); }; script.onerror = () => { reject(new Error("加载在线 PDF 解析组件失败,请检查您的网络。")); }; document.head.appendChild(script); } }); } /** * Parses DOCX files entirely in the client browser using mammoth.js via CDN. */ export async function parseDocxClient(file: File): Promise { return new Promise((resolve, reject) => { const executeParsing = async () => { try { const mammoth = (window as any).mammoth; if (!mammoth) { throw new Error("浏览器中未加载 mammoth 组件"); } const arrayBuffer = await file.arrayBuffer(); const result = await mammoth.extractRawText({ arrayBuffer }); if (result.value && result.value.trim().length > 0) { resolve(result.value); } else { throw new Error("Word 文档中未提取到有效文本"); } } catch (err: any) { reject(err); } }; if ((window as any).mammoth) { executeParsing(); } else { const script = document.createElement("script"); script.src = "https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"; script.onload = () => { executeParsing(); }; script.onerror = () => { reject(new Error("加载在线 Word 解析组件失败,请检查您的网络。")); }; document.head.appendChild(script); } }); }