@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.
Files changed (265) hide show
  1. package/README.md +252 -0
  2. package/dist/ChatPanel.d.ts +23 -0
  3. package/dist/ChatPanel.d.ts.map +1 -0
  4. package/dist/ChatPanel.js +224 -0
  5. package/dist/ChatPanel.js.map +1 -0
  6. package/dist/app.css +2 -0
  7. package/dist/components/AgentInterface.d.ts +35 -0
  8. package/dist/components/AgentInterface.d.ts.map +1 -0
  9. package/dist/components/AgentInterface.js +308 -0
  10. package/dist/components/AgentInterface.js.map +1 -0
  11. package/dist/components/AttachmentTile.d.ts +12 -0
  12. package/dist/components/AttachmentTile.d.ts.map +1 -0
  13. package/dist/components/AttachmentTile.js +114 -0
  14. package/dist/components/AttachmentTile.js.map +1 -0
  15. package/dist/components/ConsoleBlock.d.ts +11 -0
  16. package/dist/components/ConsoleBlock.d.ts.map +1 -0
  17. package/dist/components/ConsoleBlock.js +77 -0
  18. package/dist/components/ConsoleBlock.js.map +1 -0
  19. package/dist/components/Input.d.ts +26 -0
  20. package/dist/components/Input.d.ts.map +1 -0
  21. package/dist/components/Input.js +56 -0
  22. package/dist/components/Input.js.map +1 -0
  23. package/dist/components/MessageEditor.d.ts +38 -0
  24. package/dist/components/MessageEditor.d.ts.map +1 -0
  25. package/dist/components/MessageEditor.js +296 -0
  26. package/dist/components/MessageEditor.js.map +1 -0
  27. package/dist/components/MessageList.d.ts +13 -0
  28. package/dist/components/MessageList.d.ts.map +1 -0
  29. package/dist/components/MessageList.js +88 -0
  30. package/dist/components/MessageList.js.map +1 -0
  31. package/dist/components/Messages.d.ts +53 -0
  32. package/dist/components/Messages.d.ts.map +1 -0
  33. package/dist/components/Messages.js +323 -0
  34. package/dist/components/Messages.js.map +1 -0
  35. package/dist/components/ProviderKeyInput.d.ts +16 -0
  36. package/dist/components/ProviderKeyInput.d.ts.map +1 -0
  37. package/dist/components/ProviderKeyInput.js +183 -0
  38. package/dist/components/ProviderKeyInput.js.map +1 -0
  39. package/dist/components/SandboxedIframe.d.ts +63 -0
  40. package/dist/components/SandboxedIframe.d.ts.map +1 -0
  41. package/dist/components/SandboxedIframe.js +435 -0
  42. package/dist/components/SandboxedIframe.js.map +1 -0
  43. package/dist/components/StreamingMessageContainer.d.ts +17 -0
  44. package/dist/components/StreamingMessageContainer.d.ts.map +1 -0
  45. package/dist/components/StreamingMessageContainer.js +114 -0
  46. package/dist/components/StreamingMessageContainer.js.map +1 -0
  47. package/dist/dialogs/ApiKeyPromptDialog.d.ts +15 -0
  48. package/dist/dialogs/ApiKeyPromptDialog.d.ts.map +1 -0
  49. package/dist/dialogs/ApiKeyPromptDialog.js +80 -0
  50. package/dist/dialogs/ApiKeyPromptDialog.js.map +1 -0
  51. package/dist/dialogs/AttachmentOverlay.d.ts +32 -0
  52. package/dist/dialogs/AttachmentOverlay.d.ts.map +1 -0
  53. package/dist/dialogs/AttachmentOverlay.js +575 -0
  54. package/dist/dialogs/AttachmentOverlay.js.map +1 -0
  55. package/dist/dialogs/ModelSelector.d.ts +27 -0
  56. package/dist/dialogs/ModelSelector.d.ts.map +1 -0
  57. package/dist/dialogs/ModelSelector.js +334 -0
  58. package/dist/dialogs/ModelSelector.js.map +1 -0
  59. package/dist/dialogs/SettingsDialog.d.ts +31 -0
  60. package/dist/dialogs/SettingsDialog.d.ts.map +1 -0
  61. package/dist/dialogs/SettingsDialog.js +228 -0
  62. package/dist/dialogs/SettingsDialog.js.map +1 -0
  63. package/dist/index.d.ts +46 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +51 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/state/agent-session.d.ts +58 -0
  68. package/dist/state/agent-session.d.ts.map +1 -0
  69. package/dist/state/agent-session.js +252 -0
  70. package/dist/state/agent-session.js.map +1 -0
  71. package/dist/state/transports/AppTransport.d.ts +13 -0
  72. package/dist/state/transports/AppTransport.d.ts.map +1 -0
  73. package/dist/state/transports/AppTransport.js +316 -0
  74. package/dist/state/transports/AppTransport.js.map +1 -0
  75. package/dist/state/transports/ProviderTransport.d.ts +12 -0
  76. package/dist/state/transports/ProviderTransport.d.ts.map +1 -0
  77. package/dist/state/transports/ProviderTransport.js +44 -0
  78. package/dist/state/transports/ProviderTransport.js.map +1 -0
  79. package/dist/state/transports/index.d.ts +4 -0
  80. package/dist/state/transports/index.d.ts.map +1 -0
  81. package/dist/state/transports/index.js +4 -0
  82. package/dist/state/transports/index.js.map +1 -0
  83. package/dist/state/transports/proxy-types.d.ts +48 -0
  84. package/dist/state/transports/proxy-types.d.ts.map +1 -0
  85. package/dist/state/transports/proxy-types.js +2 -0
  86. package/dist/state/transports/proxy-types.js.map +1 -0
  87. package/dist/state/transports/types.d.ts +11 -0
  88. package/dist/state/transports/types.d.ts.map +1 -0
  89. package/dist/state/transports/types.js +2 -0
  90. package/dist/state/transports/types.js.map +1 -0
  91. package/dist/state/types.d.ts +15 -0
  92. package/dist/state/types.d.ts.map +1 -0
  93. package/dist/state/types.js +2 -0
  94. package/dist/state/types.js.map +1 -0
  95. package/dist/storage/app-storage.d.ts +26 -0
  96. package/dist/storage/app-storage.d.ts.map +1 -0
  97. package/dist/storage/app-storage.js +44 -0
  98. package/dist/storage/app-storage.js.map +1 -0
  99. package/dist/storage/backends/chrome-storage-backend.d.ts +18 -0
  100. package/dist/storage/backends/chrome-storage-backend.d.ts.map +1 -0
  101. package/dist/storage/backends/chrome-storage-backend.js +67 -0
  102. package/dist/storage/backends/chrome-storage-backend.js.map +1 -0
  103. package/dist/storage/backends/indexeddb-backend.d.ts +20 -0
  104. package/dist/storage/backends/indexeddb-backend.d.ts.map +1 -0
  105. package/dist/storage/backends/indexeddb-backend.js +89 -0
  106. package/dist/storage/backends/indexeddb-backend.js.map +1 -0
  107. package/dist/storage/backends/local-storage-backend.d.ts +18 -0
  108. package/dist/storage/backends/local-storage-backend.d.ts.map +1 -0
  109. package/dist/storage/backends/local-storage-backend.js +69 -0
  110. package/dist/storage/backends/local-storage-backend.js.map +1 -0
  111. package/dist/storage/repositories/provider-keys-repository.d.ts +34 -0
  112. package/dist/storage/repositories/provider-keys-repository.d.ts.map +1 -0
  113. package/dist/storage/repositories/provider-keys-repository.js +50 -0
  114. package/dist/storage/repositories/provider-keys-repository.js.map +1 -0
  115. package/dist/storage/repositories/settings-repository.d.ts +34 -0
  116. package/dist/storage/repositories/settings-repository.d.ts.map +1 -0
  117. package/dist/storage/repositories/settings-repository.js +46 -0
  118. package/dist/storage/repositories/settings-repository.js.map +1 -0
  119. package/dist/storage/types.d.ts +43 -0
  120. package/dist/storage/types.d.ts.map +1 -0
  121. package/dist/storage/types.js +2 -0
  122. package/dist/storage/types.js.map +1 -0
  123. package/dist/tools/artifacts/ArtifactElement.d.ts +10 -0
  124. package/dist/tools/artifacts/ArtifactElement.d.ts.map +1 -0
  125. package/dist/tools/artifacts/ArtifactElement.js +12 -0
  126. package/dist/tools/artifacts/ArtifactElement.js.map +1 -0
  127. package/dist/tools/artifacts/HtmlArtifact.d.ts +30 -0
  128. package/dist/tools/artifacts/HtmlArtifact.d.ts.map +1 -0
  129. package/dist/tools/artifacts/HtmlArtifact.js +217 -0
  130. package/dist/tools/artifacts/HtmlArtifact.js.map +1 -0
  131. package/dist/tools/artifacts/MarkdownArtifact.d.ts +20 -0
  132. package/dist/tools/artifacts/MarkdownArtifact.d.ts.map +1 -0
  133. package/dist/tools/artifacts/MarkdownArtifact.js +84 -0
  134. package/dist/tools/artifacts/MarkdownArtifact.js.map +1 -0
  135. package/dist/tools/artifacts/SvgArtifact.d.ts +19 -0
  136. package/dist/tools/artifacts/SvgArtifact.d.ts.map +1 -0
  137. package/dist/tools/artifacts/SvgArtifact.js +80 -0
  138. package/dist/tools/artifacts/SvgArtifact.js.map +1 -0
  139. package/dist/tools/artifacts/TextArtifact.d.ts +20 -0
  140. package/dist/tools/artifacts/TextArtifact.d.ts.map +1 -0
  141. package/dist/tools/artifacts/TextArtifact.js +147 -0
  142. package/dist/tools/artifacts/TextArtifact.js.map +1 -0
  143. package/dist/tools/artifacts/artifacts.d.ts +67 -0
  144. package/dist/tools/artifacts/artifacts.d.ts.map +1 -0
  145. package/dist/tools/artifacts/artifacts.js +836 -0
  146. package/dist/tools/artifacts/artifacts.js.map +1 -0
  147. package/dist/tools/artifacts/index.d.ts +7 -0
  148. package/dist/tools/artifacts/index.d.ts.map +1 -0
  149. package/dist/tools/artifacts/index.js +7 -0
  150. package/dist/tools/artifacts/index.js.map +1 -0
  151. package/dist/tools/index.d.ts +14 -0
  152. package/dist/tools/index.d.ts.map +1 -0
  153. package/dist/tools/index.js +29 -0
  154. package/dist/tools/index.js.map +1 -0
  155. package/dist/tools/javascript-repl.d.ts +43 -0
  156. package/dist/tools/javascript-repl.d.ts.map +1 -0
  157. package/dist/tools/javascript-repl.js +252 -0
  158. package/dist/tools/javascript-repl.js.map +1 -0
  159. package/dist/tools/renderer-registry.d.ts +11 -0
  160. package/dist/tools/renderer-registry.d.ts.map +1 -0
  161. package/dist/tools/renderer-registry.js +15 -0
  162. package/dist/tools/renderer-registry.js.map +1 -0
  163. package/dist/tools/renderers/BashRenderer.d.ts +12 -0
  164. package/dist/tools/renderers/BashRenderer.d.ts.map +1 -0
  165. package/dist/tools/renderers/BashRenderer.js +35 -0
  166. package/dist/tools/renderers/BashRenderer.js.map +1 -0
  167. package/dist/tools/renderers/CalculateRenderer.d.ts +12 -0
  168. package/dist/tools/renderers/CalculateRenderer.d.ts.map +1 -0
  169. package/dist/tools/renderers/CalculateRenderer.js +38 -0
  170. package/dist/tools/renderers/CalculateRenderer.js.map +1 -0
  171. package/dist/tools/renderers/DefaultRenderer.d.ts +8 -0
  172. package/dist/tools/renderers/DefaultRenderer.d.ts.map +1 -0
  173. package/dist/tools/renderers/DefaultRenderer.js +31 -0
  174. package/dist/tools/renderers/DefaultRenderer.js.map +1 -0
  175. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts +12 -0
  176. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts.map +1 -0
  177. package/dist/tools/renderers/GetCurrentTimeRenderer.js +30 -0
  178. package/dist/tools/renderers/GetCurrentTimeRenderer.js.map +1 -0
  179. package/dist/tools/types.d.ts +7 -0
  180. package/dist/tools/types.d.ts.map +1 -0
  181. package/dist/tools/types.js +2 -0
  182. package/dist/tools/types.js.map +1 -0
  183. package/dist/utils/attachment-utils.d.ts +19 -0
  184. package/dist/utils/attachment-utils.d.ts.map +1 -0
  185. package/dist/utils/attachment-utils.js +415 -0
  186. package/dist/utils/attachment-utils.js.map +1 -0
  187. package/dist/utils/auth-token.d.ts +3 -0
  188. package/dist/utils/auth-token.d.ts.map +1 -0
  189. package/dist/utils/auth-token.js +19 -0
  190. package/dist/utils/auth-token.js.map +1 -0
  191. package/dist/utils/format.d.ts +6 -0
  192. package/dist/utils/format.d.ts.map +1 -0
  193. package/dist/utils/format.js +47 -0
  194. package/dist/utils/format.js.map +1 -0
  195. package/dist/utils/i18n.d.ts +111 -0
  196. package/dist/utils/i18n.d.ts.map +1 -0
  197. package/dist/utils/i18n.js +224 -0
  198. package/dist/utils/i18n.js.map +1 -0
  199. package/dist/utils/test-sessions.d.ts +347 -0
  200. package/dist/utils/test-sessions.d.ts.map +1 -0
  201. package/dist/utils/test-sessions.js +2215 -0
  202. package/dist/utils/test-sessions.js.map +1 -0
  203. package/example/README.md +61 -0
  204. package/example/index.html +13 -0
  205. package/example/package-lock.json +1965 -0
  206. package/example/package.json +22 -0
  207. package/example/src/app.css +1 -0
  208. package/example/src/main.ts +57 -0
  209. package/example/src/test-sessions.ts +104 -0
  210. package/example/tsconfig.json +15 -0
  211. package/example/vite.config.ts +6 -0
  212. package/package.json +45 -0
  213. package/src/ChatPanel.ts +214 -0
  214. package/src/app.css +44 -0
  215. package/src/components/AgentInterface.ts +316 -0
  216. package/src/components/AttachmentTile.ts +112 -0
  217. package/src/components/ConsoleBlock.ts +67 -0
  218. package/src/components/Input.ts +112 -0
  219. package/src/components/MessageEditor.ts +272 -0
  220. package/src/components/MessageList.ts +82 -0
  221. package/src/components/Messages.ts +310 -0
  222. package/src/components/ProviderKeyInput.ts +170 -0
  223. package/src/components/SandboxedIframe.ts +525 -0
  224. package/src/components/StreamingMessageContainer.ts +101 -0
  225. package/src/dialogs/ApiKeyPromptDialog.ts +76 -0
  226. package/src/dialogs/AttachmentOverlay.ts +635 -0
  227. package/src/dialogs/ModelSelector.ts +324 -0
  228. package/src/dialogs/SettingsDialog.ts +223 -0
  229. package/src/index.ts +63 -0
  230. package/src/state/agent-session.ts +311 -0
  231. package/src/state/transports/AppTransport.ts +363 -0
  232. package/src/state/transports/ProviderTransport.ts +49 -0
  233. package/src/state/transports/index.ts +3 -0
  234. package/src/state/transports/proxy-types.ts +15 -0
  235. package/src/state/transports/types.ts +16 -0
  236. package/src/state/types.ts +11 -0
  237. package/src/storage/app-storage.ts +53 -0
  238. package/src/storage/backends/chrome-storage-backend.ts +82 -0
  239. package/src/storage/backends/indexeddb-backend.ts +107 -0
  240. package/src/storage/backends/local-storage-backend.ts +74 -0
  241. package/src/storage/repositories/provider-keys-repository.ts +55 -0
  242. package/src/storage/repositories/settings-repository.ts +51 -0
  243. package/src/storage/types.ts +48 -0
  244. package/src/tools/artifacts/ArtifactElement.ts +15 -0
  245. package/src/tools/artifacts/HtmlArtifact.ts +221 -0
  246. package/src/tools/artifacts/MarkdownArtifact.ts +81 -0
  247. package/src/tools/artifacts/SvgArtifact.ts +77 -0
  248. package/src/tools/artifacts/TextArtifact.ts +148 -0
  249. package/src/tools/artifacts/artifacts.ts +888 -0
  250. package/src/tools/artifacts/index.ts +6 -0
  251. package/src/tools/index.ts +35 -0
  252. package/src/tools/javascript-repl.ts +309 -0
  253. package/src/tools/renderer-registry.ts +18 -0
  254. package/src/tools/renderers/BashRenderer.ts +45 -0
  255. package/src/tools/renderers/CalculateRenderer.ts +49 -0
  256. package/src/tools/renderers/DefaultRenderer.ts +36 -0
  257. package/src/tools/renderers/GetCurrentTimeRenderer.ts +39 -0
  258. package/src/tools/types.ts +7 -0
  259. package/src/utils/attachment-utils.ts +472 -0
  260. package/src/utils/auth-token.ts +22 -0
  261. package/src/utils/format.ts +42 -0
  262. package/src/utils/i18n.ts +343 -0
  263. package/src/utils/test-sessions.ts +2247 -0
  264. package/tsconfig.build.json +20 -0
  265. 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
+ }