@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
@@ -0,0 +1,216 @@
1
+ import { i18n } from "@mariozechner/mini-lit";
2
+ import { Dialog, DialogContent, DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
3
+ import { Input } from "@mariozechner/mini-lit/dist/Input.js";
4
+ import { Label } from "@mariozechner/mini-lit/dist/Label.js";
5
+ import { Switch } from "@mariozechner/mini-lit/dist/Switch.js";
6
+ import { getProviders } from "@oh-my-pi/pi-ai";
7
+ import { html, LitElement, type TemplateResult } from "lit";
8
+ import { customElement, property, state } from "lit/decorators.js";
9
+ import "../components/ProviderKeyInput.js";
10
+ import { getAppStorage } from "../storage/app-storage.js";
11
+
12
+ // Base class for settings tabs
13
+ export abstract class SettingsTab extends LitElement {
14
+ abstract getTabName(): string;
15
+
16
+ protected createRenderRoot() {
17
+ return this;
18
+ }
19
+ }
20
+
21
+ // API Keys Tab
22
+ @customElement("api-keys-tab")
23
+ export class ApiKeysTab extends SettingsTab {
24
+ getTabName(): string {
25
+ return i18n("API Keys");
26
+ }
27
+
28
+ render(): TemplateResult {
29
+ const providers = getProviders();
30
+
31
+ return html`
32
+ <div class="flex flex-col gap-6">
33
+ <p class="text-sm text-muted-foreground">
34
+ ${i18n("Configure API keys for LLM providers. Keys are stored locally in your browser.")}
35
+ </p>
36
+ ${providers.map((provider) => html`<provider-key-input .provider=${provider}></provider-key-input>`)}
37
+ </div>
38
+ `;
39
+ }
40
+ }
41
+
42
+ // Proxy Tab
43
+ @customElement("proxy-tab")
44
+ export class ProxyTab extends SettingsTab {
45
+ @state() private proxyEnabled = false;
46
+ @state() private proxyUrl = "http://localhost:3001";
47
+
48
+ override async connectedCallback() {
49
+ super.connectedCallback();
50
+ // Load proxy settings when tab is connected
51
+ try {
52
+ const storage = getAppStorage();
53
+ const enabled = await storage.settings.get<boolean>("proxy.enabled");
54
+ const url = await storage.settings.get<string>("proxy.url");
55
+
56
+ if (enabled !== null) this.proxyEnabled = enabled;
57
+ if (url !== null) this.proxyUrl = url;
58
+ } catch (error) {
59
+ console.error("Failed to load proxy settings:", error);
60
+ }
61
+ }
62
+
63
+ private async saveProxySettings() {
64
+ try {
65
+ const storage = getAppStorage();
66
+ await storage.settings.set("proxy.enabled", this.proxyEnabled);
67
+ await storage.settings.set("proxy.url", this.proxyUrl);
68
+ } catch (error) {
69
+ console.error("Failed to save proxy settings:", error);
70
+ }
71
+ }
72
+
73
+ getTabName(): string {
74
+ return i18n("Proxy");
75
+ }
76
+
77
+ render(): TemplateResult {
78
+ return html`
79
+ <div class="flex flex-col gap-4">
80
+ <p class="text-sm text-muted-foreground">
81
+ ${i18n(
82
+ "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.",
83
+ )}
84
+ </p>
85
+
86
+ <div class="flex items-center justify-between">
87
+ <span class="text-sm font-medium text-foreground">${i18n("Use CORS Proxy")}</span>
88
+ ${Switch({
89
+ checked: this.proxyEnabled,
90
+ onChange: (checked: boolean) => {
91
+ this.proxyEnabled = checked;
92
+ this.saveProxySettings();
93
+ },
94
+ })}
95
+ </div>
96
+
97
+ <div class="space-y-2">
98
+ ${Label({ children: i18n("Proxy URL") })}
99
+ ${Input({
100
+ type: "text",
101
+ value: this.proxyUrl,
102
+ disabled: !this.proxyEnabled,
103
+ onInput: (e) => {
104
+ this.proxyUrl = (e.target as HTMLInputElement).value;
105
+ },
106
+ onChange: () => this.saveProxySettings(),
107
+ })}
108
+ <p class="text-xs text-muted-foreground">
109
+ ${i18n("Format: The proxy must accept requests as <proxy-url>/?url=<target-url>")}
110
+ </p>
111
+ </div>
112
+ </div>
113
+ `;
114
+ }
115
+ }
116
+
117
+ @customElement("settings-dialog")
118
+ export class SettingsDialog extends LitElement {
119
+ @property({ type: Array, attribute: false }) tabs: SettingsTab[] = [];
120
+ @state() private isOpen = false;
121
+ @state() private activeTabIndex = 0;
122
+
123
+ protected createRenderRoot() {
124
+ return this;
125
+ }
126
+
127
+ static async open(tabs: SettingsTab[]) {
128
+ const dialog = new SettingsDialog();
129
+ dialog.tabs = tabs;
130
+ dialog.isOpen = true;
131
+ document.body.appendChild(dialog);
132
+ }
133
+
134
+ private setActiveTab(index: number) {
135
+ this.activeTabIndex = index;
136
+ }
137
+
138
+ private renderSidebarItem(tab: SettingsTab, index: number): TemplateResult {
139
+ const isActive = this.activeTabIndex === index;
140
+ return html`
141
+ <button
142
+ class="w-full text-left px-4 py-3 rounded-md transition-colors ${
143
+ isActive
144
+ ? "bg-secondary text-foreground font-medium"
145
+ : "text-muted-foreground hover:bg-secondary/50 hover:text-foreground"
146
+ }"
147
+ @click=${() => this.setActiveTab(index)}
148
+ >
149
+ ${tab.getTabName()}
150
+ </button>
151
+ `;
152
+ }
153
+
154
+ private renderMobileTab(tab: SettingsTab, index: number): TemplateResult {
155
+ const isActive = this.activeTabIndex === index;
156
+ return html`
157
+ <button
158
+ class="px-3 py-2 text-sm font-medium transition-colors ${
159
+ isActive ? "border-b-2 border-primary text-foreground" : "text-muted-foreground hover:text-foreground"
160
+ }"
161
+ @click=${() => this.setActiveTab(index)}
162
+ >
163
+ ${tab.getTabName()}
164
+ </button>
165
+ `;
166
+ }
167
+
168
+ render() {
169
+ if (this.tabs.length === 0) {
170
+ return html``;
171
+ }
172
+
173
+ return Dialog({
174
+ isOpen: this.isOpen,
175
+ onClose: () => {
176
+ this.isOpen = false;
177
+ this.remove();
178
+ },
179
+ width: "min(1000px, 90vw)",
180
+ height: "min(800px, 90vh)",
181
+ backdropClassName: "bg-black/50 backdrop-blur-sm",
182
+ children: html`
183
+ ${DialogContent({
184
+ className: "h-full p-6",
185
+ children: html`
186
+ <div class="flex flex-col h-full overflow-hidden">
187
+ <!-- Header -->
188
+ <div class="pb-4 flex-shrink-0">${DialogHeader({ title: i18n("Settings") })}</div>
189
+
190
+ <!-- Mobile Tabs -->
191
+ <div class="md:hidden flex flex-shrink-0 pb-4">
192
+ ${this.tabs.map((tab, index) => this.renderMobileTab(tab, index))}
193
+ </div>
194
+
195
+ <!-- Layout -->
196
+ <div class="flex flex-1 overflow-hidden">
197
+ <!-- Sidebar (desktop only) -->
198
+ <div class="hidden md:block w-64 flex-shrink-0 space-y-1">
199
+ ${this.tabs.map((tab, index) => this.renderSidebarItem(tab, index))}
200
+ </div>
201
+
202
+ <!-- Content -->
203
+ <div class="flex-1 overflow-y-auto md:pl-6">
204
+ ${this.tabs.map(
205
+ (tab, index) =>
206
+ html`<div style="display: ${this.activeTabIndex === index ? "block" : "none"}">${tab}</div>`,
207
+ )}
208
+ </div>
209
+ </div>
210
+ </div>
211
+ `,
212
+ })}
213
+ `,
214
+ });
215
+ }
216
+ }
package/src/index.ts ADDED
@@ -0,0 +1,115 @@
1
+ // Main chat interface
2
+
3
+ export type { Agent, AgentMessage, AgentState, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
4
+ export type { Model } from "@oh-my-pi/pi-ai";
5
+ export { ChatPanel } from "./ChatPanel.js";
6
+ // Components
7
+ export { AgentInterface } from "./components/AgentInterface.js";
8
+ export { AttachmentTile } from "./components/AttachmentTile.js";
9
+ export { ConsoleBlock } from "./components/ConsoleBlock.js";
10
+ export { ExpandableSection } from "./components/ExpandableSection.js";
11
+ export { Input } from "./components/Input.js";
12
+ export { MessageEditor } from "./components/MessageEditor.js";
13
+ export { MessageList } from "./components/MessageList.js";
14
+ // Message components
15
+ export type { ArtifactMessage, UserMessageWithAttachments } from "./components/Messages.js";
16
+ export {
17
+ AssistantMessage,
18
+ convertAttachments,
19
+ defaultConvertToLlm,
20
+ isArtifactMessage,
21
+ isUserMessageWithAttachments,
22
+ ToolMessage,
23
+ UserMessage,
24
+ } from "./components/Messages.js";
25
+ // Message renderer registry
26
+ export {
27
+ getMessageRenderer,
28
+ type MessageRenderer,
29
+ type MessageRole,
30
+ registerMessageRenderer,
31
+ renderMessage,
32
+ } from "./components/message-renderer-registry.js";
33
+ export {
34
+ type SandboxFile,
35
+ SandboxIframe,
36
+ type SandboxResult,
37
+ type SandboxUrlProvider,
38
+ } from "./components/SandboxedIframe.js";
39
+ export { StreamingMessageContainer } from "./components/StreamingMessageContainer.js";
40
+ // Sandbox Runtime Providers
41
+ export { ArtifactsRuntimeProvider } from "./components/sandbox/ArtifactsRuntimeProvider.js";
42
+ export { AttachmentsRuntimeProvider } from "./components/sandbox/AttachmentsRuntimeProvider.js";
43
+ export { type ConsoleLog, ConsoleRuntimeProvider } from "./components/sandbox/ConsoleRuntimeProvider.js";
44
+ export {
45
+ type DownloadableFile,
46
+ FileDownloadRuntimeProvider,
47
+ } from "./components/sandbox/FileDownloadRuntimeProvider.js";
48
+ export { RuntimeMessageBridge } from "./components/sandbox/RuntimeMessageBridge.js";
49
+ export { RUNTIME_MESSAGE_ROUTER } from "./components/sandbox/RuntimeMessageRouter.js";
50
+ export type { SandboxRuntimeProvider } from "./components/sandbox/SandboxRuntimeProvider.js";
51
+ export { ThinkingBlock } from "./components/ThinkingBlock.js";
52
+ export { ApiKeyPromptDialog } from "./dialogs/ApiKeyPromptDialog.js";
53
+ export { AttachmentOverlay } from "./dialogs/AttachmentOverlay.js";
54
+ // Dialogs
55
+ export { ModelSelector } from "./dialogs/ModelSelector.js";
56
+ export { PersistentStorageDialog } from "./dialogs/PersistentStorageDialog.js";
57
+ export { ProvidersModelsTab } from "./dialogs/ProvidersModelsTab.js";
58
+ export { SessionListDialog } from "./dialogs/SessionListDialog.js";
59
+ export { ApiKeysTab, ProxyTab, SettingsDialog, SettingsTab } from "./dialogs/SettingsDialog.js";
60
+ // Prompts
61
+ export {
62
+ ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RO,
63
+ ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RW,
64
+ ATTACHMENTS_RUNTIME_DESCRIPTION,
65
+ } from "./prompts/prompts.js";
66
+ // Storage
67
+ export { AppStorage, getAppStorage, setAppStorage } from "./storage/app-storage.js";
68
+ export { IndexedDBStorageBackend } from "./storage/backends/indexeddb-storage-backend.js";
69
+ export { Store } from "./storage/store.js";
70
+ export type {
71
+ AutoDiscoveryProviderType,
72
+ CustomProvider,
73
+ CustomProviderType,
74
+ } from "./storage/stores/custom-providers-store.js";
75
+ export { CustomProvidersStore } from "./storage/stores/custom-providers-store.js";
76
+ export { ProviderKeysStore } from "./storage/stores/provider-keys-store.js";
77
+ export { SessionsStore } from "./storage/stores/sessions-store.js";
78
+ export { SettingsStore } from "./storage/stores/settings-store.js";
79
+ export type {
80
+ IndexConfig,
81
+ IndexedDBConfig,
82
+ SessionData,
83
+ SessionMetadata,
84
+ StorageBackend,
85
+ StorageTransaction,
86
+ StoreConfig,
87
+ } from "./storage/types.js";
88
+ // Artifacts
89
+ export { ArtifactElement } from "./tools/artifacts/ArtifactElement.js";
90
+ export { ArtifactPill } from "./tools/artifacts/ArtifactPill.js";
91
+ export { type Artifact, ArtifactsPanel, type ArtifactsParams } from "./tools/artifacts/artifacts.js";
92
+ export { ArtifactsToolRenderer } from "./tools/artifacts/artifacts-tool-renderer.js";
93
+ export { HtmlArtifact } from "./tools/artifacts/HtmlArtifact.js";
94
+ export { ImageArtifact } from "./tools/artifacts/ImageArtifact.js";
95
+ export { MarkdownArtifact } from "./tools/artifacts/MarkdownArtifact.js";
96
+ export { SvgArtifact } from "./tools/artifacts/SvgArtifact.js";
97
+ export { TextArtifact } from "./tools/artifacts/TextArtifact.js";
98
+ export { createExtractDocumentTool, extractDocumentTool } from "./tools/extract-document.js";
99
+ // Tools
100
+ export { getToolRenderer, registerToolRenderer, renderTool, setShowJsonMode } from "./tools/index.js";
101
+ export { createJavaScriptReplTool, javascriptReplTool } from "./tools/javascript-repl.js";
102
+ export { renderCollapsibleHeader, renderHeader } from "./tools/renderer-registry.js";
103
+ export { BashRenderer } from "./tools/renderers/BashRenderer.js";
104
+ export { CalculateRenderer } from "./tools/renderers/CalculateRenderer.js";
105
+ // Tool renderers
106
+ export { DefaultRenderer } from "./tools/renderers/DefaultRenderer.js";
107
+ export { GetCurrentTimeRenderer } from "./tools/renderers/GetCurrentTimeRenderer.js";
108
+ export type { ToolRenderer, ToolRenderResult } from "./tools/types.js";
109
+ export type { Attachment } from "./utils/attachment-utils.js";
110
+ // Utils
111
+ export { loadAttachment } from "./utils/attachment-utils.js";
112
+ export { clearAuthToken, getAuthToken } from "./utils/auth-token.js";
113
+ export { formatCost, formatModelCost, formatTokenCount, formatUsage } from "./utils/format.js";
114
+ export { i18n, setLanguage, translations } from "./utils/i18n.js";
115
+ export { applyProxyIfNeeded, createStreamFn, isCorsError, shouldUseProxyForProvider } from "./utils/proxy-utils.js";
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Centralized tool prompts/descriptions.
3
+ * Each prompt is either a string constant or a template function.
4
+ */
5
+
6
+ // ============================================================================
7
+ // JavaScript REPL Tool
8
+ // ============================================================================
9
+
10
+ export const JAVASCRIPT_REPL_TOOL_DESCRIPTION = (runtimeProviderDescriptions: string[]) => `# JavaScript REPL
11
+
12
+ ## Purpose
13
+ Execute JavaScript code in a sandboxed browser environment with full Web APIs.
14
+
15
+ ## When to Use
16
+ - Quick calculations or data transformations
17
+ - Testing JavaScript code snippets in isolation
18
+ - Processing data with libraries (XLSX, CSV, etc.)
19
+ - Creating artifacts from data
20
+
21
+ ## Environment
22
+ - ES2023+ JavaScript (async/await, optional chaining, nullish coalescing, etc.)
23
+ - All browser APIs: DOM, Canvas, WebGL, Fetch, Web Workers, WebSockets, Crypto, etc.
24
+ - Import any npm package: await import('https://esm.run/package-name')
25
+
26
+ ## Common Libraries
27
+ - XLSX: const XLSX = await import('https://esm.run/xlsx');
28
+ - CSV: const Papa = (await import('https://esm.run/papaparse')).default;
29
+ - Chart.js: const Chart = (await import('https://esm.run/chart.js/auto')).default;
30
+ - Three.js: const THREE = await import('https://esm.run/three');
31
+
32
+ ## Persistence between tool calls
33
+ - Objects stored on global scope do not persist between calls.
34
+ - Use artifacts as a key-value JSON object store:
35
+ - Use createOrUpdateArtifact(filename, content) to persist data between calls. JSON objects are auto-stringified.
36
+ - Use listArtifacts() and getArtifact(filename) to read persisted data. JSON files are auto-parsed to objects.
37
+ - Prefer to use a single artifact throughout the session to store intermediate data (e.g. 'data.json').
38
+
39
+ ## Input
40
+ - You have access to the user's attachments via listAttachments(), readTextAttachment(id), and readBinaryAttachment(id)
41
+ - You have access to previously created artifacts via listArtifacts() and getArtifact(filename)
42
+
43
+ ## Output
44
+ - All console.log() calls are captured for you to inspect. The user does not see these logs.
45
+ - Create artifacts for file results (images, JSON, CSV, etc.) which persiste throughout the
46
+ session and are accessible to you and the user.
47
+
48
+ ## Example
49
+ const data = [10, 20, 15, 25];
50
+ const sum = data.reduce((a, b) => a + b, 0);
51
+ const avg = sum / data.length;
52
+ console.log('Sum:', sum, 'Average:', avg);
53
+
54
+ ## Important Notes
55
+ - Graphics: Use fixed dimensions (800x600), NOT window.innerWidth/Height
56
+ - Chart.js: Set options: { responsive: false, animation: false }
57
+ - Three.js: renderer.setSize(800, 600) with matching aspect ratio
58
+
59
+ ## Helper Functions (Automatically Available)
60
+
61
+ These functions are injected into the execution environment and available globally:
62
+
63
+ ${runtimeProviderDescriptions.join("\n\n")}
64
+ `;
65
+
66
+ // ============================================================================
67
+ // Artifacts Tool
68
+ // ============================================================================
69
+
70
+ export const ARTIFACTS_TOOL_DESCRIPTION = (runtimeProviderDescriptions: string[]) => `# Artifacts
71
+
72
+ Create and manage persistent files that live alongside the conversation.
73
+
74
+ ## When to Use - Artifacts Tool vs REPL
75
+
76
+ **Use artifacts tool when YOU are the author:**
77
+ - Writing research summaries, analysis, ideas, documentation
78
+ - Creating markdown notes for user to read
79
+ - Building HTML applications/visualizations that present data
80
+ - Creating HTML artifacts that render charts from programmatically generated data
81
+
82
+ **Use repl + artifact storage functions when CODE processes data:**
83
+ - Scraping workflows that extract and store data
84
+ - Processing CSV/Excel files programmatically
85
+ - Data transformation pipelines
86
+ - Binary file generation requiring libraries (PDF, DOCX)
87
+
88
+ **Pattern: REPL generates data → Artifacts tool creates HTML that visualizes it**
89
+ Example: repl scrapes products → stores products.json → you author dashboard.html that reads products.json and renders Chart.js visualizations
90
+
91
+ ## Input
92
+ - { action: "create", filename: "notes.md", content: "..." } - Create new file
93
+ - { action: "update", filename: "notes.md", old_str: "...", new_str: "..." } - Update part of file (PREFERRED)
94
+ - { action: "rewrite", filename: "notes.md", content: "..." } - Replace entire file (LAST RESORT)
95
+ - { action: "get", filename: "data.json" } - Retrieve file content
96
+ - { action: "delete", filename: "old.csv" } - Delete file
97
+ - { action: "htmlArtifactLogs", filename: "app.html" } - Get console logs from HTML artifact
98
+
99
+ ## Returns
100
+ Depends on action:
101
+ - create/update/rewrite/delete: Success status or error
102
+ - get: File content
103
+ - htmlArtifactLogs: Console logs and errors
104
+
105
+ ## Supported File Types
106
+ ✅ Text-based files you author: .md, .txt, .html, .js, .css, .json, .csv, .svg
107
+ ❌ Binary files requiring libraries (use repl): .pdf, .docx
108
+
109
+ ## Critical - Prefer Update Over Rewrite
110
+ ❌ NEVER: get entire file + rewrite to change small sections
111
+ ✅ ALWAYS: update for targeted edits (token efficient)
112
+ ✅ Ask: Can I describe the change as old_str → new_str? Use update.
113
+
114
+ ---
115
+
116
+ ## HTML Artifacts
117
+
118
+ Interactive HTML applications that can visualize data from other artifacts.
119
+
120
+ ### Data Access
121
+ - Can read artifacts created by repl and user attachments
122
+ - Use to build dashboards, visualizations, interactive tools
123
+ - See Helper Functions section below for available functions
124
+
125
+ ### Requirements
126
+ - Self-contained single file
127
+ - Import ES modules from esm.sh: <script type="module">import X from 'https://esm.sh/pkg';</script>
128
+ - Use Tailwind CDN: <script src="https://cdn.tailwindcss.com"></script>
129
+ - Can embed images from any domain: <img src="https://example.com/image.jpg">
130
+ - MUST set background color explicitly (avoid transparent)
131
+ - Inline CSS or Tailwind utility classes
132
+ - No localStorage/sessionStorage
133
+
134
+ ### Styling
135
+ - Use Tailwind utility classes for clean, functional designs
136
+ - Ensure responsive layout (iframe may be resized)
137
+ - Avoid purple gradients, AI aesthetic clichés, and emojis
138
+
139
+ ### Helper Functions (Automatically Available)
140
+
141
+ These functions are injected into HTML artifact sandbox:
142
+
143
+ ${runtimeProviderDescriptions.join("\n\n")}
144
+ `;
145
+
146
+ // ============================================================================
147
+ // Artifacts Runtime Provider
148
+ // ============================================================================
149
+
150
+ export const ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RW = `
151
+ ### Artifacts Storage
152
+
153
+ Create, read, update, and delete files in artifacts storage.
154
+
155
+ #### When to Use
156
+ - Store intermediate results between tool calls
157
+ - Save generated files (images, CSVs, processed data) for user to view and download
158
+
159
+ #### Do NOT Use For
160
+ - Content you author directly, like summaries of content you read (use artifacts tool instead)
161
+
162
+ #### Functions
163
+ - listArtifacts() - List all artifact filenames, returns Promise<string[]>
164
+ - getArtifact(filename) - Read artifact content, returns Promise<string | object>. JSON files auto-parse to objects, binary files return base64 string
165
+ - createOrUpdateArtifact(filename, content, mimeType?) - Create or update artifact, returns Promise<void>. JSON files auto-stringify objects, binary requires base64 string with mimeType
166
+ - deleteArtifact(filename) - Delete artifact, returns Promise<void>
167
+
168
+ #### Example
169
+ JSON workflow:
170
+ \`\`\`javascript
171
+ // Fetch and save
172
+ const response = await fetch('https://api.example.com/products');
173
+ const products = await response.json();
174
+ await createOrUpdateArtifact('products.json', products);
175
+
176
+ // Later: read and filter
177
+ const all = await getArtifact('products.json');
178
+ const cheap = all.filter(p => p.price < 100);
179
+ await createOrUpdateArtifact('cheap.json', cheap);
180
+ \`\`\`
181
+
182
+ Binary file (image):
183
+ \`\`\`javascript
184
+ const canvas = document.createElement('canvas');
185
+ canvas.width = 800; canvas.height = 600;
186
+ const ctx = canvas.getContext('2d');
187
+ ctx.fillStyle = 'blue';
188
+ ctx.fillRect(0, 0, 800, 600);
189
+ // Remove data:image/png;base64, prefix
190
+ const base64 = canvas.toDataURL().split(',')[1];
191
+ await createOrUpdateArtifact('chart.png', base64, 'image/png');
192
+ \`\`\`
193
+ `;
194
+
195
+ export const ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RO = `
196
+ ### Artifacts Storage
197
+
198
+ Read files from artifacts storage.
199
+
200
+ #### When to Use
201
+ - Read artifacts created by REPL or artifacts tool
202
+ - Access data from other HTML artifacts
203
+ - Load configuration or data files
204
+
205
+ #### Do NOT Use For
206
+ - Creating new artifacts (not available in HTML artifacts)
207
+ - Modifying artifacts (read-only access)
208
+
209
+ #### Functions
210
+ - listArtifacts() - List all artifact filenames, returns Promise<string[]>
211
+ - getArtifact(filename) - Read artifact content, returns Promise<string | object>. JSON files auto-parse to objects, binary files return base64 string
212
+
213
+ #### Example
214
+ JSON data:
215
+ \`\`\`javascript
216
+ const products = await getArtifact('products.json');
217
+ const html = products.map(p => \`<div>\${p.name}: $\${p.price}</div>\`).join('');
218
+ document.body.innerHTML = html;
219
+ \`\`\`
220
+
221
+ Binary image:
222
+ \`\`\`javascript
223
+ const base64 = await getArtifact('chart.png');
224
+ const img = document.createElement('img');
225
+ img.src = 'data:image/png;base64,' + base64;
226
+ document.body.appendChild(img);
227
+ \`\`\`
228
+ `;
229
+
230
+ // ============================================================================
231
+ // Attachments Runtime Provider
232
+ // ============================================================================
233
+
234
+ export const ATTACHMENTS_RUNTIME_DESCRIPTION = `
235
+ ### User Attachments
236
+
237
+ Read files the user uploaded to the conversation.
238
+
239
+ #### When to Use
240
+ - Process user-uploaded files (CSV, JSON, Excel, images, PDFs)
241
+
242
+ #### Functions
243
+ - listAttachments() - List all attachments, returns array of {id, fileName, mimeType, size}
244
+ - readTextAttachment(id) - Read attachment as text, returns string
245
+ - readBinaryAttachment(id) - Read attachment as binary data, returns Uint8Array
246
+
247
+ #### Example
248
+ CSV file:
249
+ \`\`\`javascript
250
+ const files = listAttachments();
251
+ const csvFile = files.find(f => f.fileName.endsWith('.csv'));
252
+ const csvData = readTextAttachment(csvFile.id);
253
+ const rows = csvData.split('\\n').map(row => row.split(','));
254
+ \`\`\`
255
+
256
+ Excel file:
257
+ \`\`\`javascript
258
+ const XLSX = await import('https://esm.run/xlsx');
259
+ const files = listAttachments();
260
+ const xlsxFile = files.find(f => f.fileName.endsWith('.xlsx'));
261
+ const bytes = readBinaryAttachment(xlsxFile.id);
262
+ const workbook = XLSX.read(bytes);
263
+ const data = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
264
+ \`\`\`
265
+ `;
266
+
267
+ // ============================================================================
268
+ // Extract Document Tool
269
+ // ============================================================================
270
+
271
+ export const EXTRACT_DOCUMENT_DESCRIPTION = `# Extract Document
272
+
273
+ Extract plain text from documents on the web (PDF, DOCX, XLSX, PPTX).
274
+
275
+ ## When to Use
276
+ User wants you to read a document at a URL.
277
+
278
+ ## Input
279
+ - { url: "https://example.com/document.pdf" } - URL to PDF, DOCX, XLSX, or PPTX
280
+
281
+ ## Returns
282
+ Structured plain text with page/sheet/slide delimiters.`;
@@ -0,0 +1,60 @@
1
+ import type { CustomProvidersStore } from "./stores/custom-providers-store.js";
2
+ import type { ProviderKeysStore } from "./stores/provider-keys-store.js";
3
+ import type { SessionsStore } from "./stores/sessions-store.js";
4
+ import type { SettingsStore } from "./stores/settings-store.js";
5
+ import type { StorageBackend } from "./types.js";
6
+
7
+ /**
8
+ * High-level storage API providing access to all storage operations.
9
+ * Subclasses can extend this to add domain-specific stores.
10
+ */
11
+ export class AppStorage {
12
+ readonly backend: StorageBackend;
13
+ readonly settings: SettingsStore;
14
+ readonly providerKeys: ProviderKeysStore;
15
+ readonly sessions: SessionsStore;
16
+ readonly customProviders: CustomProvidersStore;
17
+
18
+ constructor(
19
+ settings: SettingsStore,
20
+ providerKeys: ProviderKeysStore,
21
+ sessions: SessionsStore,
22
+ customProviders: CustomProvidersStore,
23
+ backend: StorageBackend,
24
+ ) {
25
+ this.settings = settings;
26
+ this.providerKeys = providerKeys;
27
+ this.sessions = sessions;
28
+ this.customProviders = customProviders;
29
+ this.backend = backend;
30
+ }
31
+
32
+ async getQuotaInfo(): Promise<{ usage: number; quota: number; percent: number }> {
33
+ return this.backend.getQuotaInfo();
34
+ }
35
+
36
+ async requestPersistence(): Promise<boolean> {
37
+ return this.backend.requestPersistence();
38
+ }
39
+ }
40
+
41
+ // Global instance management
42
+ let globalAppStorage: AppStorage | null = null;
43
+
44
+ /**
45
+ * Get the global AppStorage instance.
46
+ * Throws if not initialized.
47
+ */
48
+ export function getAppStorage(): AppStorage {
49
+ if (!globalAppStorage) {
50
+ throw new Error("AppStorage not initialized. Call setAppStorage() first.");
51
+ }
52
+ return globalAppStorage;
53
+ }
54
+
55
+ /**
56
+ * Set the global AppStorage instance.
57
+ */
58
+ export function setAppStorage(storage: AppStorage): void {
59
+ globalAppStorage = storage;
60
+ }