@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,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
|
+
}
|