@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,635 @@
1
+ import { Button, html, icon } from "@mariozechner/mini-lit";
2
+ import "@mariozechner/mini-lit/dist/ModeToggle.js";
3
+ import { renderAsync } from "docx-preview";
4
+ import { LitElement } from "lit";
5
+ import { state } from "lit/decorators.js";
6
+ import { Download, X } from "lucide";
7
+ import * as pdfjsLib from "pdfjs-dist";
8
+ import * as XLSX from "xlsx";
9
+ import type { Attachment } from "../utils/attachment-utils.js";
10
+ import { i18n } from "../utils/i18n.js";
11
+
12
+ type FileType = "image" | "pdf" | "docx" | "pptx" | "excel" | "text";
13
+
14
+ export class AttachmentOverlay extends LitElement {
15
+ @state() private attachment?: Attachment;
16
+ @state() private showExtractedText = false;
17
+ @state() private error: string | null = null;
18
+
19
+ // Track current loading task to cancel if needed
20
+ private currentLoadingTask: any = null;
21
+ private onCloseCallback?: () => void;
22
+ private boundHandleKeyDown?: (e: KeyboardEvent) => void;
23
+
24
+ protected override createRenderRoot(): HTMLElement | DocumentFragment {
25
+ return this;
26
+ }
27
+
28
+ static open(attachment: Attachment, onClose?: () => void) {
29
+ const overlay = new AttachmentOverlay();
30
+ overlay.attachment = attachment;
31
+ overlay.onCloseCallback = onClose;
32
+ document.body.appendChild(overlay);
33
+ overlay.setupEventListeners();
34
+ }
35
+
36
+ private setupEventListeners() {
37
+ this.boundHandleKeyDown = (e: KeyboardEvent) => {
38
+ if (e.key === "Escape") {
39
+ this.close();
40
+ }
41
+ };
42
+ window.addEventListener("keydown", this.boundHandleKeyDown);
43
+ }
44
+
45
+ private close() {
46
+ this.cleanup();
47
+ if (this.boundHandleKeyDown) {
48
+ window.removeEventListener("keydown", this.boundHandleKeyDown);
49
+ }
50
+ this.onCloseCallback?.();
51
+ this.remove();
52
+ }
53
+
54
+ private getFileType(): FileType {
55
+ if (!this.attachment) return "text";
56
+
57
+ if (this.attachment.type === "image") return "image";
58
+ if (this.attachment.mimeType === "application/pdf") return "pdf";
59
+ if (this.attachment.mimeType?.includes("wordprocessingml")) return "docx";
60
+ if (
61
+ this.attachment.mimeType?.includes("presentationml") ||
62
+ this.attachment.fileName.toLowerCase().endsWith(".pptx")
63
+ )
64
+ return "pptx";
65
+ if (
66
+ this.attachment.mimeType?.includes("spreadsheetml") ||
67
+ this.attachment.mimeType?.includes("ms-excel") ||
68
+ this.attachment.fileName.toLowerCase().endsWith(".xlsx") ||
69
+ this.attachment.fileName.toLowerCase().endsWith(".xls")
70
+ )
71
+ return "excel";
72
+
73
+ return "text";
74
+ }
75
+
76
+ private getFileTypeLabel(): string {
77
+ const type = this.getFileType();
78
+ switch (type) {
79
+ case "pdf":
80
+ return i18n("PDF");
81
+ case "docx":
82
+ return i18n("Document");
83
+ case "pptx":
84
+ return i18n("Presentation");
85
+ case "excel":
86
+ return i18n("Spreadsheet");
87
+ default:
88
+ return "";
89
+ }
90
+ }
91
+
92
+ private handleBackdropClick = () => {
93
+ this.close();
94
+ };
95
+
96
+ private handleDownload = () => {
97
+ if (!this.attachment) return;
98
+
99
+ // Create a blob from the base64 content
100
+ const byteCharacters = atob(this.attachment.content);
101
+ const byteNumbers = new Array(byteCharacters.length);
102
+ for (let i = 0; i < byteCharacters.length; i++) {
103
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
104
+ }
105
+ const byteArray = new Uint8Array(byteNumbers);
106
+ const blob = new Blob([byteArray], { type: this.attachment.mimeType });
107
+
108
+ // Create download link
109
+ const url = URL.createObjectURL(blob);
110
+ const a = document.createElement("a");
111
+ a.href = url;
112
+ a.download = this.attachment.fileName;
113
+ document.body.appendChild(a);
114
+ a.click();
115
+ document.body.removeChild(a);
116
+ URL.revokeObjectURL(url);
117
+ };
118
+
119
+ private cleanup() {
120
+ this.showExtractedText = false;
121
+ this.error = null;
122
+ // Cancel any loading PDF task when closing
123
+ if (this.currentLoadingTask) {
124
+ this.currentLoadingTask.destroy();
125
+ this.currentLoadingTask = null;
126
+ }
127
+ }
128
+
129
+ override render() {
130
+ if (!this.attachment) return html``;
131
+
132
+ return html`
133
+ <!-- Full screen overlay -->
134
+ <div class="fixed inset-0 bg-black/90 z-50 flex flex-col" @click=${this.handleBackdropClick}>
135
+ <!-- Compact header bar -->
136
+ <div class="bg-background/95 backdrop-blur border-b border-border" @click=${(e: Event) => e.stopPropagation()}>
137
+ <div class="px-4 py-2 flex items-center justify-between">
138
+ <div class="flex items-center gap-3 min-w-0">
139
+ <span class="text-sm font-medium text-foreground truncate">${this.attachment.fileName}</span>
140
+ </div>
141
+ <div class="flex items-center gap-2">
142
+ ${this.renderToggle()}
143
+ ${Button({
144
+ variant: "ghost",
145
+ size: "icon",
146
+ onClick: this.handleDownload,
147
+ children: icon(Download, "sm"),
148
+ className: "h-8 w-8",
149
+ })}
150
+ ${Button({
151
+ variant: "ghost",
152
+ size: "icon",
153
+ onClick: () => this.close(),
154
+ children: icon(X, "sm"),
155
+ className: "h-8 w-8",
156
+ })}
157
+ </div>
158
+ </div>
159
+ </div>
160
+
161
+ <!-- Content container -->
162
+ <div class="flex-1 flex items-center justify-center overflow-auto" @click=${(e: Event) => e.stopPropagation()}>
163
+ ${this.renderContent()}
164
+ </div>
165
+ </div>
166
+ `;
167
+ }
168
+
169
+ private renderToggle() {
170
+ if (!this.attachment) return html``;
171
+
172
+ const fileType = this.getFileType();
173
+ const hasExtractedText = !!this.attachment.extractedText;
174
+ const showToggle = fileType !== "image" && fileType !== "text" && fileType !== "pptx" && hasExtractedText;
175
+
176
+ if (!showToggle) return html``;
177
+
178
+ const fileTypeLabel = this.getFileTypeLabel();
179
+
180
+ return html`
181
+ <mode-toggle
182
+ .modes=${[fileTypeLabel, i18n("Text")]}
183
+ .selectedIndex=${this.showExtractedText ? 1 : 0}
184
+ @mode-change=${(e: CustomEvent<{ index: number; mode: string }>) => {
185
+ e.stopPropagation();
186
+ this.showExtractedText = e.detail.index === 1;
187
+ this.error = null;
188
+ }}
189
+ ></mode-toggle>
190
+ `;
191
+ }
192
+
193
+ private renderContent() {
194
+ if (!this.attachment) return html``;
195
+
196
+ // Error state
197
+ if (this.error) {
198
+ return html`
199
+ <div class="bg-destructive/10 border border-destructive text-destructive p-4 rounded-lg max-w-2xl">
200
+ <div class="font-medium mb-1">${i18n("Error loading file")}</div>
201
+ <div class="text-sm opacity-90">${this.error}</div>
202
+ </div>
203
+ `;
204
+ }
205
+
206
+ // Content based on file type
207
+ return this.renderFileContent();
208
+ }
209
+
210
+ private renderFileContent() {
211
+ if (!this.attachment) return html``;
212
+
213
+ const fileType = this.getFileType();
214
+
215
+ // Show extracted text if toggled
216
+ if (this.showExtractedText && fileType !== "image") {
217
+ return html`
218
+ <div class="bg-card border border-border text-foreground p-6 w-full h-full max-w-4xl overflow-auto">
219
+ <pre class="whitespace-pre-wrap font-mono text-xs leading-relaxed">${
220
+ this.attachment.extractedText || i18n("No text content available")
221
+ }</pre>
222
+ </div>
223
+ `;
224
+ }
225
+
226
+ // Render based on file type
227
+ switch (fileType) {
228
+ case "image": {
229
+ const imageUrl = `data:${this.attachment.mimeType};base64,${this.attachment.content}`;
230
+ return html`
231
+ <img src="${imageUrl}" class="max-w-full max-h-full object-contain rounded-lg shadow-lg" alt="${this.attachment.fileName}" />
232
+ `;
233
+ }
234
+
235
+ case "pdf":
236
+ return html`
237
+ <div
238
+ id="pdf-container"
239
+ class="bg-card text-foreground overflow-auto shadow-lg border border-border w-full h-full max-w-[1000px]"
240
+ ></div>
241
+ `;
242
+
243
+ case "docx":
244
+ return html`
245
+ <div
246
+ id="docx-container"
247
+ class="bg-card text-foreground overflow-auto shadow-lg border border-border w-full h-full max-w-[1000px]"
248
+ ></div>
249
+ `;
250
+
251
+ case "excel":
252
+ return html` <div id="excel-container" class="bg-card text-foreground overflow-auto w-full h-full"></div> `;
253
+
254
+ case "pptx":
255
+ return html`
256
+ <div
257
+ id="pptx-container"
258
+ class="bg-card text-foreground overflow-auto shadow-lg border border-border w-full h-full max-w-[1000px]"
259
+ ></div>
260
+ `;
261
+
262
+ default:
263
+ return html`
264
+ <div class="bg-card border border-border text-foreground p-6 w-full h-full max-w-4xl overflow-auto">
265
+ <pre class="whitespace-pre-wrap font-mono text-sm">${
266
+ this.attachment.extractedText || i18n("No content available")
267
+ }</pre>
268
+ </div>
269
+ `;
270
+ }
271
+ }
272
+
273
+ override async updated(changedProperties: Map<string, any>) {
274
+ super.updated(changedProperties);
275
+
276
+ // Only process if we need to render the actual file (not extracted text)
277
+ if (
278
+ (changedProperties.has("attachment") || changedProperties.has("showExtractedText")) &&
279
+ this.attachment &&
280
+ !this.showExtractedText &&
281
+ !this.error
282
+ ) {
283
+ const fileType = this.getFileType();
284
+
285
+ switch (fileType) {
286
+ case "pdf":
287
+ await this.renderPdf();
288
+ break;
289
+ case "docx":
290
+ await this.renderDocx();
291
+ break;
292
+ case "excel":
293
+ await this.renderExcel();
294
+ break;
295
+ case "pptx":
296
+ await this.renderExtractedText();
297
+ break;
298
+ }
299
+ }
300
+ }
301
+
302
+ private async renderPdf() {
303
+ const container = this.querySelector("#pdf-container");
304
+ if (!container || !this.attachment) return;
305
+
306
+ let pdf: any = null;
307
+
308
+ try {
309
+ // Convert base64 to ArrayBuffer
310
+ const arrayBuffer = this.base64ToArrayBuffer(this.attachment.content);
311
+
312
+ // Cancel any existing loading task
313
+ if (this.currentLoadingTask) {
314
+ this.currentLoadingTask.destroy();
315
+ }
316
+
317
+ // Load the PDF
318
+ this.currentLoadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
319
+ pdf = await this.currentLoadingTask.promise;
320
+ this.currentLoadingTask = null;
321
+
322
+ // Clear container and add wrapper
323
+ container.innerHTML = "";
324
+ const wrapper = document.createElement("div");
325
+ wrapper.className = "";
326
+ container.appendChild(wrapper);
327
+
328
+ // Render all pages
329
+ for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
330
+ const page = await pdf.getPage(pageNum);
331
+
332
+ // Create a container for each page
333
+ const pageContainer = document.createElement("div");
334
+ pageContainer.className = "mb-4 last:mb-0";
335
+
336
+ // Create canvas for this page
337
+ const canvas = document.createElement("canvas");
338
+ const context = canvas.getContext("2d");
339
+
340
+ // Set scale for reasonable resolution
341
+ const viewport = page.getViewport({ scale: 1.5 });
342
+ canvas.height = viewport.height;
343
+ canvas.width = viewport.width;
344
+
345
+ // Style the canvas
346
+ canvas.className = "w-full max-w-full h-auto block mx-auto bg-white rounded shadow-sm border border-border";
347
+
348
+ // Fill white background for proper PDF rendering
349
+ if (context) {
350
+ context.fillStyle = "white";
351
+ context.fillRect(0, 0, canvas.width, canvas.height);
352
+ }
353
+
354
+ // Render page
355
+ await page.render({
356
+ canvasContext: context!,
357
+ viewport: viewport,
358
+ canvas: canvas,
359
+ }).promise;
360
+
361
+ pageContainer.appendChild(canvas);
362
+
363
+ // Add page separator for multi-page documents
364
+ if (pageNum < pdf.numPages) {
365
+ const separator = document.createElement("div");
366
+ separator.className = "h-px bg-border my-4";
367
+ pageContainer.appendChild(separator);
368
+ }
369
+
370
+ wrapper.appendChild(pageContainer);
371
+ }
372
+ } catch (error: any) {
373
+ console.error("Error rendering PDF:", error);
374
+ this.error = error?.message || i18n("Failed to load PDF");
375
+ } finally {
376
+ if (pdf) {
377
+ pdf.destroy();
378
+ }
379
+ }
380
+ }
381
+
382
+ private async renderDocx() {
383
+ const container = this.querySelector("#docx-container");
384
+ if (!container || !this.attachment) return;
385
+
386
+ try {
387
+ // Convert base64 to ArrayBuffer
388
+ const arrayBuffer = this.base64ToArrayBuffer(this.attachment.content);
389
+
390
+ // Clear container first
391
+ container.innerHTML = "";
392
+
393
+ // Create a wrapper div for the document
394
+ const wrapper = document.createElement("div");
395
+ wrapper.className = "docx-wrapper-custom";
396
+ container.appendChild(wrapper);
397
+
398
+ // Render the DOCX file into the wrapper
399
+ await renderAsync(arrayBuffer, wrapper as HTMLElement, undefined, {
400
+ className: "docx",
401
+ inWrapper: true,
402
+ ignoreWidth: true, // Let it be responsive
403
+ ignoreHeight: false,
404
+ ignoreFonts: false,
405
+ breakPages: true,
406
+ ignoreLastRenderedPageBreak: true,
407
+ experimental: false,
408
+ trimXmlDeclaration: true,
409
+ useBase64URL: false,
410
+ renderHeaders: true,
411
+ renderFooters: true,
412
+ renderFootnotes: true,
413
+ renderEndnotes: true,
414
+ });
415
+
416
+ // Apply custom styles to match theme and fix sizing
417
+ const style = document.createElement("style");
418
+ style.textContent = `
419
+ #docx-container {
420
+ padding: 0;
421
+ }
422
+
423
+ #docx-container .docx-wrapper-custom {
424
+ max-width: 100%;
425
+ overflow-x: auto;
426
+ }
427
+
428
+ #docx-container .docx-wrapper {
429
+ max-width: 100% !important;
430
+ margin: 0 !important;
431
+ background: transparent !important;
432
+ padding: 0em !important;
433
+ }
434
+
435
+ #docx-container .docx-wrapper > section.docx {
436
+ box-shadow: none !important;
437
+ border: none !important;
438
+ border-radius: 0 !important;
439
+ margin: 0 !important;
440
+ padding: 2em !important;
441
+ background: white !important;
442
+ color: black !important;
443
+ max-width: 100% !important;
444
+ width: 100% !important;
445
+ min-width: 0 !important;
446
+ overflow-x: auto !important;
447
+ }
448
+
449
+ /* Fix tables and wide content */
450
+ #docx-container table {
451
+ max-width: 100% !important;
452
+ width: auto !important;
453
+ overflow-x: auto !important;
454
+ display: block !important;
455
+ }
456
+
457
+ #docx-container img {
458
+ max-width: 100% !important;
459
+ height: auto !important;
460
+ }
461
+
462
+ /* Fix paragraphs and text */
463
+ #docx-container p,
464
+ #docx-container span,
465
+ #docx-container div {
466
+ max-width: 100% !important;
467
+ word-wrap: break-word !important;
468
+ overflow-wrap: break-word !important;
469
+ }
470
+
471
+ /* Hide page breaks in web view */
472
+ #docx-container .docx-page-break {
473
+ display: none !important;
474
+ }
475
+ `;
476
+ container.appendChild(style);
477
+ } catch (error: any) {
478
+ console.error("Error rendering DOCX:", error);
479
+ this.error = error?.message || i18n("Failed to load document");
480
+ }
481
+ }
482
+
483
+ private async renderExcel() {
484
+ const container = this.querySelector("#excel-container");
485
+ if (!container || !this.attachment) return;
486
+
487
+ try {
488
+ // Convert base64 to ArrayBuffer
489
+ const arrayBuffer = this.base64ToArrayBuffer(this.attachment.content);
490
+
491
+ // Read the workbook
492
+ const workbook = XLSX.read(arrayBuffer, { type: "array" });
493
+
494
+ // Clear container
495
+ container.innerHTML = "";
496
+ const wrapper = document.createElement("div");
497
+ wrapper.className = "overflow-auto h-full flex flex-col";
498
+ container.appendChild(wrapper);
499
+
500
+ // Create tabs for multiple sheets
501
+ if (workbook.SheetNames.length > 1) {
502
+ const tabContainer = document.createElement("div");
503
+ tabContainer.className = "flex gap-2 mb-4 border-b border-border sticky top-0 bg-card z-10";
504
+
505
+ const sheetContents: HTMLElement[] = [];
506
+
507
+ workbook.SheetNames.forEach((sheetName, index) => {
508
+ // Create tab button
509
+ const tab = document.createElement("button");
510
+ tab.textContent = sheetName;
511
+ tab.className =
512
+ index === 0
513
+ ? "px-4 py-2 text-sm font-medium border-b-2 border-primary text-primary"
514
+ : "px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:border-b-2 hover:border-border transition-colors";
515
+
516
+ // Create sheet content
517
+ const sheetDiv = document.createElement("div");
518
+ sheetDiv.style.display = index === 0 ? "flex" : "none";
519
+ sheetDiv.className = "flex-1 overflow-auto";
520
+ sheetDiv.appendChild(this.renderExcelSheet(workbook.Sheets[sheetName], sheetName));
521
+ sheetContents.push(sheetDiv);
522
+
523
+ // Tab click handler
524
+ tab.onclick = () => {
525
+ // Update tab styles
526
+ tabContainer.querySelectorAll("button").forEach((btn, btnIndex) => {
527
+ if (btnIndex === index) {
528
+ btn.className = "px-4 py-2 text-sm font-medium border-b-2 border-primary text-primary";
529
+ } else {
530
+ btn.className =
531
+ "px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:border-b-2 hover:border-border transition-colors";
532
+ }
533
+ });
534
+ // Show/hide sheets
535
+ sheetContents.forEach((content, contentIndex) => {
536
+ content.style.display = contentIndex === index ? "flex" : "none";
537
+ });
538
+ };
539
+
540
+ tabContainer.appendChild(tab);
541
+ });
542
+
543
+ wrapper.appendChild(tabContainer);
544
+ sheetContents.forEach((content) => {
545
+ wrapper.appendChild(content);
546
+ });
547
+ } else {
548
+ // Single sheet
549
+ const sheetName = workbook.SheetNames[0];
550
+ wrapper.appendChild(this.renderExcelSheet(workbook.Sheets[sheetName], sheetName));
551
+ }
552
+ } catch (error: any) {
553
+ console.error("Error rendering Excel:", error);
554
+ this.error = error?.message || i18n("Failed to load spreadsheet");
555
+ }
556
+ }
557
+
558
+ private renderExcelSheet(worksheet: any, sheetName: string): HTMLElement {
559
+ const sheetDiv = document.createElement("div");
560
+
561
+ // Generate HTML table
562
+ const htmlTable = XLSX.utils.sheet_to_html(worksheet, { id: `sheet-${sheetName}` });
563
+ const tempDiv = document.createElement("div");
564
+ tempDiv.innerHTML = htmlTable;
565
+
566
+ // Find and style the table
567
+ const table = tempDiv.querySelector("table");
568
+ if (table) {
569
+ table.className = "w-full border-collapse text-foreground";
570
+
571
+ // Style all cells
572
+ table.querySelectorAll("td, th").forEach((cell) => {
573
+ const cellEl = cell as HTMLElement;
574
+ cellEl.className = "border border-border px-3 py-2 text-sm text-left";
575
+ });
576
+
577
+ // Style header row
578
+ const headerCells = table.querySelectorAll("thead th, tr:first-child td");
579
+ if (headerCells.length > 0) {
580
+ headerCells.forEach((th) => {
581
+ const thEl = th as HTMLElement;
582
+ thEl.className =
583
+ "border border-border px-3 py-2 text-sm font-semibold bg-muted text-foreground sticky top-0";
584
+ });
585
+ }
586
+
587
+ // Alternate row colors
588
+ table.querySelectorAll("tbody tr:nth-child(even)").forEach((row) => {
589
+ const rowEl = row as HTMLElement;
590
+ rowEl.className = "bg-muted/30";
591
+ });
592
+
593
+ sheetDiv.appendChild(table);
594
+ }
595
+
596
+ return sheetDiv;
597
+ }
598
+
599
+ private base64ToArrayBuffer(base64: string): ArrayBuffer {
600
+ const binaryString = atob(base64);
601
+ const bytes = new Uint8Array(binaryString.length);
602
+ for (let i = 0; i < binaryString.length; i++) {
603
+ bytes[i] = binaryString.charCodeAt(i);
604
+ }
605
+ return bytes.buffer;
606
+ }
607
+
608
+ private async renderExtractedText() {
609
+ const container = this.querySelector("#pptx-container");
610
+ if (!container || !this.attachment) return;
611
+
612
+ try {
613
+ // Display the extracted text content
614
+ container.innerHTML = "";
615
+ const wrapper = document.createElement("div");
616
+ wrapper.className = "p-6 overflow-auto";
617
+
618
+ // Create a pre element to preserve formatting
619
+ const pre = document.createElement("pre");
620
+ pre.className = "whitespace-pre-wrap text-sm text-foreground font-mono";
621
+ pre.textContent = this.attachment.extractedText || i18n("No text content available");
622
+
623
+ wrapper.appendChild(pre);
624
+ container.appendChild(wrapper);
625
+ } catch (error: any) {
626
+ console.error("Error rendering extracted text:", error);
627
+ this.error = error?.message || i18n("Failed to display text content");
628
+ }
629
+ }
630
+ }
631
+
632
+ // Register the custom element only once
633
+ if (!customElements.get("attachment-overlay")) {
634
+ customElements.define("attachment-overlay", AttachmentOverlay);
635
+ }