@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,221 @@
1
+ import { CopyButton, DownloadButton, PreviewCodeToggle } from "@mariozechner/mini-lit";
2
+ import hljs from "highlight.js";
3
+ import { html } from "lit";
4
+ import { customElement, property, state } from "lit/decorators.js";
5
+ import { createRef, type Ref, ref } from "lit/directives/ref.js";
6
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
7
+ import type { SandboxIframe } from "../../components/SandboxedIframe.js";
8
+ import type { Attachment } from "../../utils/attachment-utils.js";
9
+ import { i18n } from "../../utils/i18n.js";
10
+ import "../../components/SandboxedIframe.js";
11
+ import { ArtifactElement } from "./ArtifactElement.js";
12
+
13
+ @customElement("html-artifact")
14
+ export class HtmlArtifact extends ArtifactElement {
15
+ @property() override filename = "";
16
+ @property({ attribute: false }) override displayTitle = "";
17
+ @property({ attribute: false }) attachments: Attachment[] = [];
18
+ @property({ attribute: false }) sandboxUrlProvider?: () => string;
19
+
20
+ private _content = "";
21
+ private logs: Array<{ type: "log" | "error"; text: string }> = [];
22
+
23
+ // Refs for DOM elements
24
+ private sandboxIframeRef: Ref<SandboxIframe> = createRef();
25
+ private consoleLogsRef: Ref<HTMLDivElement> = createRef();
26
+ private consoleButtonRef: Ref<HTMLButtonElement> = createRef();
27
+
28
+ // Store message handler so we can remove it
29
+ private messageHandler?: (e: MessageEvent) => void;
30
+
31
+ @state() private viewMode: "preview" | "code" = "preview";
32
+ @state() private consoleOpen = false;
33
+
34
+ private setViewMode(mode: "preview" | "code") {
35
+ this.viewMode = mode;
36
+ }
37
+
38
+ public getHeaderButtons() {
39
+ const toggle = new PreviewCodeToggle();
40
+ toggle.mode = this.viewMode;
41
+ toggle.addEventListener("mode-change", (e: Event) => {
42
+ this.setViewMode((e as CustomEvent).detail);
43
+ });
44
+
45
+ const copyButton = new CopyButton();
46
+ copyButton.text = this._content;
47
+ copyButton.title = i18n("Copy HTML");
48
+ copyButton.showText = false;
49
+
50
+ return html`
51
+ <div class="flex items-center gap-2">
52
+ ${toggle}
53
+ ${copyButton}
54
+ ${DownloadButton({ content: this._content, filename: this.filename, mimeType: "text/html", title: i18n("Download HTML") })}
55
+ </div>
56
+ `;
57
+ }
58
+
59
+ override set content(value: string) {
60
+ const oldValue = this._content;
61
+ this._content = value;
62
+ if (oldValue !== value) {
63
+ // Reset logs when content changes
64
+ this.logs = [];
65
+ if (this.consoleLogsRef.value) {
66
+ this.consoleLogsRef.value.innerHTML = "";
67
+ }
68
+ this.requestUpdate();
69
+ // Execute content in sandbox if it exists
70
+ if (this.sandboxIframeRef.value && value) {
71
+ this.updateConsoleButton();
72
+ this.executeContent(value);
73
+ }
74
+ }
75
+ }
76
+
77
+ private executeContent(html: string) {
78
+ const sandbox = this.sandboxIframeRef.value;
79
+ if (!sandbox) return;
80
+
81
+ // Configure sandbox URL provider if provided (for browser extensions)
82
+ if (this.sandboxUrlProvider) {
83
+ sandbox.sandboxUrlProvider = this.sandboxUrlProvider;
84
+ }
85
+
86
+ // Remove previous message handler if it exists
87
+ if (this.messageHandler) {
88
+ window.removeEventListener("message", this.messageHandler);
89
+ }
90
+
91
+ const sandboxId = `artifact-${this.filename}`;
92
+
93
+ // Set up message listener to collect logs
94
+ this.messageHandler = (e: MessageEvent) => {
95
+ if (e.data.sandboxId !== sandboxId) return;
96
+
97
+ if (e.data.type === "console") {
98
+ this.logs.push({
99
+ type: e.data.method === "error" ? "error" : "log",
100
+ text: e.data.text,
101
+ });
102
+ this.updateConsoleButton();
103
+ }
104
+ };
105
+ window.addEventListener("message", this.messageHandler);
106
+
107
+ // Load content (iframe persists, doesn't get removed)
108
+ sandbox.loadContent(sandboxId, html, this.attachments);
109
+ }
110
+
111
+ override get content(): string {
112
+ return this._content;
113
+ }
114
+
115
+ override disconnectedCallback() {
116
+ super.disconnectedCallback();
117
+ // Clean up message handler when element is removed from DOM
118
+ if (this.messageHandler) {
119
+ window.removeEventListener("message", this.messageHandler);
120
+ this.messageHandler = undefined;
121
+ }
122
+ }
123
+
124
+ override firstUpdated() {
125
+ // Execute initial content
126
+ if (this._content && this.sandboxIframeRef.value) {
127
+ this.executeContent(this._content);
128
+ }
129
+ }
130
+
131
+ override updated(changedProperties: Map<string | number | symbol, unknown>) {
132
+ super.updated(changedProperties);
133
+ // If we have content but haven't executed yet (e.g., during reconstruction),
134
+ // execute when the iframe ref becomes available
135
+ if (this._content && this.sandboxIframeRef.value && this.logs.length === 0) {
136
+ this.executeContent(this._content);
137
+ }
138
+ }
139
+
140
+ private updateConsoleButton() {
141
+ const button = this.consoleButtonRef.value;
142
+ if (!button) return;
143
+
144
+ const errorCount = this.logs.filter((l) => l.type === "error").length;
145
+ const text =
146
+ errorCount > 0
147
+ ? `${i18n("console")} <span class="text-destructive">${errorCount} errors</span>`
148
+ : `${i18n("console")} (${this.logs.length})`;
149
+ button.innerHTML = `<span>${text}</span><span>${this.consoleOpen ? "▼" : "▶"}</span>`;
150
+ }
151
+
152
+ private toggleConsole() {
153
+ this.consoleOpen = !this.consoleOpen;
154
+ this.requestUpdate();
155
+
156
+ // Populate console logs if opening
157
+ if (this.consoleOpen) {
158
+ requestAnimationFrame(() => {
159
+ if (this.consoleLogsRef.value) {
160
+ // Populate with existing logs
161
+ this.consoleLogsRef.value.innerHTML = "";
162
+ this.logs.forEach((log) => {
163
+ const logEl = document.createElement("div");
164
+ logEl.className = `text-xs font-mono ${log.type === "error" ? "text-destructive" : "text-muted-foreground"}`;
165
+ logEl.textContent = `[${log.type}] ${log.text}`;
166
+ this.consoleLogsRef.value!.appendChild(logEl);
167
+ });
168
+ }
169
+ });
170
+ }
171
+ }
172
+
173
+ public getLogs(): string {
174
+ if (this.logs.length === 0) return i18n("No logs for {filename}").replace("{filename}", this.filename);
175
+ return this.logs.map((l) => `[${l.type}] ${l.text}`).join("\n");
176
+ }
177
+
178
+ override render() {
179
+ return html`
180
+ <div class="h-full flex flex-col">
181
+ <div class="flex-1 overflow-hidden relative">
182
+ <!-- Preview container - always in DOM, just hidden when not active -->
183
+ <div class="absolute inset-0 flex flex-col" style="display: ${this.viewMode === "preview" ? "flex" : "none"}">
184
+ <sandbox-iframe class="flex-1" ${ref(this.sandboxIframeRef)}></sandbox-iframe>
185
+ ${
186
+ this.logs.length > 0
187
+ ? html`
188
+ <div class="border-t border-border">
189
+ <button
190
+ @click=${() => this.toggleConsole()}
191
+ class="w-full px-3 py-1 text-xs text-left hover:bg-muted flex items-center justify-between"
192
+ ${ref(this.consoleButtonRef)}
193
+ >
194
+ <span
195
+ >${i18n("console")}
196
+ ${
197
+ this.logs.filter((l) => l.type === "error").length > 0
198
+ ? html`<span class="text-destructive">${this.logs.filter((l) => l.type === "error").length} errors</span>`
199
+ : `(${this.logs.length})`
200
+ }</span
201
+ >
202
+ <span>${this.consoleOpen ? "▼" : "▶"}</span>
203
+ </button>
204
+ ${this.consoleOpen ? html` <div class="max-h-48 overflow-y-auto bg-muted/50 p-2" ${ref(this.consoleLogsRef)}></div> ` : ""}
205
+ </div>
206
+ `
207
+ : ""
208
+ }
209
+ </div>
210
+
211
+ <!-- Code view - always in DOM, just hidden when not active -->
212
+ <div class="absolute inset-0 overflow-auto bg-background" style="display: ${this.viewMode === "code" ? "block" : "none"}">
213
+ <pre class="m-0 p-4 text-xs"><code class="hljs language-html">${unsafeHTML(
214
+ hljs.highlight(this._content, { language: "html" }).value,
215
+ )}</code></pre>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ `;
220
+ }
221
+ }
@@ -0,0 +1,81 @@
1
+ import { CopyButton, DownloadButton, PreviewCodeToggle } from "@mariozechner/mini-lit";
2
+ import hljs from "highlight.js";
3
+ import { html } from "lit";
4
+ import { customElement, property, state } from "lit/decorators.js";
5
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
6
+ import { i18n } from "../../utils/i18n.js";
7
+ import "@mariozechner/mini-lit/dist/MarkdownBlock.js";
8
+ import { ArtifactElement } from "./ArtifactElement.js";
9
+
10
+ @customElement("markdown-artifact")
11
+ export class MarkdownArtifact extends ArtifactElement {
12
+ @property() override filename = "";
13
+ @property({ attribute: false }) override displayTitle = "";
14
+
15
+ private _content = "";
16
+ override get content(): string {
17
+ return this._content;
18
+ }
19
+ override set content(value: string) {
20
+ this._content = value;
21
+ this.requestUpdate();
22
+ }
23
+
24
+ @state() private viewMode: "preview" | "code" = "preview";
25
+
26
+ protected override createRenderRoot(): HTMLElement | DocumentFragment {
27
+ return this; // light DOM
28
+ }
29
+
30
+ private setViewMode(mode: "preview" | "code") {
31
+ this.viewMode = mode;
32
+ }
33
+
34
+ public getHeaderButtons() {
35
+ const toggle = new PreviewCodeToggle();
36
+ toggle.mode = this.viewMode;
37
+ toggle.addEventListener("mode-change", (e: Event) => {
38
+ this.setViewMode((e as CustomEvent).detail);
39
+ });
40
+
41
+ const copyButton = new CopyButton();
42
+ copyButton.text = this._content;
43
+ copyButton.title = i18n("Copy Markdown");
44
+ copyButton.showText = false;
45
+
46
+ return html`
47
+ <div class="flex items-center gap-2">
48
+ ${toggle}
49
+ ${copyButton}
50
+ ${DownloadButton({
51
+ content: this._content,
52
+ filename: this.filename,
53
+ mimeType: "text/markdown",
54
+ title: i18n("Download Markdown"),
55
+ })}
56
+ </div>
57
+ `;
58
+ }
59
+
60
+ override render() {
61
+ return html`
62
+ <div class="h-full flex flex-col">
63
+ <div class="flex-1 overflow-auto">
64
+ ${
65
+ this.viewMode === "preview"
66
+ ? html`<div class="p-4"><markdown-block .content=${this.content}></markdown-block></div>`
67
+ : html`<pre class="m-0 p-4 text-xs whitespace-pre-wrap break-words"><code class="hljs language-markdown">${unsafeHTML(
68
+ hljs.highlight(this.content, { language: "markdown", ignoreIllegals: true }).value,
69
+ )}</code></pre>`
70
+ }
71
+ </div>
72
+ </div>
73
+ `;
74
+ }
75
+ }
76
+
77
+ declare global {
78
+ interface HTMLElementTagNameMap {
79
+ "markdown-artifact": MarkdownArtifact;
80
+ }
81
+ }
@@ -0,0 +1,77 @@
1
+ import { CopyButton, DownloadButton, PreviewCodeToggle } from "@mariozechner/mini-lit";
2
+ import hljs from "highlight.js";
3
+ import { html } from "lit";
4
+ import { customElement, property, state } from "lit/decorators.js";
5
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
6
+ import { i18n } from "../../utils/i18n.js";
7
+ import { ArtifactElement } from "./ArtifactElement.js";
8
+
9
+ @customElement("svg-artifact")
10
+ export class SvgArtifact extends ArtifactElement {
11
+ @property() override filename = "";
12
+ @property({ attribute: false }) override displayTitle = "";
13
+
14
+ private _content = "";
15
+ override get content(): string {
16
+ return this._content;
17
+ }
18
+ override set content(value: string) {
19
+ this._content = value;
20
+ this.requestUpdate();
21
+ }
22
+
23
+ @state() private viewMode: "preview" | "code" = "preview";
24
+
25
+ protected override createRenderRoot(): HTMLElement | DocumentFragment {
26
+ return this; // light DOM
27
+ }
28
+
29
+ private setViewMode(mode: "preview" | "code") {
30
+ this.viewMode = mode;
31
+ }
32
+
33
+ public getHeaderButtons() {
34
+ const toggle = new PreviewCodeToggle();
35
+ toggle.mode = this.viewMode;
36
+ toggle.addEventListener("mode-change", (e: Event) => {
37
+ this.setViewMode((e as CustomEvent).detail);
38
+ });
39
+
40
+ const copyButton = new CopyButton();
41
+ copyButton.text = this._content;
42
+ copyButton.title = i18n("Copy SVG");
43
+ copyButton.showText = false;
44
+
45
+ return html`
46
+ <div class="flex items-center gap-2">
47
+ ${toggle}
48
+ ${copyButton}
49
+ ${DownloadButton({ content: this._content, filename: this.filename, mimeType: "image/svg+xml", title: i18n("Download SVG") })}
50
+ </div>
51
+ `;
52
+ }
53
+
54
+ override render() {
55
+ return html`
56
+ <div class="h-full flex flex-col">
57
+ <div class="flex-1 overflow-auto">
58
+ ${
59
+ this.viewMode === "preview"
60
+ ? html`<div class="h-full flex items-center justify-center">
61
+ ${unsafeHTML(this.content.replace(/<svg(\s|>)/i, (_m, p1) => `<svg class="w-full h-full"${p1}`))}
62
+ </div>`
63
+ : html`<pre class="m-0 p-4 text-xs"><code class="hljs language-xml">${unsafeHTML(
64
+ hljs.highlight(this.content, { language: "xml", ignoreIllegals: true }).value,
65
+ )}</code></pre>`
66
+ }
67
+ </div>
68
+ </div>
69
+ `;
70
+ }
71
+ }
72
+
73
+ declare global {
74
+ interface HTMLElementTagNameMap {
75
+ "svg-artifact": SvgArtifact;
76
+ }
77
+ }
@@ -0,0 +1,148 @@
1
+ import { CopyButton, DownloadButton } from "@mariozechner/mini-lit";
2
+ import hljs from "highlight.js";
3
+ import { html } from "lit";
4
+ import { customElement, property } from "lit/decorators.js";
5
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
6
+ import { i18n } from "../../utils/i18n.js";
7
+ import { ArtifactElement } from "./ArtifactElement.js";
8
+
9
+ // Known code file extensions for highlighting
10
+ const CODE_EXTENSIONS = [
11
+ "js",
12
+ "javascript",
13
+ "ts",
14
+ "typescript",
15
+ "jsx",
16
+ "tsx",
17
+ "py",
18
+ "python",
19
+ "java",
20
+ "c",
21
+ "cpp",
22
+ "cs",
23
+ "php",
24
+ "rb",
25
+ "ruby",
26
+ "go",
27
+ "rust",
28
+ "swift",
29
+ "kotlin",
30
+ "scala",
31
+ "dart",
32
+ "html",
33
+ "css",
34
+ "scss",
35
+ "sass",
36
+ "less",
37
+ "json",
38
+ "xml",
39
+ "yaml",
40
+ "yml",
41
+ "toml",
42
+ "sql",
43
+ "sh",
44
+ "bash",
45
+ "ps1",
46
+ "bat",
47
+ "r",
48
+ "matlab",
49
+ "julia",
50
+ "lua",
51
+ "perl",
52
+ "vue",
53
+ "svelte",
54
+ ];
55
+
56
+ @customElement("text-artifact")
57
+ export class TextArtifact extends ArtifactElement {
58
+ @property() override filename = "";
59
+ @property({ attribute: false }) override displayTitle = "";
60
+
61
+ private _content = "";
62
+ override get content(): string {
63
+ return this._content;
64
+ }
65
+ override set content(value: string) {
66
+ this._content = value;
67
+ this.requestUpdate();
68
+ }
69
+
70
+ protected override createRenderRoot(): HTMLElement | DocumentFragment {
71
+ return this; // light DOM
72
+ }
73
+
74
+ private isCode(): boolean {
75
+ const ext = this.filename.split(".").pop()?.toLowerCase() || "";
76
+ return CODE_EXTENSIONS.includes(ext);
77
+ }
78
+
79
+ private getLanguageFromExtension(ext: string): string {
80
+ const languageMap: Record<string, string> = {
81
+ js: "javascript",
82
+ ts: "typescript",
83
+ py: "python",
84
+ rb: "ruby",
85
+ yml: "yaml",
86
+ ps1: "powershell",
87
+ bat: "batch",
88
+ };
89
+ return languageMap[ext] || ext;
90
+ }
91
+
92
+ private getMimeType(): string {
93
+ const ext = this.filename.split(".").pop()?.toLowerCase() || "";
94
+ if (ext === "svg") return "image/svg+xml";
95
+ if (ext === "md" || ext === "markdown") return "text/markdown";
96
+ return "text/plain";
97
+ }
98
+
99
+ public getHeaderButtons() {
100
+ const copyButton = new CopyButton();
101
+ copyButton.text = this.content;
102
+ copyButton.title = i18n("Copy");
103
+ copyButton.showText = false;
104
+
105
+ return html`
106
+ <div class="flex items-center gap-1">
107
+ ${copyButton}
108
+ ${DownloadButton({
109
+ content: this.content,
110
+ filename: this.filename,
111
+ mimeType: this.getMimeType(),
112
+ title: i18n("Download"),
113
+ })}
114
+ </div>
115
+ `;
116
+ }
117
+
118
+ override render() {
119
+ const isCode = this.isCode();
120
+ const ext = this.filename.split(".").pop() || "";
121
+ return html`
122
+ <div class="h-full flex flex-col">
123
+ <div class="flex-1 overflow-auto">
124
+ ${
125
+ isCode
126
+ ? html`
127
+ <pre class="m-0 p-4 text-xs"><code class="hljs language-${this.getLanguageFromExtension(
128
+ ext.toLowerCase(),
129
+ )}">${unsafeHTML(
130
+ hljs.highlight(this.content, {
131
+ language: this.getLanguageFromExtension(ext.toLowerCase()),
132
+ ignoreIllegals: true,
133
+ }).value,
134
+ )}</code></pre>
135
+ `
136
+ : html` <pre class="m-0 p-4 text-xs font-mono">${this.content}</pre> `
137
+ }
138
+ </div>
139
+ </div>
140
+ `;
141
+ }
142
+ }
143
+
144
+ declare global {
145
+ interface HTMLElementTagNameMap {
146
+ "text-artifact": TextArtifact;
147
+ }
148
+ }