@oh-my-pi/pi-web-ui 0.1.0

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 (86) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +609 -0
  3. package/example/README.md +61 -0
  4. package/example/index.html +13 -0
  5. package/example/package.json +25 -0
  6. package/example/src/app.css +1 -0
  7. package/example/src/custom-messages.ts +99 -0
  8. package/example/src/main.ts +420 -0
  9. package/example/tsconfig.json +23 -0
  10. package/example/vite.config.ts +6 -0
  11. package/package.json +58 -0
  12. package/scripts/count-prompt-tokens.ts +88 -0
  13. package/src/ChatPanel.ts +218 -0
  14. package/src/app.css +68 -0
  15. package/src/components/AgentInterface.ts +390 -0
  16. package/src/components/AttachmentTile.ts +107 -0
  17. package/src/components/ConsoleBlock.ts +74 -0
  18. package/src/components/CustomProviderCard.ts +96 -0
  19. package/src/components/ExpandableSection.ts +46 -0
  20. package/src/components/Input.ts +113 -0
  21. package/src/components/MessageEditor.ts +404 -0
  22. package/src/components/MessageList.ts +97 -0
  23. package/src/components/Messages.ts +384 -0
  24. package/src/components/ProviderKeyInput.ts +152 -0
  25. package/src/components/SandboxedIframe.ts +626 -0
  26. package/src/components/StreamingMessageContainer.ts +107 -0
  27. package/src/components/ThinkingBlock.ts +45 -0
  28. package/src/components/message-renderer-registry.ts +28 -0
  29. package/src/components/sandbox/ArtifactsRuntimeProvider.ts +219 -0
  30. package/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
  31. package/src/components/sandbox/ConsoleRuntimeProvider.ts +186 -0
  32. package/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
  33. package/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
  34. package/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
  35. package/src/components/sandbox/SandboxRuntimeProvider.ts +52 -0
  36. package/src/dialogs/ApiKeyPromptDialog.ts +75 -0
  37. package/src/dialogs/AttachmentOverlay.ts +640 -0
  38. package/src/dialogs/CustomProviderDialog.ts +274 -0
  39. package/src/dialogs/ModelSelector.ts +314 -0
  40. package/src/dialogs/PersistentStorageDialog.ts +146 -0
  41. package/src/dialogs/ProvidersModelsTab.ts +212 -0
  42. package/src/dialogs/SessionListDialog.ts +157 -0
  43. package/src/dialogs/SettingsDialog.ts +216 -0
  44. package/src/index.ts +115 -0
  45. package/src/prompts/prompts.ts +282 -0
  46. package/src/storage/app-storage.ts +60 -0
  47. package/src/storage/backends/indexeddb-storage-backend.ts +193 -0
  48. package/src/storage/store.ts +33 -0
  49. package/src/storage/stores/custom-providers-store.ts +62 -0
  50. package/src/storage/stores/provider-keys-store.ts +33 -0
  51. package/src/storage/stores/sessions-store.ts +136 -0
  52. package/src/storage/stores/settings-store.ts +34 -0
  53. package/src/storage/types.ts +206 -0
  54. package/src/tools/artifacts/ArtifactElement.ts +14 -0
  55. package/src/tools/artifacts/ArtifactPill.ts +26 -0
  56. package/src/tools/artifacts/Console.ts +102 -0
  57. package/src/tools/artifacts/DocxArtifact.ts +213 -0
  58. package/src/tools/artifacts/ExcelArtifact.ts +231 -0
  59. package/src/tools/artifacts/GenericArtifact.ts +118 -0
  60. package/src/tools/artifacts/HtmlArtifact.ts +203 -0
  61. package/src/tools/artifacts/ImageArtifact.ts +116 -0
  62. package/src/tools/artifacts/MarkdownArtifact.ts +83 -0
  63. package/src/tools/artifacts/PdfArtifact.ts +201 -0
  64. package/src/tools/artifacts/SvgArtifact.ts +82 -0
  65. package/src/tools/artifacts/TextArtifact.ts +148 -0
  66. package/src/tools/artifacts/artifacts-tool-renderer.ts +371 -0
  67. package/src/tools/artifacts/artifacts.ts +713 -0
  68. package/src/tools/artifacts/index.ts +7 -0
  69. package/src/tools/extract-document.ts +271 -0
  70. package/src/tools/index.ts +46 -0
  71. package/src/tools/javascript-repl.ts +316 -0
  72. package/src/tools/renderer-registry.ts +127 -0
  73. package/src/tools/renderers/BashRenderer.ts +52 -0
  74. package/src/tools/renderers/CalculateRenderer.ts +58 -0
  75. package/src/tools/renderers/DefaultRenderer.ts +95 -0
  76. package/src/tools/renderers/GetCurrentTimeRenderer.ts +92 -0
  77. package/src/tools/types.ts +15 -0
  78. package/src/utils/attachment-utils.ts +606 -0
  79. package/src/utils/auth-token.ts +22 -0
  80. package/src/utils/format.ts +42 -0
  81. package/src/utils/i18n.ts +653 -0
  82. package/src/utils/model-discovery.ts +277 -0
  83. package/src/utils/proxy-utils.ts +134 -0
  84. package/src/utils/test-sessions.ts +2324 -0
  85. package/tsconfig.build.json +20 -0
  86. package/tsconfig.json +7 -0
