@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
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
|