@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.
- package/CHANGELOG.md +96 -0
- package/README.md +609 -0
- package/example/README.md +61 -0
- package/example/index.html +13 -0
- package/example/package.json +24 -0
- package/example/src/app.css +1 -0
- package/example/src/custom-messages.ts +99 -0
- package/example/src/main.ts +420 -0
- package/example/tsconfig.json +23 -0
- package/example/vite.config.ts +6 -0
- package/package.json +57 -0
- package/scripts/count-prompt-tokens.ts +88 -0
- package/src/ChatPanel.ts +218 -0
- package/src/app.css +68 -0
- package/src/components/AgentInterface.ts +390 -0
- package/src/components/AttachmentTile.ts +107 -0
- package/src/components/ConsoleBlock.ts +74 -0
- package/src/components/CustomProviderCard.ts +96 -0
- package/src/components/ExpandableSection.ts +46 -0
- package/src/components/Input.ts +113 -0
- package/src/components/MessageEditor.ts +404 -0
- package/src/components/MessageList.ts +97 -0
- package/src/components/Messages.ts +384 -0
- package/src/components/ProviderKeyInput.ts +152 -0
- package/src/components/SandboxedIframe.ts +626 -0
- package/src/components/StreamingMessageContainer.ts +107 -0
- package/src/components/ThinkingBlock.ts +45 -0
- package/src/components/message-renderer-registry.ts +28 -0
- package/src/components/sandbox/ArtifactsRuntimeProvider.ts +219 -0
- package/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
- package/src/components/sandbox/ConsoleRuntimeProvider.ts +186 -0
- package/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
- package/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
- package/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
- package/src/components/sandbox/SandboxRuntimeProvider.ts +52 -0
- package/src/dialogs/ApiKeyPromptDialog.ts +75 -0
- package/src/dialogs/AttachmentOverlay.ts +640 -0
- package/src/dialogs/CustomProviderDialog.ts +274 -0
- package/src/dialogs/ModelSelector.ts +314 -0
- package/src/dialogs/PersistentStorageDialog.ts +146 -0
- package/src/dialogs/ProvidersModelsTab.ts +212 -0
- package/src/dialogs/SessionListDialog.ts +157 -0
- package/src/dialogs/SettingsDialog.ts +216 -0
- package/src/index.ts +115 -0
- package/src/prompts/prompts.ts +282 -0
- package/src/storage/app-storage.ts +60 -0
- package/src/storage/backends/indexeddb-storage-backend.ts +193 -0
- package/src/storage/store.ts +33 -0
- package/src/storage/stores/custom-providers-store.ts +62 -0
- package/src/storage/stores/provider-keys-store.ts +33 -0
- package/src/storage/stores/sessions-store.ts +136 -0
- package/src/storage/stores/settings-store.ts +34 -0
- package/src/storage/types.ts +206 -0
- package/src/tools/artifacts/ArtifactElement.ts +14 -0
- package/src/tools/artifacts/ArtifactPill.ts +26 -0
- package/src/tools/artifacts/Console.ts +102 -0
- package/src/tools/artifacts/DocxArtifact.ts +213 -0
- package/src/tools/artifacts/ExcelArtifact.ts +231 -0
- package/src/tools/artifacts/GenericArtifact.ts +118 -0
- package/src/tools/artifacts/HtmlArtifact.ts +203 -0
- package/src/tools/artifacts/ImageArtifact.ts +116 -0
- package/src/tools/artifacts/MarkdownArtifact.ts +83 -0
- package/src/tools/artifacts/PdfArtifact.ts +201 -0
- package/src/tools/artifacts/SvgArtifact.ts +82 -0
- package/src/tools/artifacts/TextArtifact.ts +148 -0
- package/src/tools/artifacts/artifacts-tool-renderer.ts +371 -0
- package/src/tools/artifacts/artifacts.ts +713 -0
- package/src/tools/artifacts/index.ts +7 -0
- package/src/tools/extract-document.ts +271 -0
- package/src/tools/index.ts +46 -0
- package/src/tools/javascript-repl.ts +316 -0
- package/src/tools/renderer-registry.ts +127 -0
- package/src/tools/renderers/BashRenderer.ts +52 -0
- package/src/tools/renderers/CalculateRenderer.ts +58 -0
- package/src/tools/renderers/DefaultRenderer.ts +95 -0
- package/src/tools/renderers/GetCurrentTimeRenderer.ts +92 -0
- package/src/tools/types.ts +15 -0
- package/src/utils/attachment-utils.ts +472 -0
- package/src/utils/auth-token.ts +22 -0
- package/src/utils/format.ts +42 -0
- package/src/utils/i18n.ts +653 -0
- package/src/utils/model-discovery.ts +277 -0
- package/src/utils/proxy-utils.ts +134 -0
- package/src/utils/test-sessions.ts +2357 -0
- package/tsconfig.build.json +20 -0
- 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
|
+
}
|