@mariozechner/pi-web-ui 0.5.44
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/README.md +252 -0
- package/dist/ChatPanel.d.ts +23 -0
- package/dist/ChatPanel.d.ts.map +1 -0
- package/dist/ChatPanel.js +224 -0
- package/dist/ChatPanel.js.map +1 -0
- package/dist/app.css +2 -0
- package/dist/components/AgentInterface.d.ts +35 -0
- package/dist/components/AgentInterface.d.ts.map +1 -0
- package/dist/components/AgentInterface.js +308 -0
- package/dist/components/AgentInterface.js.map +1 -0
- package/dist/components/AttachmentTile.d.ts +12 -0
- package/dist/components/AttachmentTile.d.ts.map +1 -0
- package/dist/components/AttachmentTile.js +114 -0
- package/dist/components/AttachmentTile.js.map +1 -0
- package/dist/components/ConsoleBlock.d.ts +11 -0
- package/dist/components/ConsoleBlock.d.ts.map +1 -0
- package/dist/components/ConsoleBlock.js +77 -0
- package/dist/components/ConsoleBlock.js.map +1 -0
- package/dist/components/Input.d.ts +26 -0
- package/dist/components/Input.d.ts.map +1 -0
- package/dist/components/Input.js +56 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/MessageEditor.d.ts +38 -0
- package/dist/components/MessageEditor.d.ts.map +1 -0
- package/dist/components/MessageEditor.js +296 -0
- package/dist/components/MessageEditor.js.map +1 -0
- package/dist/components/MessageList.d.ts +13 -0
- package/dist/components/MessageList.d.ts.map +1 -0
- package/dist/components/MessageList.js +88 -0
- package/dist/components/MessageList.js.map +1 -0
- package/dist/components/Messages.d.ts +53 -0
- package/dist/components/Messages.d.ts.map +1 -0
- package/dist/components/Messages.js +323 -0
- package/dist/components/Messages.js.map +1 -0
- package/dist/components/ProviderKeyInput.d.ts +16 -0
- package/dist/components/ProviderKeyInput.d.ts.map +1 -0
- package/dist/components/ProviderKeyInput.js +183 -0
- package/dist/components/ProviderKeyInput.js.map +1 -0
- package/dist/components/SandboxedIframe.d.ts +63 -0
- package/dist/components/SandboxedIframe.d.ts.map +1 -0
- package/dist/components/SandboxedIframe.js +435 -0
- package/dist/components/SandboxedIframe.js.map +1 -0
- package/dist/components/StreamingMessageContainer.d.ts +17 -0
- package/dist/components/StreamingMessageContainer.d.ts.map +1 -0
- package/dist/components/StreamingMessageContainer.js +114 -0
- package/dist/components/StreamingMessageContainer.js.map +1 -0
- package/dist/dialogs/ApiKeyPromptDialog.d.ts +15 -0
- package/dist/dialogs/ApiKeyPromptDialog.d.ts.map +1 -0
- package/dist/dialogs/ApiKeyPromptDialog.js +80 -0
- package/dist/dialogs/ApiKeyPromptDialog.js.map +1 -0
- package/dist/dialogs/AttachmentOverlay.d.ts +32 -0
- package/dist/dialogs/AttachmentOverlay.d.ts.map +1 -0
- package/dist/dialogs/AttachmentOverlay.js +575 -0
- package/dist/dialogs/AttachmentOverlay.js.map +1 -0
- package/dist/dialogs/ModelSelector.d.ts +27 -0
- package/dist/dialogs/ModelSelector.d.ts.map +1 -0
- package/dist/dialogs/ModelSelector.js +334 -0
- package/dist/dialogs/ModelSelector.js.map +1 -0
- package/dist/dialogs/SettingsDialog.d.ts +31 -0
- package/dist/dialogs/SettingsDialog.d.ts.map +1 -0
- package/dist/dialogs/SettingsDialog.js +228 -0
- package/dist/dialogs/SettingsDialog.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/state/agent-session.d.ts +58 -0
- package/dist/state/agent-session.d.ts.map +1 -0
- package/dist/state/agent-session.js +252 -0
- package/dist/state/agent-session.js.map +1 -0
- package/dist/state/transports/AppTransport.d.ts +13 -0
- package/dist/state/transports/AppTransport.d.ts.map +1 -0
- package/dist/state/transports/AppTransport.js +316 -0
- package/dist/state/transports/AppTransport.js.map +1 -0
- package/dist/state/transports/ProviderTransport.d.ts +12 -0
- package/dist/state/transports/ProviderTransport.d.ts.map +1 -0
- package/dist/state/transports/ProviderTransport.js +44 -0
- package/dist/state/transports/ProviderTransport.js.map +1 -0
- package/dist/state/transports/index.d.ts +4 -0
- package/dist/state/transports/index.d.ts.map +1 -0
- package/dist/state/transports/index.js +4 -0
- package/dist/state/transports/index.js.map +1 -0
- package/dist/state/transports/proxy-types.d.ts +48 -0
- package/dist/state/transports/proxy-types.d.ts.map +1 -0
- package/dist/state/transports/proxy-types.js +2 -0
- package/dist/state/transports/proxy-types.js.map +1 -0
- package/dist/state/transports/types.d.ts +11 -0
- package/dist/state/transports/types.d.ts.map +1 -0
- package/dist/state/transports/types.js +2 -0
- package/dist/state/transports/types.js.map +1 -0
- package/dist/state/types.d.ts +15 -0
- package/dist/state/types.d.ts.map +1 -0
- package/dist/state/types.js +2 -0
- package/dist/state/types.js.map +1 -0
- package/dist/storage/app-storage.d.ts +26 -0
- package/dist/storage/app-storage.d.ts.map +1 -0
- package/dist/storage/app-storage.js +44 -0
- package/dist/storage/app-storage.js.map +1 -0
- package/dist/storage/backends/chrome-storage-backend.d.ts +18 -0
- package/dist/storage/backends/chrome-storage-backend.d.ts.map +1 -0
- package/dist/storage/backends/chrome-storage-backend.js +67 -0
- package/dist/storage/backends/chrome-storage-backend.js.map +1 -0
- package/dist/storage/backends/indexeddb-backend.d.ts +20 -0
- package/dist/storage/backends/indexeddb-backend.d.ts.map +1 -0
- package/dist/storage/backends/indexeddb-backend.js +89 -0
- package/dist/storage/backends/indexeddb-backend.js.map +1 -0
- package/dist/storage/backends/local-storage-backend.d.ts +18 -0
- package/dist/storage/backends/local-storage-backend.d.ts.map +1 -0
- package/dist/storage/backends/local-storage-backend.js +69 -0
- package/dist/storage/backends/local-storage-backend.js.map +1 -0
- package/dist/storage/repositories/provider-keys-repository.d.ts +34 -0
- package/dist/storage/repositories/provider-keys-repository.d.ts.map +1 -0
- package/dist/storage/repositories/provider-keys-repository.js +50 -0
- package/dist/storage/repositories/provider-keys-repository.js.map +1 -0
- package/dist/storage/repositories/settings-repository.d.ts +34 -0
- package/dist/storage/repositories/settings-repository.d.ts.map +1 -0
- package/dist/storage/repositories/settings-repository.js +46 -0
- package/dist/storage/repositories/settings-repository.js.map +1 -0
- package/dist/storage/types.d.ts +43 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/tools/artifacts/ArtifactElement.d.ts +10 -0
- package/dist/tools/artifacts/ArtifactElement.d.ts.map +1 -0
- package/dist/tools/artifacts/ArtifactElement.js +12 -0
- package/dist/tools/artifacts/ArtifactElement.js.map +1 -0
- package/dist/tools/artifacts/HtmlArtifact.d.ts +30 -0
- package/dist/tools/artifacts/HtmlArtifact.d.ts.map +1 -0
- package/dist/tools/artifacts/HtmlArtifact.js +217 -0
- package/dist/tools/artifacts/HtmlArtifact.js.map +1 -0
- package/dist/tools/artifacts/MarkdownArtifact.d.ts +20 -0
- package/dist/tools/artifacts/MarkdownArtifact.d.ts.map +1 -0
- package/dist/tools/artifacts/MarkdownArtifact.js +84 -0
- package/dist/tools/artifacts/MarkdownArtifact.js.map +1 -0
- package/dist/tools/artifacts/SvgArtifact.d.ts +19 -0
- package/dist/tools/artifacts/SvgArtifact.d.ts.map +1 -0
- package/dist/tools/artifacts/SvgArtifact.js +80 -0
- package/dist/tools/artifacts/SvgArtifact.js.map +1 -0
- package/dist/tools/artifacts/TextArtifact.d.ts +20 -0
- package/dist/tools/artifacts/TextArtifact.d.ts.map +1 -0
- package/dist/tools/artifacts/TextArtifact.js +147 -0
- package/dist/tools/artifacts/TextArtifact.js.map +1 -0
- package/dist/tools/artifacts/artifacts.d.ts +67 -0
- package/dist/tools/artifacts/artifacts.d.ts.map +1 -0
- package/dist/tools/artifacts/artifacts.js +836 -0
- package/dist/tools/artifacts/artifacts.js.map +1 -0
- package/dist/tools/artifacts/index.d.ts +7 -0
- package/dist/tools/artifacts/index.d.ts.map +1 -0
- package/dist/tools/artifacts/index.js +7 -0
- package/dist/tools/artifacts/index.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +29 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/javascript-repl.d.ts +43 -0
- package/dist/tools/javascript-repl.d.ts.map +1 -0
- package/dist/tools/javascript-repl.js +252 -0
- package/dist/tools/javascript-repl.js.map +1 -0
- package/dist/tools/renderer-registry.d.ts +11 -0
- package/dist/tools/renderer-registry.d.ts.map +1 -0
- package/dist/tools/renderer-registry.js +15 -0
- package/dist/tools/renderer-registry.js.map +1 -0
- package/dist/tools/renderers/BashRenderer.d.ts +12 -0
- package/dist/tools/renderers/BashRenderer.d.ts.map +1 -0
- package/dist/tools/renderers/BashRenderer.js +35 -0
- package/dist/tools/renderers/BashRenderer.js.map +1 -0
- package/dist/tools/renderers/CalculateRenderer.d.ts +12 -0
- package/dist/tools/renderers/CalculateRenderer.d.ts.map +1 -0
- package/dist/tools/renderers/CalculateRenderer.js +38 -0
- package/dist/tools/renderers/CalculateRenderer.js.map +1 -0
- package/dist/tools/renderers/DefaultRenderer.d.ts +8 -0
- package/dist/tools/renderers/DefaultRenderer.d.ts.map +1 -0
- package/dist/tools/renderers/DefaultRenderer.js +31 -0
- package/dist/tools/renderers/DefaultRenderer.js.map +1 -0
- package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts +12 -0
- package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts.map +1 -0
- package/dist/tools/renderers/GetCurrentTimeRenderer.js +30 -0
- package/dist/tools/renderers/GetCurrentTimeRenderer.js.map +1 -0
- package/dist/tools/types.d.ts +7 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/utils/attachment-utils.d.ts +19 -0
- package/dist/utils/attachment-utils.d.ts.map +1 -0
- package/dist/utils/attachment-utils.js +415 -0
- package/dist/utils/attachment-utils.js.map +1 -0
- package/dist/utils/auth-token.d.ts +3 -0
- package/dist/utils/auth-token.d.ts.map +1 -0
- package/dist/utils/auth-token.js +19 -0
- package/dist/utils/auth-token.js.map +1 -0
- package/dist/utils/format.d.ts +6 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +47 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/i18n.d.ts +111 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +224 -0
- package/dist/utils/i18n.js.map +1 -0
- package/dist/utils/test-sessions.d.ts +347 -0
- package/dist/utils/test-sessions.d.ts.map +1 -0
- package/dist/utils/test-sessions.js +2215 -0
- package/dist/utils/test-sessions.js.map +1 -0
- package/example/README.md +61 -0
- package/example/index.html +13 -0
- package/example/package-lock.json +1965 -0
- package/example/package.json +22 -0
- package/example/src/app.css +1 -0
- package/example/src/main.ts +57 -0
- package/example/src/test-sessions.ts +104 -0
- package/example/tsconfig.json +15 -0
- package/example/vite.config.ts +6 -0
- package/package.json +45 -0
- package/src/ChatPanel.ts +214 -0
- package/src/app.css +44 -0
- package/src/components/AgentInterface.ts +316 -0
- package/src/components/AttachmentTile.ts +112 -0
- package/src/components/ConsoleBlock.ts +67 -0
- package/src/components/Input.ts +112 -0
- package/src/components/MessageEditor.ts +272 -0
- package/src/components/MessageList.ts +82 -0
- package/src/components/Messages.ts +310 -0
- package/src/components/ProviderKeyInput.ts +170 -0
- package/src/components/SandboxedIframe.ts +525 -0
- package/src/components/StreamingMessageContainer.ts +101 -0
- package/src/dialogs/ApiKeyPromptDialog.ts +76 -0
- package/src/dialogs/AttachmentOverlay.ts +635 -0
- package/src/dialogs/ModelSelector.ts +324 -0
- package/src/dialogs/SettingsDialog.ts +223 -0
- package/src/index.ts +63 -0
- package/src/state/agent-session.ts +311 -0
- package/src/state/transports/AppTransport.ts +363 -0
- package/src/state/transports/ProviderTransport.ts +49 -0
- package/src/state/transports/index.ts +3 -0
- package/src/state/transports/proxy-types.ts +15 -0
- package/src/state/transports/types.ts +16 -0
- package/src/state/types.ts +11 -0
- package/src/storage/app-storage.ts +53 -0
- package/src/storage/backends/chrome-storage-backend.ts +82 -0
- package/src/storage/backends/indexeddb-backend.ts +107 -0
- package/src/storage/backends/local-storage-backend.ts +74 -0
- package/src/storage/repositories/provider-keys-repository.ts +55 -0
- package/src/storage/repositories/settings-repository.ts +51 -0
- package/src/storage/types.ts +48 -0
- package/src/tools/artifacts/ArtifactElement.ts +15 -0
- package/src/tools/artifacts/HtmlArtifact.ts +221 -0
- package/src/tools/artifacts/MarkdownArtifact.ts +81 -0
- package/src/tools/artifacts/SvgArtifact.ts +77 -0
- package/src/tools/artifacts/TextArtifact.ts +148 -0
- package/src/tools/artifacts/artifacts.ts +888 -0
- package/src/tools/artifacts/index.ts +6 -0
- package/src/tools/index.ts +35 -0
- package/src/tools/javascript-repl.ts +309 -0
- package/src/tools/renderer-registry.ts +18 -0
- package/src/tools/renderers/BashRenderer.ts +45 -0
- package/src/tools/renderers/CalculateRenderer.ts +49 -0
- package/src/tools/renderers/DefaultRenderer.ts +36 -0
- package/src/tools/renderers/GetCurrentTimeRenderer.ts +39 -0
- package/src/tools/types.ts +7 -0
- package/src/utils/attachment-utils.ts +472 -0
- package/src/utils/auth-token.ts +22 -0
- package/src/utils/format.ts +42 -0
- package/src/utils/i18n.ts +343 -0
- package/src/utils/test-sessions.ts +2247 -0
- package/tsconfig.build.json +20 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { parseAsync } from "docx-preview";
|
|
2
|
+
import JSZip from "jszip";
|
|
3
|
+
import type { PDFDocumentProxy } from "pdfjs-dist";
|
|
4
|
+
import * as pdfjsLib from "pdfjs-dist";
|
|
5
|
+
import * as XLSX from "xlsx";
|
|
6
|
+
import { i18n } from "./i18n.js";
|
|
7
|
+
|
|
8
|
+
// Configure PDF.js worker - we'll need to bundle this
|
|
9
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url).toString();
|
|
10
|
+
|
|
11
|
+
export interface Attachment {
|
|
12
|
+
id: string;
|
|
13
|
+
type: "image" | "document";
|
|
14
|
+
fileName: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
size: number;
|
|
17
|
+
content: string; // base64 encoded original data (without data URL prefix)
|
|
18
|
+
extractedText?: string; // For documents: <pdf filename="..."><page number="1">text</page></pdf>
|
|
19
|
+
preview?: string; // base64 image preview (first page for PDFs, or same as content for images)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load an attachment from various sources
|
|
24
|
+
* @param source - URL string, File, Blob, or ArrayBuffer
|
|
25
|
+
* @param fileName - Optional filename override
|
|
26
|
+
* @returns Promise<Attachment>
|
|
27
|
+
* @throws Error if loading fails
|
|
28
|
+
*/
|
|
29
|
+
export async function loadAttachment(
|
|
30
|
+
source: string | File | Blob | ArrayBuffer,
|
|
31
|
+
fileName?: string,
|
|
32
|
+
): Promise<Attachment> {
|
|
33
|
+
let arrayBuffer: ArrayBuffer;
|
|
34
|
+
let detectedFileName = fileName || "unnamed";
|
|
35
|
+
let mimeType = "application/octet-stream";
|
|
36
|
+
let size = 0;
|
|
37
|
+
|
|
38
|
+
// Convert source to ArrayBuffer
|
|
39
|
+
if (typeof source === "string") {
|
|
40
|
+
// It's a URL - fetch it
|
|
41
|
+
const response = await fetch(source);
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(i18n("Failed to fetch file"));
|
|
44
|
+
}
|
|
45
|
+
arrayBuffer = await response.arrayBuffer();
|
|
46
|
+
size = arrayBuffer.byteLength;
|
|
47
|
+
mimeType = response.headers.get("content-type") || mimeType;
|
|
48
|
+
if (!fileName) {
|
|
49
|
+
// Try to extract filename from URL
|
|
50
|
+
const urlParts = source.split("/");
|
|
51
|
+
detectedFileName = urlParts[urlParts.length - 1] || "document";
|
|
52
|
+
}
|
|
53
|
+
} else if (source instanceof File) {
|
|
54
|
+
arrayBuffer = await source.arrayBuffer();
|
|
55
|
+
size = source.size;
|
|
56
|
+
mimeType = source.type || mimeType;
|
|
57
|
+
detectedFileName = fileName || source.name;
|
|
58
|
+
} else if (source instanceof Blob) {
|
|
59
|
+
arrayBuffer = await source.arrayBuffer();
|
|
60
|
+
size = source.size;
|
|
61
|
+
mimeType = source.type || mimeType;
|
|
62
|
+
} else if (source instanceof ArrayBuffer) {
|
|
63
|
+
arrayBuffer = source;
|
|
64
|
+
size = source.byteLength;
|
|
65
|
+
} else {
|
|
66
|
+
throw new Error(i18n("Invalid source type"));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Convert ArrayBuffer to base64 - handle large files properly
|
|
70
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
71
|
+
let binary = "";
|
|
72
|
+
const chunkSize = 0x8000; // Process in 32KB chunks to avoid stack overflow
|
|
73
|
+
for (let i = 0; i < uint8Array.length; i += chunkSize) {
|
|
74
|
+
const chunk = uint8Array.slice(i, i + chunkSize);
|
|
75
|
+
binary += String.fromCharCode(...chunk);
|
|
76
|
+
}
|
|
77
|
+
const base64Content = btoa(binary);
|
|
78
|
+
|
|
79
|
+
// Detect type and process accordingly
|
|
80
|
+
const id = `${detectedFileName}_${Date.now()}_${Math.random()}`;
|
|
81
|
+
|
|
82
|
+
// Check if it's a PDF
|
|
83
|
+
if (mimeType === "application/pdf" || detectedFileName.toLowerCase().endsWith(".pdf")) {
|
|
84
|
+
const { extractedText, preview } = await processPdf(arrayBuffer, detectedFileName);
|
|
85
|
+
return {
|
|
86
|
+
id,
|
|
87
|
+
type: "document",
|
|
88
|
+
fileName: detectedFileName,
|
|
89
|
+
mimeType: "application/pdf",
|
|
90
|
+
size,
|
|
91
|
+
content: base64Content,
|
|
92
|
+
extractedText,
|
|
93
|
+
preview,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check if it's a DOCX file
|
|
98
|
+
if (
|
|
99
|
+
mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
|
|
100
|
+
detectedFileName.toLowerCase().endsWith(".docx")
|
|
101
|
+
) {
|
|
102
|
+
const { extractedText } = await processDocx(arrayBuffer, detectedFileName);
|
|
103
|
+
return {
|
|
104
|
+
id,
|
|
105
|
+
type: "document",
|
|
106
|
+
fileName: detectedFileName,
|
|
107
|
+
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
108
|
+
size,
|
|
109
|
+
content: base64Content,
|
|
110
|
+
extractedText,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if it's a PPTX file
|
|
115
|
+
if (
|
|
116
|
+
mimeType === "application/vnd.openxmlformats-officedocument.presentationml.presentation" ||
|
|
117
|
+
detectedFileName.toLowerCase().endsWith(".pptx")
|
|
118
|
+
) {
|
|
119
|
+
const { extractedText } = await processPptx(arrayBuffer, detectedFileName);
|
|
120
|
+
return {
|
|
121
|
+
id,
|
|
122
|
+
type: "document",
|
|
123
|
+
fileName: detectedFileName,
|
|
124
|
+
mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
125
|
+
size,
|
|
126
|
+
content: base64Content,
|
|
127
|
+
extractedText,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if it's an Excel file (XLSX/XLS)
|
|
132
|
+
const excelMimeTypes = [
|
|
133
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
134
|
+
"application/vnd.ms-excel",
|
|
135
|
+
];
|
|
136
|
+
if (
|
|
137
|
+
excelMimeTypes.includes(mimeType) ||
|
|
138
|
+
detectedFileName.toLowerCase().endsWith(".xlsx") ||
|
|
139
|
+
detectedFileName.toLowerCase().endsWith(".xls")
|
|
140
|
+
) {
|
|
141
|
+
const { extractedText } = await processExcel(arrayBuffer, detectedFileName);
|
|
142
|
+
return {
|
|
143
|
+
id,
|
|
144
|
+
type: "document",
|
|
145
|
+
fileName: detectedFileName,
|
|
146
|
+
mimeType: mimeType.startsWith("application/vnd")
|
|
147
|
+
? mimeType
|
|
148
|
+
: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
149
|
+
size,
|
|
150
|
+
content: base64Content,
|
|
151
|
+
extractedText,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check if it's an image
|
|
156
|
+
if (mimeType.startsWith("image/")) {
|
|
157
|
+
return {
|
|
158
|
+
id,
|
|
159
|
+
type: "image",
|
|
160
|
+
fileName: detectedFileName,
|
|
161
|
+
mimeType,
|
|
162
|
+
size,
|
|
163
|
+
content: base64Content,
|
|
164
|
+
preview: base64Content, // For images, preview is the same as content
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check if it's a text document
|
|
169
|
+
const textExtensions = [
|
|
170
|
+
".txt",
|
|
171
|
+
".md",
|
|
172
|
+
".json",
|
|
173
|
+
".xml",
|
|
174
|
+
".html",
|
|
175
|
+
".css",
|
|
176
|
+
".js",
|
|
177
|
+
".ts",
|
|
178
|
+
".jsx",
|
|
179
|
+
".tsx",
|
|
180
|
+
".yml",
|
|
181
|
+
".yaml",
|
|
182
|
+
];
|
|
183
|
+
const isTextFile =
|
|
184
|
+
mimeType.startsWith("text/") || textExtensions.some((ext) => detectedFileName.toLowerCase().endsWith(ext));
|
|
185
|
+
|
|
186
|
+
if (isTextFile) {
|
|
187
|
+
const decoder = new TextDecoder();
|
|
188
|
+
const text = decoder.decode(arrayBuffer);
|
|
189
|
+
return {
|
|
190
|
+
id,
|
|
191
|
+
type: "document",
|
|
192
|
+
fileName: detectedFileName,
|
|
193
|
+
mimeType: mimeType.startsWith("text/") ? mimeType : "text/plain",
|
|
194
|
+
size,
|
|
195
|
+
content: base64Content,
|
|
196
|
+
extractedText: text,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
throw new Error(`Unsupported file type: ${mimeType}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function processPdf(
|
|
204
|
+
arrayBuffer: ArrayBuffer,
|
|
205
|
+
fileName: string,
|
|
206
|
+
): Promise<{ extractedText: string; preview?: string }> {
|
|
207
|
+
let pdf: PDFDocumentProxy | null = null;
|
|
208
|
+
try {
|
|
209
|
+
pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
|
210
|
+
|
|
211
|
+
// Extract text with page structure
|
|
212
|
+
let extractedText = `<pdf filename="${fileName}">`;
|
|
213
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
214
|
+
const page = await pdf.getPage(i);
|
|
215
|
+
const textContent = await page.getTextContent();
|
|
216
|
+
const pageText = textContent.items
|
|
217
|
+
.map((item: any) => item.str)
|
|
218
|
+
.filter((str: string) => str.trim())
|
|
219
|
+
.join(" ");
|
|
220
|
+
extractedText += `\n<page number="${i}">\n${pageText}\n</page>`;
|
|
221
|
+
}
|
|
222
|
+
extractedText += "\n</pdf>";
|
|
223
|
+
|
|
224
|
+
// Generate preview from first page
|
|
225
|
+
const preview = await generatePdfPreview(pdf);
|
|
226
|
+
|
|
227
|
+
return { extractedText, preview };
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error("Error processing PDF:", error);
|
|
230
|
+
throw new Error(`Failed to process PDF: ${String(error)}`);
|
|
231
|
+
} finally {
|
|
232
|
+
// Clean up PDF resources
|
|
233
|
+
if (pdf) {
|
|
234
|
+
pdf.destroy();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function generatePdfPreview(pdf: PDFDocumentProxy): Promise<string | undefined> {
|
|
240
|
+
try {
|
|
241
|
+
const page = await pdf.getPage(1);
|
|
242
|
+
const viewport = page.getViewport({ scale: 1.0 });
|
|
243
|
+
|
|
244
|
+
// Create canvas with reasonable size for thumbnail (160x160 max)
|
|
245
|
+
const scale = Math.min(160 / viewport.width, 160 / viewport.height);
|
|
246
|
+
const scaledViewport = page.getViewport({ scale });
|
|
247
|
+
|
|
248
|
+
const canvas = document.createElement("canvas");
|
|
249
|
+
const context = canvas.getContext("2d");
|
|
250
|
+
if (!context) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
canvas.height = scaledViewport.height;
|
|
255
|
+
canvas.width = scaledViewport.width;
|
|
256
|
+
|
|
257
|
+
const renderContext = {
|
|
258
|
+
canvasContext: context,
|
|
259
|
+
viewport: scaledViewport,
|
|
260
|
+
canvas: canvas,
|
|
261
|
+
};
|
|
262
|
+
await page.render(renderContext).promise;
|
|
263
|
+
|
|
264
|
+
// Return base64 without data URL prefix
|
|
265
|
+
return canvas.toDataURL("image/png").split(",")[1];
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.error("Error generating PDF preview:", error);
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function processDocx(arrayBuffer: ArrayBuffer, fileName: string): Promise<{ extractedText: string }> {
|
|
273
|
+
try {
|
|
274
|
+
// Parse document structure
|
|
275
|
+
const wordDoc = await parseAsync(arrayBuffer);
|
|
276
|
+
|
|
277
|
+
// Extract structured text from document body
|
|
278
|
+
let extractedText = `<docx filename="${fileName}">\n<page number="1">\n`;
|
|
279
|
+
|
|
280
|
+
const body = wordDoc.documentPart?.body;
|
|
281
|
+
if (body?.children) {
|
|
282
|
+
// Walk through document elements and extract text
|
|
283
|
+
const texts: string[] = [];
|
|
284
|
+
for (const element of body.children) {
|
|
285
|
+
const text = extractTextFromElement(element);
|
|
286
|
+
if (text) {
|
|
287
|
+
texts.push(text);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
extractedText += texts.join("\n");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
extractedText += `\n</page>\n</docx>`;
|
|
294
|
+
return { extractedText };
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error("Error processing DOCX:", error);
|
|
297
|
+
throw new Error(`Failed to process DOCX: ${String(error)}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function extractTextFromElement(element: any): string {
|
|
302
|
+
let text = "";
|
|
303
|
+
|
|
304
|
+
// Check type with lowercase
|
|
305
|
+
const elementType = element.type?.toLowerCase() || "";
|
|
306
|
+
|
|
307
|
+
// Handle paragraphs
|
|
308
|
+
if (elementType === "paragraph" && element.children) {
|
|
309
|
+
for (const child of element.children) {
|
|
310
|
+
const childType = child.type?.toLowerCase() || "";
|
|
311
|
+
if (childType === "run" && child.children) {
|
|
312
|
+
for (const textChild of child.children) {
|
|
313
|
+
const textType = textChild.type?.toLowerCase() || "";
|
|
314
|
+
if (textType === "text") {
|
|
315
|
+
text += textChild.text || "";
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} else if (childType === "text") {
|
|
319
|
+
text += child.text || "";
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Handle tables
|
|
324
|
+
else if (elementType === "table") {
|
|
325
|
+
if (element.children) {
|
|
326
|
+
const tableTexts: string[] = [];
|
|
327
|
+
for (const row of element.children) {
|
|
328
|
+
const rowType = row.type?.toLowerCase() || "";
|
|
329
|
+
if (rowType === "tablerow" && row.children) {
|
|
330
|
+
const rowTexts: string[] = [];
|
|
331
|
+
for (const cell of row.children) {
|
|
332
|
+
const cellType = cell.type?.toLowerCase() || "";
|
|
333
|
+
if (cellType === "tablecell" && cell.children) {
|
|
334
|
+
const cellTexts: string[] = [];
|
|
335
|
+
for (const cellElement of cell.children) {
|
|
336
|
+
const cellText = extractTextFromElement(cellElement);
|
|
337
|
+
if (cellText) cellTexts.push(cellText);
|
|
338
|
+
}
|
|
339
|
+
if (cellTexts.length > 0) rowTexts.push(cellTexts.join(" "));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (rowTexts.length > 0) tableTexts.push(rowTexts.join(" | "));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (tableTexts.length > 0) {
|
|
346
|
+
text = "\n[Table]\n" + tableTexts.join("\n") + "\n[/Table]\n";
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Recursively handle other container elements
|
|
351
|
+
else if (element.children && Array.isArray(element.children)) {
|
|
352
|
+
const childTexts: string[] = [];
|
|
353
|
+
for (const child of element.children) {
|
|
354
|
+
const childText = extractTextFromElement(child);
|
|
355
|
+
if (childText) childTexts.push(childText);
|
|
356
|
+
}
|
|
357
|
+
text = childTexts.join(" ");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return text.trim();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function processPptx(arrayBuffer: ArrayBuffer, fileName: string): Promise<{ extractedText: string }> {
|
|
364
|
+
try {
|
|
365
|
+
// Load the PPTX file as a ZIP
|
|
366
|
+
const zip = await JSZip.loadAsync(arrayBuffer);
|
|
367
|
+
|
|
368
|
+
// PPTX slides are stored in ppt/slides/slide[n].xml
|
|
369
|
+
let extractedText = `<pptx filename="${fileName}">`;
|
|
370
|
+
|
|
371
|
+
// Get all slide files and sort them numerically
|
|
372
|
+
const slideFiles = Object.keys(zip.files)
|
|
373
|
+
.filter((name) => name.match(/ppt\/slides\/slide\d+\.xml$/))
|
|
374
|
+
.sort((a, b) => {
|
|
375
|
+
const numA = Number.parseInt(a.match(/slide(\d+)\.xml$/)?.[1] || "0", 10);
|
|
376
|
+
const numB = Number.parseInt(b.match(/slide(\d+)\.xml$/)?.[1] || "0", 10);
|
|
377
|
+
return numA - numB;
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Extract text from each slide
|
|
381
|
+
for (let i = 0; i < slideFiles.length; i++) {
|
|
382
|
+
const slideFile = zip.file(slideFiles[i]);
|
|
383
|
+
if (slideFile) {
|
|
384
|
+
const slideXml = await slideFile.async("text");
|
|
385
|
+
|
|
386
|
+
// Extract text from XML (simple regex approach)
|
|
387
|
+
// Looking for <a:t> tags which contain text in PPTX
|
|
388
|
+
const textMatches = slideXml.match(/<a:t[^>]*>([^<]+)<\/a:t>/g);
|
|
389
|
+
|
|
390
|
+
if (textMatches) {
|
|
391
|
+
extractedText += `\n<slide number="${i + 1}">`;
|
|
392
|
+
const slideTexts = textMatches
|
|
393
|
+
.map((match) => {
|
|
394
|
+
const textMatch = match.match(/<a:t[^>]*>([^<]+)<\/a:t>/);
|
|
395
|
+
return textMatch ? textMatch[1] : "";
|
|
396
|
+
})
|
|
397
|
+
.filter((t) => t.trim());
|
|
398
|
+
|
|
399
|
+
if (slideTexts.length > 0) {
|
|
400
|
+
extractedText += "\n" + slideTexts.join("\n");
|
|
401
|
+
}
|
|
402
|
+
extractedText += "\n</slide>";
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Also try to extract text from notes
|
|
408
|
+
const notesFiles = Object.keys(zip.files)
|
|
409
|
+
.filter((name) => name.match(/ppt\/notesSlides\/notesSlide\d+\.xml$/))
|
|
410
|
+
.sort((a, b) => {
|
|
411
|
+
const numA = Number.parseInt(a.match(/notesSlide(\d+)\.xml$/)?.[1] || "0", 10);
|
|
412
|
+
const numB = Number.parseInt(b.match(/notesSlide(\d+)\.xml$/)?.[1] || "0", 10);
|
|
413
|
+
return numA - numB;
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
if (notesFiles.length > 0) {
|
|
417
|
+
extractedText += "\n<notes>";
|
|
418
|
+
for (const noteFile of notesFiles) {
|
|
419
|
+
const file = zip.file(noteFile);
|
|
420
|
+
if (file) {
|
|
421
|
+
const noteXml = await file.async("text");
|
|
422
|
+
const textMatches = noteXml.match(/<a:t[^>]*>([^<]+)<\/a:t>/g);
|
|
423
|
+
if (textMatches) {
|
|
424
|
+
const noteTexts = textMatches
|
|
425
|
+
.map((match) => {
|
|
426
|
+
const textMatch = match.match(/<a:t[^>]*>([^<]+)<\/a:t>/);
|
|
427
|
+
return textMatch ? textMatch[1] : "";
|
|
428
|
+
})
|
|
429
|
+
.filter((t) => t.trim());
|
|
430
|
+
|
|
431
|
+
if (noteTexts.length > 0) {
|
|
432
|
+
const slideNum = noteFile.match(/notesSlide(\d+)\.xml$/)?.[1];
|
|
433
|
+
extractedText += `\n[Slide ${slideNum} notes]: ${noteTexts.join(" ")}`;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
extractedText += "\n</notes>";
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
extractedText += "\n</pptx>";
|
|
442
|
+
return { extractedText };
|
|
443
|
+
} catch (error) {
|
|
444
|
+
console.error("Error processing PPTX:", error);
|
|
445
|
+
throw new Error(`Failed to process PPTX: ${String(error)}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function processExcel(arrayBuffer: ArrayBuffer, fileName: string): Promise<{ extractedText: string }> {
|
|
450
|
+
try {
|
|
451
|
+
// Read the workbook
|
|
452
|
+
const workbook = XLSX.read(arrayBuffer, { type: "array" });
|
|
453
|
+
|
|
454
|
+
let extractedText = `<excel filename="${fileName}">`;
|
|
455
|
+
|
|
456
|
+
// Process each sheet
|
|
457
|
+
for (const [index, sheetName] of workbook.SheetNames.entries()) {
|
|
458
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
459
|
+
|
|
460
|
+
// Extract text as CSV for the extractedText field
|
|
461
|
+
const csvText = XLSX.utils.sheet_to_csv(worksheet);
|
|
462
|
+
extractedText += `\n<sheet name="${sheetName}" index="${index + 1}">\n${csvText}\n</sheet>`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
extractedText += "\n</excel>";
|
|
466
|
+
|
|
467
|
+
return { extractedText };
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error("Error processing Excel:", error);
|
|
470
|
+
throw new Error(`Failed to process Excel: ${String(error)}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PromptDialog } from "@mariozechner/mini-lit";
|
|
2
|
+
import { i18n } from "./i18n.js";
|
|
3
|
+
|
|
4
|
+
export async function getAuthToken(): Promise<string | undefined> {
|
|
5
|
+
let authToken: string | undefined = localStorage.getItem(`auth-token`) || "";
|
|
6
|
+
if (authToken) return authToken;
|
|
7
|
+
|
|
8
|
+
while (true) {
|
|
9
|
+
authToken = (
|
|
10
|
+
await PromptDialog.ask(i18n("Enter Auth Token"), i18n("Please enter your auth token."), "", true)
|
|
11
|
+
)?.trim();
|
|
12
|
+
if (authToken) {
|
|
13
|
+
localStorage.setItem(`auth-token`, authToken);
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return authToken?.trim() || undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function clearAuthToken() {
|
|
21
|
+
localStorage.removeItem(`auth-token`);
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { i18n } from "@mariozechner/mini-lit";
|
|
2
|
+
import type { Usage } from "@mariozechner/pi-ai";
|
|
3
|
+
|
|
4
|
+
export function formatCost(cost: number): string {
|
|
5
|
+
return `$${cost.toFixed(4)}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatModelCost(cost: any): string {
|
|
9
|
+
if (!cost) return i18n("Free");
|
|
10
|
+
const input = cost.input || 0;
|
|
11
|
+
const output = cost.output || 0;
|
|
12
|
+
if (input === 0 && output === 0) return i18n("Free");
|
|
13
|
+
|
|
14
|
+
// Format numbers with appropriate precision
|
|
15
|
+
const formatNum = (num: number): string => {
|
|
16
|
+
if (num >= 100) return num.toFixed(0);
|
|
17
|
+
if (num >= 10) return num.toFixed(1).replace(/\.0$/, "");
|
|
18
|
+
if (num >= 1) return num.toFixed(2).replace(/\.?0+$/, "");
|
|
19
|
+
return num.toFixed(3).replace(/\.?0+$/, "");
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return `$${formatNum(input)}/$${formatNum(output)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function formatUsage(usage: Usage) {
|
|
26
|
+
if (!usage) return "";
|
|
27
|
+
|
|
28
|
+
const parts = [];
|
|
29
|
+
if (usage.input) parts.push(`↑${formatTokenCount(usage.input)}`);
|
|
30
|
+
if (usage.output) parts.push(`↓${formatTokenCount(usage.output)}`);
|
|
31
|
+
if (usage.cacheRead) parts.push(`R${formatTokenCount(usage.cacheRead)}`);
|
|
32
|
+
if (usage.cacheWrite) parts.push(`W${formatTokenCount(usage.cacheWrite)}`);
|
|
33
|
+
if (usage.cost?.total) parts.push(formatCost(usage.cost.total));
|
|
34
|
+
|
|
35
|
+
return parts.join(" ");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function formatTokenCount(count: number): string {
|
|
39
|
+
if (count < 1000) return count.toString();
|
|
40
|
+
if (count < 10000) return (count / 1000).toFixed(1) + "k";
|
|
41
|
+
return Math.round(count / 1000) + "k";
|
|
42
|
+
}
|