@oh-my-pi/pi-web-ui 1.337.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 +96 -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 +24 -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 +57 -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 +472 -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 +2357 -0
  85. package/tsconfig.build.json +20 -0
  86. package/tsconfig.json +7 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,96 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.31.1] - 2026-01-02
6
+
7
+ ## [0.31.0] - 2026-01-02
8
+
9
+ ### Breaking Changes
10
+
11
+ - **Agent class moved to `@oh-my-pi/pi-agent-core`**: The `Agent` class, `AgentState`, and related types are no longer exported from this package. Import them from `@oh-my-pi/pi-agent-core` instead.
12
+
13
+ - **Transport abstraction removed**: `ProviderTransport`, `AppTransport`, `AgentTransport` interface, and related types have been removed. The `Agent` class now uses `streamFn` for custom streaming.
14
+
15
+ - **`AppMessage` renamed to `AgentMessage`**: Now imported from `@oh-my-pi/pi-agent-core`. Custom message types use declaration merging on `CustomAgentMessages` interface.
16
+
17
+ - **`UserMessageWithAttachments` is now a custom message type**: Has `role: "user-with-attachments"` instead of `role: "user"`. Use `isUserMessageWithAttachments()` type guard.
18
+
19
+ - **`CustomMessages` interface removed**: Use declaration merging on `CustomAgentMessages` from `@oh-my-pi/pi-agent-core` instead.
20
+
21
+ - **`agent.appendMessage()` removed**: Use `agent.queueMessage()` instead.
22
+
23
+ - **Agent event types changed**: `AgentInterface` now handles new event types from `@oh-my-pi/pi-agent-core`: `message_start`, `message_end`, `message_update`, `turn_start`, `turn_end`, `agent_start`, `agent_end`.
24
+
25
+ ### Added
26
+
27
+ - **`defaultConvertToLlm`**: Default message transformer that handles `UserMessageWithAttachments` and `ArtifactMessage`. Apps can extend this for custom message types.
28
+
29
+ - **`convertAttachments`**: Utility to convert `Attachment[]` to LLM content blocks (images and extracted document text).
30
+
31
+ - **`isUserMessageWithAttachments` / `isArtifactMessage`**: Type guard functions for custom message types.
32
+
33
+ - **`createStreamFn`**: Creates a stream function with CORS proxy support. Reads proxy settings on each call for dynamic configuration.
34
+
35
+ - **Default `streamFn` and `getApiKey`**: `AgentInterface` now sets sensible defaults if not provided:
36
+
37
+ - `streamFn`: Uses `createStreamFn` with proxy settings from storage
38
+ - `getApiKey`: Reads from `providerKeys` storage
39
+
40
+ - **Proxy utilities exported**: `applyProxyIfNeeded`, `shouldUseProxyForProvider`, `isCorsError`, `createStreamFn`
41
+
42
+ ### Removed
43
+
44
+ - `Agent` class (moved to `@oh-my-pi/pi-agent-core`)
45
+ - `ProviderTransport` class
46
+ - `AppTransport` class
47
+ - `AgentTransport` interface
48
+ - `AgentRunConfig` type
49
+ - `ProxyAssistantMessageEvent` type
50
+ - `test-sessions.ts` example file
51
+
52
+ ### Migration Guide
53
+
54
+ **Before (0.30.x):**
55
+
56
+ ```typescript
57
+ import { Agent, ProviderTransport, type AppMessage } from '@oh-my-pi/pi-web-ui';
58
+
59
+ const agent = new Agent({
60
+ transport: new ProviderTransport(),
61
+ messageTransformer: (messages: AppMessage[]) => messages.filter(...)
62
+ });
63
+ ```
64
+
65
+ **After:**
66
+
67
+ ```typescript
68
+ import { Agent, type AgentMessage } from "@oh-my-pi/pi-agent-core";
69
+ import { defaultConvertToLlm } from "@oh-my-pi/pi-web-ui";
70
+
71
+ const agent = new Agent({
72
+ convertToLlm: (messages: AgentMessage[]) => {
73
+ // Extend defaultConvertToLlm for custom types
74
+ return defaultConvertToLlm(messages);
75
+ },
76
+ });
77
+ // AgentInterface will set streamFn and getApiKey defaults automatically
78
+ ```
79
+
80
+ **Custom message types:**
81
+
82
+ ```typescript
83
+ // Before: declaration merging on CustomMessages
84
+ declare module "@oh-my-pi/pi-web-ui" {
85
+ interface CustomMessages {
86
+ "my-message": MyMessage;
87
+ }
88
+ }
89
+
90
+ // After: declaration merging on CustomAgentMessages
91
+ declare module "@oh-my-pi/pi-agent-core" {
92
+ interface CustomAgentMessages {
93
+ "my-message": MyMessage;
94
+ }
95
+ }
96
+ ```
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