package/README.md ADDED
@@ -0,0 +1,609 @@
1
+ # @oh-my-pi/pi-web-ui
2
+
3
+ Reusable web UI components for building AI chat interfaces powered by [@oh-my-pi/pi-ai](../ai) and [@oh-my-pi/pi-agent-core](../agent).
4
+
5
+ Built with [mini-lit](https://github.com/badlogic/mini-lit) web components and Tailwind CSS v4.
6
+
7
+ ## Features
8
+
9
+ - **Chat UI**: Complete interface with message history, streaming, and tool execution
10
+ - **Tools**: JavaScript REPL, document extraction, and artifacts (HTML, SVG, Markdown, etc.)
11
+ - **Attachments**: PDF, DOCX, XLSX, PPTX, images with preview and text extraction
12
+ - **Artifacts**: Interactive HTML, SVG, Markdown with sandboxed execution
13
+ - **Storage**: IndexedDB-backed storage for sessions, API keys, and settings
14
+ - **CORS Proxy**: Automatic proxy handling for browser environments
15
+ - **Custom Providers**: Support for Ollama, LM Studio, vLLM, and OpenAI-compatible APIs
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @oh-my-pi/pi-web-ui @oh-my-pi/pi-agent-core @oh-my-pi/pi-ai
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ See the [example](./example) directory for a complete working application.
26
+
27
+ ```typescript
28
+ import { Agent } from "@oh-my-pi/pi-agent-core";
29
+ import { getModel } from "@oh-my-pi/pi-ai";
30
+ import {
31
+ ChatPanel,
32
+ AppStorage,
33
+ IndexedDBStorageBackend,
34
+ ProviderKeysStore,
35
+ SessionsStore,
36
+ SettingsStore,
37
+ setAppStorage,
38
+ defaultConvertToLlm,
39
+ ApiKeyPromptDialog,
40
+ } from "@oh-my-pi/pi-web-ui";
41
+ import "@oh-my-pi/pi-web-ui/app.css";
42
+
43
+ // Set up storage
44
+ const settings = new SettingsStore();
45
+ const providerKeys = new ProviderKeysStore();
46
+ const sessions = new SessionsStore();
47
+
48
+ const backend = new IndexedDBStorageBackend({
49
+ dbName: "my-app",
50
+ version: 1,
51
+ stores: [settings.getConfig(), providerKeys.getConfig(), sessions.getConfig(), SessionsStore.getMetadataConfig()],
52
+ });
53
+
54
+ settings.setBackend(backend);
55
+ providerKeys.setBackend(backend);
56
+ sessions.setBackend(backend);
57
+
58
+ const storage = new AppStorage(settings, providerKeys, sessions, undefined, backend);
59
+ setAppStorage(storage);
60
+
61
+ // Create agent
62
+ const agent = new Agent({
63
+ initialState: {
64
+ systemPrompt: "You are a helpful assistant.",
65
+ model: getModel("anthropic", "claude-sonnet-4-5-20250929"),
66
+ thinkingLevel: "off",
67
+ messages: [],
68
+ tools: [],
69
+ },
70
+ convertToLlm: defaultConvertToLlm,
71
+ });
72
+
73
+ // Create chat panel
74
+ const chatPanel = new ChatPanel();
75
+ await chatPanel.setAgent(agent, {
76
+ onApiKeyRequired: (provider) => ApiKeyPromptDialog.prompt(provider),
77
+ });
78
+
79
+ document.body.appendChild(chatPanel);
80
+ ```
81
+
82
+ ## Architecture
83
+
84
+ ```
85
+ ┌─────────────────────────────────────────────────────┐
86
+ │ ChatPanel │
87
+ │ ┌─────────────────────┐ ┌─────────────────────┐ │
88
+ │ │ AgentInterface │ │ ArtifactsPanel │ │
89
+ │ │ (messages, input) │ │ (HTML, SVG, MD) │ │
90
+ │ └─────────────────────┘ └─────────────────────┘ │
91
+ └─────────────────────────────────────────────────────┘
92
+
93
+
94
+ ┌─────────────────────────────────────────────────────┐
95
+ │ Agent (from pi-agent-core) │
96
+ │ - State management (messages, model, tools) │
97
+ │ - Event emission (agent_start, message_update, ...) │
98
+ │ - Tool execution │
99
+ └─────────────────────────────────────────────────────┘
100
+
101
+
102
+ ┌─────────────────────────────────────────────────────┐
103
+ │ AppStorage │
104
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
105
+ │ │ Settings │ │ Provider │ │ Sessions │ │
106
+ │ │ Store │ │Keys Store│ │ Store │ │
107
+ │ └──────────┘ └──────────┘ └──────────┘ │
108
+ │ │ │
109
+ │ IndexedDBStorageBackend │
110
+ └─────────────────────────────────────────────────────┘
111
+ ```
112
+
113
+ ## Components
114
+
115
+ ### ChatPanel
116
+
117
+ High-level chat interface with built-in artifacts panel.
118
+
119
+ ```typescript
120
+ const chatPanel = new ChatPanel();
121
+ await chatPanel.setAgent(agent, {
122
+ // Prompt for API key when needed
123
+ onApiKeyRequired: async (provider) => ApiKeyPromptDialog.prompt(provider),
124
+
125
+ // Hook before sending messages
126
+ onBeforeSend: async () => {
127
+ /* save draft, etc. */
128
+ },
129
+
130
+ // Handle cost display click
131
+ onCostClick: () => {
132
+ /* show cost breakdown */
133
+ },
134
+
135
+ // Custom sandbox URL for browser extensions
136
+ sandboxUrlProvider: () => chrome.runtime.getURL("sandbox.html"),
137
+
138
+ // Add custom tools
139
+ toolsFactory: (agent, agentInterface, artifactsPanel, runtimeProvidersFactory) => {
140
+ const replTool = createJavaScriptReplTool();
141
+ replTool.runtimeProvidersFactory = runtimeProvidersFactory;
142
+ return [replTool];
143
+ },
144
+ });
145
+ ```
146
+
147
+ ### AgentInterface
148
+
149
+ Lower-level chat interface for custom layouts.
150
+
151
+ ```typescript
152
+ const chat = document.createElement("agent-interface") as AgentInterface;
153
+ chat.session = agent;
154
+ chat.enableAttachments = true;
155
+ chat.enableModelSelector = true;
156
+ chat.enableThinkingSelector = true;
157
+ chat.onApiKeyRequired = async (provider) => {
158
+ /* ... */
159
+ };
160
+ chat.onBeforeSend = async () => {
161
+ /* ... */
162
+ };
163
+ ```
164
+
165
+ Properties:
166
+
167
+ - `session`: Agent instance
168
+ - `enableAttachments`: Show attachment button (default: true)
169
+ - `enableModelSelector`: Show model selector (default: true)
170
+ - `enableThinkingSelector`: Show thinking level selector (default: true)
171
+ - `showThemeToggle`: Show theme toggle (default: false)
172
+
173
+ ### Agent (from pi-agent-core)
174
+
175
+ ```typescript
176
+ import { Agent } from '@oh-my-pi/pi-agent-core';
177
+
178
+ const agent = new Agent({
179
+ initialState: {
180
+ model: getModel('anthropic', 'claude-sonnet-4-5-20250929'),
181
+ systemPrompt: 'You are helpful.',
182
+ thinkingLevel: 'off',
183
+ messages: [],
184
+ tools: [],
185
+ },
186
+ convertToLlm: defaultConvertToLlm,
187
+ });
188
+
189
+ // Events
190
+ agent.subscribe((event) => {
191
+ switch (event.type) {
192
+ case 'agent_start': // Agent loop started
193
+ case 'agent_end': // Agent loop finished
194
+ case 'turn_start': // LLM call started
195
+ case 'turn_end': // LLM call finished
196
+ case 'message_start':
197
+ case 'message_update': // Streaming update
198
+ case 'message_end':
199
+ break;
200
+ }
201
+ });
202
+
203
+ // Send message
204
+ await agent.prompt('Hello!');
205
+ await agent.prompt({ role: 'user-with-attachments', content: 'Check this', attachments, timestamp: Date.now() });
206
+
207
+ // Control
208
+ agent.abort();
209
+ agent.setModel(newModel);
210
+ agent.setThinkingLevel('medium');
211
+ agent.setTools([...]);
212
+ agent.queueMessage(customMessage);
213
+ ```
214
+
215
+ ## Message Types
216
+
217
+ ### UserMessageWithAttachments
218
+
219
+ User message with file attachments:
220
+
221
+ ```typescript
222
+ const message: UserMessageWithAttachments = {
223
+ role: "user-with-attachments",
224
+ content: "Analyze this document",
225
+ attachments: [pdfAttachment],
226
+ timestamp: Date.now(),
227
+ };
228
+
229
+ // Type guard
230
+ if (isUserMessageWithAttachments(msg)) {
231
+ console.log(msg.attachments);
232
+ }
233
+ ```
234
+
235
+ ### ArtifactMessage
236
+
237
+ For session persistence of artifacts:
238
+
239
+ ```typescript
240
+ const artifact: ArtifactMessage = {
241
+ role: "artifact",
242
+ action: "create", // or 'update', 'delete'
243
+ filename: "chart.html",
244
+ content: "<div>...</div>",
245
+ timestamp: new Date().toISOString(),
246
+ };
247
+
248
+ // Type guard
249
+ if (isArtifactMessage(msg)) {
250
+ console.log(msg.filename);
251
+ }
252
+ ```
253
+
254
+ ### Custom Message Types
255
+
256
+ Extend via declaration merging:
257
+
258
+ ```typescript
259
+ interface SystemNotification {
260
+ role: "system-notification";
261
+ message: string;
262
+ level: "info" | "warning" | "error";
263
+ timestamp: string;
264
+ }
265
+
266
+ declare module "@oh-my-pi/pi-agent-core" {
267
+ interface CustomAgentMessages {
268
+ "system-notification": SystemNotification;
269
+ }
270
+ }
271
+
272
+ // Register renderer
273
+ registerMessageRenderer("system-notification", {
274
+ render: (msg) => html`<div class="alert">${msg.message}</div>`,
275
+ });
276
+
277
+ // Extend convertToLlm
278
+ function myConvertToLlm(messages: AgentMessage[]): Message[] {
279
+ const processed = messages.map((m) => {
280
+ if (m.role === "system-notification") {
281
+ return { role: "user", content: `<system>${m.message}</system>`, timestamp: Date.now() };
282
+ }
283
+ return m;
284
+ });
285
+ return defaultConvertToLlm(processed);
286
+ }
287
+ ```
288
+
289
+ ## Message Transformer
290
+
291
+ `convertToLlm` transforms app messages to LLM-compatible format:
292
+
293
+ ```typescript
294
+ import { defaultConvertToLlm, convertAttachments } from "@oh-my-pi/pi-web-ui";
295
+
296
+ // defaultConvertToLlm handles:
297
+ // - UserMessageWithAttachments → user message with image/text content blocks
298
+ // - ArtifactMessage → filtered out (UI-only)
299
+ // - Standard messages (user, assistant, toolResult) → passed through
300
+ ```
301
+
302
+ ## Tools
303
+
304
+ ### JavaScript REPL
305
+
306
+ Execute JavaScript in a sandboxed browser environment:
307
+
308
+ ```typescript
309
+ import { createJavaScriptReplTool } from "@oh-my-pi/pi-web-ui";
310
+
311
+ const replTool = createJavaScriptReplTool();
312
+
313
+ // Configure runtime providers for artifact/attachment access
314
+ replTool.runtimeProvidersFactory = () => [
315
+ new AttachmentsRuntimeProvider(attachments),
316
+ new ArtifactsRuntimeProvider(artifactsPanel, agent, true), // read-write
317
+ ];
318
+
319
+ agent.setTools([replTool]);
320
+ ```
321
+
322
+ ### Extract Document
323
+
324
+ Extract text from documents at URLs:
325
+
326
+ ```typescript
327
+ import { createExtractDocumentTool } from "@oh-my-pi/pi-web-ui";
328
+
329
+ const extractTool = createExtractDocumentTool();
330
+ extractTool.corsProxyUrl = "https://corsproxy.io/?";
331
+
332
+ agent.setTools([extractTool]);
333
+ ```
334
+
335
+ ### Artifacts Tool
336
+
337
+ Built into ArtifactsPanel, supports: HTML, SVG, Markdown, text, JSON, images, PDF, DOCX, XLSX.
338
+
339
+ ```typescript
340
+ const artifactsPanel = new ArtifactsPanel();
341
+ artifactsPanel.agent = agent;
342
+
343
+ // The tool is available as artifactsPanel.tool
344
+ agent.setTools([artifactsPanel.tool]);
345
+ ```
346
+
347
+ ### Custom Tool Renderers
348
+
349
+ ```typescript
350
+ import { registerToolRenderer, type ToolRenderer } from "@oh-my-pi/pi-web-ui";
351
+
352
+ const myRenderer: ToolRenderer = {
353
+ render(params, result, isStreaming) {
354
+ return {
355
+ content: html`<div>...</div>`,
356
+ isCustom: false, // true = no card wrapper
357
+ };
358
+ },
359
+ };
360
+
361
+ registerToolRenderer("my_tool", myRenderer);
362
+ ```
363
+
364
+ ## Storage
365
+
366
+ ### Setup
367
+
368
+ ```typescript
369
+ import {
370
+ AppStorage,
371
+ IndexedDBStorageBackend,
372
+ SettingsStore,
373
+ ProviderKeysStore,
374
+ SessionsStore,
375
+ CustomProvidersStore,
376
+ setAppStorage,
377
+ getAppStorage,
378
+ } from "@oh-my-pi/pi-web-ui";
379
+
380
+ // Create stores
381
+ const settings = new SettingsStore();
382
+ const providerKeys = new ProviderKeysStore();
383
+ const sessions = new SessionsStore();
384
+ const customProviders = new CustomProvidersStore();
385
+
386
+ // Create backend with all store configs
387
+ const backend = new IndexedDBStorageBackend({
388
+ dbName: "my-app",
389
+ version: 1,
390
+ stores: [
391
+ settings.getConfig(),
392
+ providerKeys.getConfig(),
393
+ sessions.getConfig(),
394
+ SessionsStore.getMetadataConfig(),
395
+ customProviders.getConfig(),
396
+ ],
397
+ });
398
+
399
+ // Wire stores to backend
400
+ settings.setBackend(backend);
401
+ providerKeys.setBackend(backend);
402
+ sessions.setBackend(backend);
403
+ customProviders.setBackend(backend);
404
+
405
+ // Create and set global storage
406
+ const storage = new AppStorage(settings, providerKeys, sessions, customProviders, backend);
407
+ setAppStorage(storage);
408
+ ```
409
+
410
+ ### SettingsStore
411
+
412
+ Key-value settings:
413
+
414
+ ```typescript
415
+ await storage.settings.set("proxy.enabled", true);
416
+ await storage.settings.set("proxy.url", "https://proxy.example.com");
417
+ const enabled = await storage.settings.get<boolean>("proxy.enabled");
418
+ ```
419
+
420
+ ### ProviderKeysStore
421
+
422
+ API keys by provider:
423
+
424
+ ```typescript
425
+ await storage.providerKeys.set("anthropic", "sk-ant-...");
426
+ const key = await storage.providerKeys.get("anthropic");
427
+ const providers = await storage.providerKeys.list();
428
+ ```
429
+
430
+ ### SessionsStore
431
+
432
+ Chat sessions with metadata:
433
+
434
+ ```typescript
435
+ // Save session
436
+ await storage.sessions.save(sessionData, metadata);
437
+
438
+ // Load session
439
+ const data = await storage.sessions.get(sessionId);
440
+ const metadata = await storage.sessions.getMetadata(sessionId);
441
+
442
+ // List sessions (sorted by lastModified)
443
+ const allMetadata = await storage.sessions.getAllMetadata();
444
+
445
+ // Update title
446
+ await storage.sessions.updateTitle(sessionId, "New Title");
447
+
448
+ // Delete
449
+ await storage.sessions.delete(sessionId);
450
+ ```
451
+
452
+ ### CustomProvidersStore
453
+
454
+ Custom LLM providers:
455
+
456
+ ```typescript
457
+ const provider: CustomProvider = {
458
+ id: crypto.randomUUID(),
459
+ name: "My Ollama",
460
+ type: "ollama",
461
+ baseUrl: "http://localhost:11434",
462
+ };
463
+
464
+ await storage.customProviders.set(provider);
465
+ const all = await storage.customProviders.getAll();
466
+ ```
467
+
468
+ ## Attachments
469
+
470
+ Load and process files:
471
+
472
+ ```typescript
473
+ import { loadAttachment, type Attachment } from "@oh-my-pi/pi-web-ui";
474
+
475
+ // From File input
476
+ const file = inputElement.files[0];
477
+ const attachment = await loadAttachment(file);
478
+
479
+ // From URL
480
+ const attachment = await loadAttachment("https://example.com/doc.pdf");
481
+
482
+ // From ArrayBuffer
483
+ const attachment = await loadAttachment(arrayBuffer, "document.pdf");
484
+
485
+ // Attachment structure
486
+ interface Attachment {
487
+ id: string;
488
+ type: "image" | "document";
489
+ fileName: string;
490
+ mimeType: string;
491
+ size: number;
492
+ content: string; // base64 encoded
493
+ extractedText?: string; // For documents
494
+ preview?: string; // base64 preview image
495
+ }
496
+ ```
497
+
498
+ Supported formats: PDF, DOCX, XLSX, PPTX, images, text files.
499
+
500
+ ## CORS Proxy
501
+
502
+ For browser environments with CORS restrictions:
503
+
504
+ ```typescript
505
+ import { createStreamFn, shouldUseProxyForProvider, isCorsError } from "@oh-my-pi/pi-web-ui";
506
+
507
+ // AgentInterface auto-configures proxy from settings
508
+ // For manual setup:
509
+ agent.streamFn = createStreamFn(async () => {
510
+ const enabled = await storage.settings.get<boolean>("proxy.enabled");
511
+ return enabled ? await storage.settings.get<string>("proxy.url") : undefined;
512
+ });
513
+
514
+ // Providers requiring proxy:
515
+ // - zai: always
516
+ // - anthropic: only OAuth tokens (sk-ant-oat-*)
517
+ ```
518
+
519
+ ## Dialogs
520
+
521
+ ### SettingsDialog
522
+
523
+ ```typescript
524
+ import { SettingsDialog, ProvidersModelsTab, ProxyTab, ApiKeysTab } from "@oh-my-pi/pi-web-ui";
525
+
526
+ SettingsDialog.open([
527
+ new ProvidersModelsTab(), // Custom providers + model list
528
+ new ProxyTab(), // CORS proxy settings
529
+ new ApiKeysTab(), // API keys per provider
530
+ ]);
531
+ ```
532
+
533
+ ### SessionListDialog
534
+
535
+ ```typescript
536
+ import { SessionListDialog } from "@oh-my-pi/pi-web-ui";
537
+
538
+ SessionListDialog.open(
539
+ async (sessionId) => {
540
+ /* load session */
541
+ },
542
+ (deletedId) => {
543
+ /* handle deletion */
544
+ }
545
+ );
546
+ ```
547
+
548
+ ### ApiKeyPromptDialog
549
+
550
+ ```typescript
551
+ import { ApiKeyPromptDialog } from "@oh-my-pi/pi-web-ui";
552
+
553
+ const success = await ApiKeyPromptDialog.prompt("anthropic");
554
+ ```
555
+
556
+ ### ModelSelector
557
+
558
+ ```typescript
559
+ import { ModelSelector } from "@oh-my-pi/pi-web-ui";
560
+
561
+ ModelSelector.open(currentModel, (selectedModel) => {
562
+ agent.setModel(selectedModel);
563
+ });
564
+ ```
565
+
566
+ ## Styling
567
+
568
+ Import the pre-built CSS:
569
+
570
+ ```typescript
571
+ import "@oh-my-pi/pi-web-ui/app.css";
572
+ ```
573
+
574
+ Or use Tailwind with custom config:
575
+
576
+ ```css
577
+ @import "@mariozechner/mini-lit/themes/claude.css";
578
+ @tailwind base;
579
+ @tailwind components;
580
+ @tailwind utilities;
581
+ ```
582
+
583
+ ## Internationalization
584
+
585
+ ```typescript
586
+ import { i18n, setLanguage, translations } from "@oh-my-pi/pi-web-ui";
587
+
588
+ // Add translations
589
+ translations.de = {
590
+ "Loading...": "Laden...",
591
+ "No sessions yet": "Noch keine Sitzungen",
592
+ };
593
+
594
+ setLanguage("de");
595
+ console.log(i18n("Loading...")); // "Laden..."
596
+ ```
597
+
598
+ ## Examples
599
+
600
+ - [example/](./example) - Complete web app with sessions, artifacts, custom messages
601
+ - [sitegeist](https://sitegeist.ai) - Browser extension using pi-web-ui
602
+
603
+ ## Known Issues
604
+
605
+ - **PersistentStorageDialog**: Currently broken
606
+
607
+ ## License
608
+
609
+ MIT
@@ -0,0 +1,61 @@
1
+ # Pi Web UI - Example
2
+
3
+ This is a minimal example showing how to use `@oh-my-pi/pi-web-ui` in a web application.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ npm install
9
+ ```
10
+
11
+ ## Development
12
+
13
+ ```bash
14
+ npm run dev
15
+ ```
16
+
17
+ Open [http://localhost:5173](http://localhost:5173) in your browser.
18
+
19
+ ## What's Included
20
+
21
+ This example demonstrates:
22
+
23
+ - **ChatPanel** - The main chat interface component
24
+ - **System Prompt** - Custom configuration for the AI assistant
25
+ - **Tools** - JavaScript REPL and artifacts tool
26
+
27
+ ## Configuration
28
+
29
+ ### API Keys
30
+
31
+ The example uses **Direct Mode** by default, which means it calls AI provider APIs directly from the browser.
32
+
33
+ To use the chat:
34
+
35
+ 1. Click the settings icon (⚙️) in the chat interface
36
+ 2. Click "Manage API Keys"
37
+ 3. Add your API key for your preferred provider:
38
+ - **Anthropic**: Get a key from [console.anthropic.com](https://console.anthropic.com/)
39
+ - **OpenAI**: Get a key from [platform.openai.com](https://platform.openai.com/)
40
+ - **Google**: Get a key from [makersuite.google.com](https://makersuite.google.com/)
41
+
42
+ API keys are stored in your browser's localStorage and never sent to any server except the AI provider's API.
43
+
44
+ ## Project Structure
45
+
46
+ ```
47
+ example/
48
+ ├── src/
49
+ │ ├── main.ts # Main application entry point
50
+ │ └── app.css # Tailwind CSS configuration
51
+ ├── index.html # HTML entry point
52
+ ├── package.json # Dependencies
53
+ ├── vite.config.ts # Vite configuration
54
+ └── tsconfig.json # TypeScript configuration
55
+ ```
56
+
57
+ ## Learn More
58
+
59
+ - [Pi Web UI Documentation](../README.md)
60
+ - [Pi AI Documentation](../../ai/README.md)
61
+ - [Mini Lit Documentation](https://github.com/badlogic/mini-lit)
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Pi Web UI - Example</title>
7
+ <meta name="description" content="Example usage of @oh-my-pi/pi-web-ui - Reusable AI chat interface" />
8
+ </head>
9
+ <body class="bg-background">
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "pi-web-ui-example",
3
+ "version": "1.20.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "clean": "rm -rf dist",
11
+ "check": "biome check --write . && tsgo --noEmit",
12
+ "fix": "biome check --write --unsafe ."
13
+ },
14
+ "dependencies": {
15
+ "@mariozechner/mini-lit": "^0.2.0",
16
+ "@oh-my-pi/pi-ai": "workspace:*",
17
+ "@oh-my-pi/pi-web-ui": "workspace:*",
18
+ "@tailwindcss/vite": "^4.1.17",
19
+ "lit": "^3.3.1",
20
+ "lucide": "^0.544.0"
21
+ },
22
+ "devDependencies": {
23
+ "vite": "^7.1.6"
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ @import "../../dist/app.css";