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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +609 -0
  3. package/example/README.md +61 -0
  4. package/example/index.html +13 -0
  5. package/example/package.json +24 -0
  6. package/example/src/app.css +1 -0
  7. package/example/src/custom-messages.ts +99 -0
  8. package/example/src/main.ts +420 -0
  9. package/example/tsconfig.json +23 -0
  10. package/example/vite.config.ts +6 -0
  11. package/package.json +57 -0
  12. package/scripts/count-prompt-tokens.ts +88 -0
  13. package/src/ChatPanel.ts +218 -0
  14. package/src/app.css +68 -0
  15. package/src/components/AgentInterface.ts +390 -0
  16. package/src/components/AttachmentTile.ts +107 -0
  17. package/src/components/ConsoleBlock.ts +74 -0
  18. package/src/components/CustomProviderCard.ts +96 -0
  19. package/src/components/ExpandableSection.ts +46 -0
  20. package/src/components/Input.ts +113 -0
  21. package/src/components/MessageEditor.ts +404 -0
  22. package/src/components/MessageList.ts +97 -0
  23. package/src/components/Messages.ts +384 -0
  24. package/src/components/ProviderKeyInput.ts +152 -0
  25. package/src/components/SandboxedIframe.ts +626 -0
  26. package/src/components/StreamingMessageContainer.ts +107 -0
  27. package/src/components/ThinkingBlock.ts +45 -0
  28. package/src/components/message-renderer-registry.ts +28 -0
  29. package/src/components/sandbox/ArtifactsRuntimeProvider.ts +219 -0
  30. package/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
  31. package/src/components/sandbox/ConsoleRuntimeProvider.ts +186 -0
  32. package/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
  33. package/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
  34. package/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
  35. package/src/components/sandbox/SandboxRuntimeProvider.ts +52 -0
  36. package/src/dialogs/ApiKeyPromptDialog.ts +75 -0
  37. package/src/dialogs/AttachmentOverlay.ts +640 -0
  38. package/src/dialogs/CustomProviderDialog.ts +274 -0
  39. package/src/dialogs/ModelSelector.ts +314 -0
  40. package/src/dialogs/PersistentStorageDialog.ts +146 -0
  41. package/src/dialogs/ProvidersModelsTab.ts +212 -0
  42. package/src/dialogs/SessionListDialog.ts +157 -0
  43. package/src/dialogs/SettingsDialog.ts +216 -0
  44. package/src/index.ts +115 -0
  45. package/src/prompts/prompts.ts +282 -0
  46. package/src/storage/app-storage.ts +60 -0
  47. package/src/storage/backends/indexeddb-storage-backend.ts +193 -0
  48. package/src/storage/store.ts +33 -0
  49. package/src/storage/stores/custom-providers-store.ts +62 -0
  50. package/src/storage/stores/provider-keys-store.ts +33 -0
  51. package/src/storage/stores/sessions-store.ts +136 -0
  52. package/src/storage/stores/settings-store.ts +34 -0
  53. package/src/storage/types.ts +206 -0
  54. package/src/tools/artifacts/ArtifactElement.ts +14 -0
  55. package/src/tools/artifacts/ArtifactPill.ts +26 -0
  56. package/src/tools/artifacts/Console.ts +102 -0
  57. package/src/tools/artifacts/DocxArtifact.ts +213 -0
  58. package/src/tools/artifacts/ExcelArtifact.ts +231 -0
  59. package/src/tools/artifacts/GenericArtifact.ts +118 -0
  60. package/src/tools/artifacts/HtmlArtifact.ts +203 -0
  61. package/src/tools/artifacts/ImageArtifact.ts +116 -0
  62. package/src/tools/artifacts/MarkdownArtifact.ts +83 -0
  63. package/src/tools/artifacts/PdfArtifact.ts +201 -0
  64. package/src/tools/artifacts/SvgArtifact.ts +82 -0
  65. package/src/tools/artifacts/TextArtifact.ts +148 -0
  66. package/src/tools/artifacts/artifacts-tool-renderer.ts +371 -0
  67. package/src/tools/artifacts/artifacts.ts +713 -0
  68. package/src/tools/artifacts/index.ts +7 -0
  69. package/src/tools/extract-document.ts +271 -0
  70. package/src/tools/index.ts +46 -0
  71. package/src/tools/javascript-repl.ts +316 -0
  72. package/src/tools/renderer-registry.ts +127 -0
  73. package/src/tools/renderers/BashRenderer.ts +52 -0
  74. package/src/tools/renderers/CalculateRenderer.ts +58 -0
  75. package/src/tools/renderers/DefaultRenderer.ts +95 -0
  76. package/src/tools/renderers/GetCurrentTimeRenderer.ts +92 -0
  77. package/src/tools/types.ts +15 -0
  78. package/src/utils/attachment-utils.ts +472 -0
  79. package/src/utils/auth-token.ts +22 -0
  80. package/src/utils/format.ts +42 -0
  81. package/src/utils/i18n.ts +653 -0
  82. package/src/utils/model-discovery.ts +277 -0
  83. package/src/utils/proxy-utils.ts +134 -0
  84. package/src/utils/test-sessions.ts +2357 -0
  85. package/tsconfig.build.json +20 -0
  86. package/tsconfig.json +7 -0
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@oh-my-pi/pi-web-ui",
3
+ "version": "1.337.0",
4
+ "description": "Reusable web UI components for AI chat interfaces powered by @oh-my-pi/pi-ai",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./app.css": "./src/app.css"
11
+ },
12
+ "scripts": {
13
+ "dev:css": "tailwindcss -i ./src/app.css -o ./dist/app.css --watch",
14
+ "dev:example": "bun --cwd=example run dev",
15
+ "dev": "bun run dev:css & bun run dev:example",
16
+ "build:css": "tailwindcss -i ./src/app.css -o ./dist/app.css --minify",
17
+ "check": "biome check --write . && tsgo --noEmit && bun --cwd=example run check"
18
+ },
19
+ "dependencies": {
20
+ "@lmstudio/sdk": "^1.5.0",
21
+ "@oh-my-pi/pi-agent-core": "workspace:*",
22
+ "@oh-my-pi/pi-ai": "workspace:*",
23
+ "@oh-my-pi/pi-tui": "workspace:*",
24
+ "docx-preview": "^0.3.7",
25
+ "highlight.js": "^11.11.1",
26
+ "jszip": "^3.10.1",
27
+ "lucide": "^0.544.0",
28
+ "ollama": "^0.6.0",
29
+ "pdfjs-dist": "5.4.394",
30
+ "tailwindcss": "^4.1.18",
31
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
32
+ },
33
+ "peerDependencies": {
34
+ "@mariozechner/mini-lit": "^0.2.0",
35
+ "lit": "^3.3.1"
36
+ },
37
+ "devDependencies": {
38
+ "@mariozechner/mini-lit": "^0.2.0",
39
+ "@tailwindcss/cli": "^4.0.0-beta.14"
40
+ },
41
+ "keywords": [
42
+ "ai",
43
+ "chat",
44
+ "ui",
45
+ "components",
46
+ "llm",
47
+ "web-components",
48
+ "mini-lit"
49
+ ],
50
+ "author": "Mario Zechner",
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/can1357/oh-my-pi.git",
55
+ "directory": "packages/web-ui"
56
+ }
57
+ }
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Count tokens in system prompts using Anthropic's token counter API
4
+ */
5
+
6
+ import * as prompts from "../src/prompts/prompts.js";
7
+
8
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
9
+
10
+ if (!ANTHROPIC_API_KEY) {
11
+ console.error("Error: ANTHROPIC_API_KEY environment variable not set");
12
+ process.exit(1);
13
+ }
14
+
15
+ interface TokenCountResponse {
16
+ input_tokens: number;
17
+ }
18
+
19
+ async function countTokens(text: string): Promise<number> {
20
+ const response = await fetch("https://api.anthropic.com/v1/messages/count_tokens", {
21
+ method: "POST",
22
+ headers: {
23
+ "Content-Type": "application/json",
24
+ "x-api-key": ANTHROPIC_API_KEY,
25
+ "anthropic-version": "2023-06-01",
26
+ },
27
+ body: JSON.stringify({
28
+ model: "claude-3-5-sonnet-20241022",
29
+ messages: [
30
+ {
31
+ role: "user",
32
+ content: text,
33
+ },
34
+ ],
35
+ }),
36
+ });
37
+
38
+ if (!response.ok) {
39
+ const error = await response.text();
40
+ throw new Error(`API error: ${response.status} ${error}`);
41
+ }
42
+
43
+ const data = (await response.json()) as TokenCountResponse;
44
+ return data.input_tokens;
45
+ }
46
+
47
+ async function main() {
48
+ console.log("Counting tokens in prompts...\n");
49
+
50
+ const promptsToCount: Array<{ name: string; content: string }> = [
51
+ {
52
+ name: "ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RW",
53
+ content: prompts.ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RW,
54
+ },
55
+ {
56
+ name: "ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RO",
57
+ content: prompts.ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RO,
58
+ },
59
+ {
60
+ name: "ATTACHMENTS_RUNTIME_DESCRIPTION",
61
+ content: prompts.ATTACHMENTS_RUNTIME_DESCRIPTION,
62
+ },
63
+ {
64
+ name: "JAVASCRIPT_REPL_TOOL_DESCRIPTION (without runtime providers)",
65
+ content: prompts.JAVASCRIPT_REPL_TOOL_DESCRIPTION([]),
66
+ },
67
+ {
68
+ name: "ARTIFACTS_TOOL_DESCRIPTION (without runtime providers)",
69
+ content: prompts.ARTIFACTS_TOOL_DESCRIPTION([]),
70
+ },
71
+ ];
72
+
73
+ let total = 0;
74
+
75
+ for (const prompt of promptsToCount) {
76
+ try {
77
+ const tokens = await countTokens(prompt.content);
78
+ total += tokens;
79
+ console.log(`${prompt.name}: ${tokens.toLocaleString()} tokens`);
80
+ } catch (error) {
81
+ console.error(`Error counting tokens for ${prompt.name}:`, error);
82
+ }
83
+ }
84
+
85
+ console.log(`\nTotal: ${total.toLocaleString()} tokens`);
86
+ }
87
+
88
+ main();
@@ -0,0 +1,218 @@
1
+ import { Badge } from "@mariozechner/mini-lit/dist/Badge.js";
2
+ import { html, LitElement } from "lit";
3
+ import { customElement, state } from "lit/decorators.js";
4
+ import "./components/AgentInterface.js";
5
+ import type { Agent, AgentTool } from "@oh-my-pi/pi-agent-core";
6
+ import type { AgentInterface } from "./components/AgentInterface.js";
7
+ import { ArtifactsRuntimeProvider } from "./components/sandbox/ArtifactsRuntimeProvider.js";
8
+ import { AttachmentsRuntimeProvider } from "./components/sandbox/AttachmentsRuntimeProvider.js";
9
+ import type { SandboxRuntimeProvider } from "./components/sandbox/SandboxRuntimeProvider.js";
10
+ import { ArtifactsPanel, ArtifactsToolRenderer } from "./tools/artifacts/index.js";
11
+ import { registerToolRenderer } from "./tools/renderer-registry.js";
12
+ import type { Attachment } from "./utils/attachment-utils.js";
13
+ import { i18n } from "./utils/i18n.js";
14
+
15
+ const BREAKPOINT = 800; // px - switch between overlay and side-by-side
16
+
17
+ @customElement("pi-chat-panel")
18
+ export class ChatPanel extends LitElement {
19
+ @state() public agent?: Agent;
20
+ @state() public agentInterface?: AgentInterface;
21
+ @state() public artifactsPanel?: ArtifactsPanel;
22
+ @state() private hasArtifacts = false;
23
+ @state() private artifactCount = 0;
24
+ @state() private showArtifactsPanel = false;
25
+ @state() private windowWidth = 0;
26
+
27
+ private resizeHandler = () => {
28
+ this.windowWidth = window.innerWidth;
29
+ this.requestUpdate();
30
+ };
31
+
32
+ createRenderRoot() {
33
+ return this;
34
+ }
35
+
36
+ override connectedCallback() {
37
+ super.connectedCallback();
38
+ this.windowWidth = window.innerWidth; // Set initial width after connection
39
+ window.addEventListener("resize", this.resizeHandler);
40
+ this.style.display = "flex";
41
+ this.style.flexDirection = "column";
42
+ this.style.height = "100%";
43
+ this.style.minHeight = "0";
44
+ // Update width after initial render
45
+ requestAnimationFrame(() => {
46
+ this.windowWidth = window.innerWidth;
47
+ this.requestUpdate();
48
+ });
49
+ }
50
+
51
+ override disconnectedCallback() {
52
+ super.disconnectedCallback();
53
+ window.removeEventListener("resize", this.resizeHandler);
54
+ }
55
+
56
+ async setAgent(
57
+ agent: Agent,
58
+ config?: {
59
+ onApiKeyRequired?: (provider: string) => Promise<boolean>;
60
+ onBeforeSend?: () => void | Promise<void>;
61
+ onCostClick?: () => void;
62
+ sandboxUrlProvider?: () => string;
63
+ toolsFactory?: (
64
+ agent: Agent,
65
+ agentInterface: AgentInterface,
66
+ artifactsPanel: ArtifactsPanel,
67
+ runtimeProvidersFactory: () => SandboxRuntimeProvider[],
68
+ ) => AgentTool<any>[];
69
+ },
70
+ ) {
71
+ this.agent = agent;
72
+
73
+ // Create AgentInterface
74
+ this.agentInterface = document.createElement("agent-interface") as AgentInterface;
75
+ this.agentInterface.session = agent;
76
+ this.agentInterface.enableAttachments = true;
77
+ this.agentInterface.enableModelSelector = true;
78
+ this.agentInterface.enableThinkingSelector = true;
79
+ this.agentInterface.showThemeToggle = false;
80
+ this.agentInterface.onApiKeyRequired = config?.onApiKeyRequired;
81
+ this.agentInterface.onBeforeSend = config?.onBeforeSend;
82
+ this.agentInterface.onCostClick = config?.onCostClick;
83
+
84
+ // Set up artifacts panel
85
+ this.artifactsPanel = new ArtifactsPanel();
86
+ this.artifactsPanel.agent = agent; // Pass agent for HTML artifact runtime providers
87
+ if (config?.sandboxUrlProvider) {
88
+ this.artifactsPanel.sandboxUrlProvider = config.sandboxUrlProvider;
89
+ }
90
+ // Register the standalone tool renderer (not the panel itself)
91
+ registerToolRenderer("artifacts", new ArtifactsToolRenderer(this.artifactsPanel));
92
+
93
+ // Runtime providers factory for REPL tools (read-write access)
94
+ const runtimeProvidersFactory = () => {
95
+ const attachments: Attachment[] = [];
96
+ for (const message of this.agent!.state.messages) {
97
+ if (message.role === "user-with-attachments") {
98
+ message.attachments?.forEach((a) => {
99
+ attachments.push(a);
100
+ });
101
+ }
102
+ }
103
+ const providers: SandboxRuntimeProvider[] = [];
104
+
105
+ // Add attachments provider if there are attachments
106
+ if (attachments.length > 0) {
107
+ providers.push(new AttachmentsRuntimeProvider(attachments));
108
+ }
109
+
110
+ // Add artifacts provider with read-write access (for REPL)
111
+ providers.push(new ArtifactsRuntimeProvider(this.artifactsPanel!, this.agent!, true));
112
+
113
+ return providers;
114
+ };
115
+
116
+ this.artifactsPanel.onArtifactsChange = () => {
117
+ const count = this.artifactsPanel?.artifacts?.size ?? 0;
118
+ const created = count > this.artifactCount;
119
+ this.hasArtifacts = count > 0;
120
+ this.artifactCount = count;
121
+ if (this.hasArtifacts && created) {
122
+ this.showArtifactsPanel = true;
123
+ }
124
+ this.requestUpdate();
125
+ };
126
+
127
+ this.artifactsPanel.onClose = () => {
128
+ this.showArtifactsPanel = false;
129
+ this.requestUpdate();
130
+ };
131
+
132
+ this.artifactsPanel.onOpen = () => {
133
+ this.showArtifactsPanel = true;
134
+ this.requestUpdate();
135
+ };
136
+
137
+ // Set tools on the agent
138
+ // Pass runtimeProvidersFactory so consumers can configure their own REPL tools
139
+ const additionalTools =
140
+ config?.toolsFactory?.(agent, this.agentInterface, this.artifactsPanel, runtimeProvidersFactory) || [];
141
+ const tools = [this.artifactsPanel.tool, ...additionalTools];
142
+ this.agent.setTools(tools);
143
+
144
+ // Reconstruct artifacts from existing messages
145
+ // Temporarily disable the onArtifactsChange callback to prevent auto-opening on load
146
+ const originalCallback = this.artifactsPanel.onArtifactsChange;
147
+ this.artifactsPanel.onArtifactsChange = undefined;
148
+ await this.artifactsPanel.reconstructFromMessages(this.agent.state.messages);
149
+ this.artifactsPanel.onArtifactsChange = originalCallback;
150
+
151
+ this.hasArtifacts = this.artifactsPanel.artifacts.size > 0;
152
+ this.artifactCount = this.artifactsPanel.artifacts.size;
153
+
154
+ this.requestUpdate();
155
+ }
156
+
157
+ render() {
158
+ if (!this.agent || !this.agentInterface) {
159
+ return html`<div class="flex items-center justify-center h-full">
160
+ <div class="text-muted-foreground">No agent set</div>
161
+ </div>`;
162
+ }
163
+
164
+ const isMobile = this.windowWidth < BREAKPOINT;
165
+
166
+ // Set panel props
167
+ if (this.artifactsPanel) {
168
+ this.artifactsPanel.collapsed = !this.showArtifactsPanel;
169
+ this.artifactsPanel.overlay = isMobile;
170
+ }
171
+
172
+ return html`
173
+ <div class="relative w-full h-full overflow-hidden flex">
174
+ <div
175
+ class="h-full"
176
+ style="${!isMobile && this.showArtifactsPanel && this.hasArtifacts ? "width: 50%;" : "width: 100%;"}"
177
+ >
178
+ ${this.agentInterface}
179
+ </div>
180
+
181
+ <!-- Floating pill when artifacts exist and panel is collapsed -->
182
+ ${
183
+ this.hasArtifacts && !this.showArtifactsPanel
184
+ ? html`
185
+ <button
186
+ class="absolute z-30 top-4 left-1/2 -translate-x-1/2 pointer-events-auto"
187
+ @click=${() => {
188
+ this.showArtifactsPanel = true;
189
+ this.requestUpdate();
190
+ }}
191
+ title=${i18n("Show artifacts")}
192
+ >
193
+ ${Badge(html`
194
+ <span class="inline-flex items-center gap-1">
195
+ <span>${i18n("Artifacts")}</span>
196
+ <span
197
+ class="text-[10px] leading-none bg-primary-foreground/20 text-primary-foreground rounded px-1 font-mono tabular-nums"
198
+ >${this.artifactCount}</span
199
+ >
200
+ </span>
201
+ `)}
202
+ </button>
203
+ `
204
+ : ""
205
+ }
206
+
207
+ <div
208
+ class="h-full ${isMobile ? "absolute inset-0 pointer-events-none" : ""}"
209
+ style="${
210
+ !isMobile ? (!this.hasArtifacts || !this.showArtifactsPanel ? "display: none;" : "width: 50%;") : ""
211
+ }"
212
+ >
213
+ ${this.artifactsPanel}
214
+ </div>
215
+ </div>
216
+ `;
217
+ }
218
+ }
package/src/app.css ADDED
@@ -0,0 +1,68 @@
1
+ /* Import Claude theme from mini-lit */
2
+ @import "@mariozechner/mini-lit/styles/themes/default.css";
3
+
4
+ /* Tell Tailwind to scan mini-lit components */
5
+ /* biome-ignore lint/suspicious/noUnknownAtRules: Tailwind 4 source directive */
6
+ @source "../../../node_modules/@mariozechner/mini-lit/dist";
7
+
8
+ /* Import Tailwind */
9
+ /* biome-ignore lint/correctness/noInvalidPositionAtImportRule: fuck you */
10
+ @import "tailwindcss";
11
+
12
+ body {
13
+ font-size: 16px;
14
+ -webkit-font-smoothing: antialiased;
15
+ }
16
+
17
+ * {
18
+ scrollbar-width: thin;
19
+ scrollbar-color: var(--color-border) rgba(0, 0, 0, 0);
20
+ }
21
+
22
+ *::-webkit-scrollbar {
23
+ width: 8px;
24
+ height: 8px;
25
+ }
26
+
27
+ *::-webkit-scrollbar-track {
28
+ background: transparent;
29
+ }
30
+
31
+ *::-webkit-scrollbar-thumb {
32
+ background-color: var(--color-border);
33
+ border-radius: 4px;
34
+ }
35
+
36
+ *::-webkit-scrollbar-thumb:hover {
37
+ background-color: rgba(0, 0, 0, 0);
38
+ }
39
+
40
+ /* Fix cursor for dialog close buttons */
41
+ .fixed.inset-0 button[aria-label*="Close"],
42
+ .fixed.inset-0 button[type="button"] {
43
+ cursor: pointer;
44
+ }
45
+
46
+ /* Shimmer animation for thinking text */
47
+ @keyframes shimmer {
48
+ 0% {
49
+ background-position: -200% 0;
50
+ }
51
+ 100% {
52
+ background-position: 200% 0;
53
+ }
54
+ }
55
+
56
+ .animate-shimmer {
57
+ animation: shimmer 2s ease-in-out infinite;
58
+ }
59
+
60
+ /* User message with fancy pill styling */
61
+ .user-message-container {
62
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
63
+ position: relative;
64
+ background: linear-gradient(135deg, rgba(217, 79, 0, 0.12), rgba(255, 107, 0, 0.12), rgba(212, 165, 0, 0.12));
65
+ border: 1px solid rgba(255, 107, 0, 0.25);
66
+ backdrop-filter: blur(10px);
67
+ max-width: 100%;
68
+ }