mirror of
https://github.com/harivansh-afk/clanker-agent.git
synced 2026-04-16 19:04:37 +00:00
move pi-mono into companion-cloud as apps/companion-os
- Copy all pi-mono source into apps/companion-os/ - Update Dockerfile to COPY pre-built binary instead of downloading from GitHub Releases - Update deploy-staging.yml to build pi from source (bun compile) before Docker build - Add apps/companion-os/** to path triggers - No more cross-repo dispatch needed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
0250f72976
579 changed files with 206942 additions and 0 deletions
3
packages/web-ui/example/.gitignore
vendored
Normal file
3
packages/web-ui/example/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
.DS_Store
|
||||
61
packages/web-ui/example/README.md
Normal file
61
packages/web-ui/example/README.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# Pi Web UI - Example
|
||||
|
||||
This is a minimal example showing how to use `@mariozechner/pi-web-ui` in a web application.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open [http://localhost:5173](http://localhost:5173) in your browser.
|
||||
|
||||
## What's Included
|
||||
|
||||
This example demonstrates:
|
||||
|
||||
- **ChatPanel** - The main chat interface component
|
||||
- **System Prompt** - Custom configuration for the AI assistant
|
||||
- **Tools** - JavaScript REPL and artifacts tool
|
||||
|
||||
## Configuration
|
||||
|
||||
### API Keys
|
||||
|
||||
The example uses **Direct Mode** by default, which means it calls AI provider APIs directly from the browser.
|
||||
|
||||
To use the chat:
|
||||
|
||||
1. Click the settings icon (⚙️) in the chat interface
|
||||
2. Click "Manage API Keys"
|
||||
3. Add your API key for your preferred provider:
|
||||
- **Anthropic**: Get a key from [console.anthropic.com](https://console.anthropic.com/)
|
||||
- **OpenAI**: Get a key from [platform.openai.com](https://platform.openai.com/)
|
||||
- **Google**: Get a key from [makersuite.google.com](https://makersuite.google.com/)
|
||||
|
||||
API keys are stored in your browser's localStorage and never sent to any server except the AI provider's API.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
example/
|
||||
├── src/
|
||||
│ ├── main.ts # Main application entry point
|
||||
│ └── app.css # Tailwind CSS configuration
|
||||
├── index.html # HTML entry point
|
||||
├── package.json # Dependencies
|
||||
├── vite.config.ts # Vite configuration
|
||||
└── tsconfig.json # TypeScript configuration
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Pi Web UI Documentation](../README.md)
|
||||
- [Pi AI Documentation](../../ai/README.md)
|
||||
- [Mini Lit Documentation](https://github.com/badlogic/mini-lit)
|
||||
13
packages/web-ui/example/index.html
Normal file
13
packages/web-ui/example/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Pi Web UI - Example</title>
|
||||
<meta name="description" content="Example usage of @mariozechner/pi-web-ui - Reusable AI chat interface" />
|
||||
</head>
|
||||
<body class="bg-background">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
25
packages/web-ui/example/package.json
Normal file
25
packages/web-ui/example/package.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "pi-web-ui-example",
|
||||
"version": "1.44.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "tsgo --noEmit",
|
||||
"clean": "shx rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mariozechner/mini-lit": "^0.2.0",
|
||||
"@mariozechner/pi-ai": "file:../../ai",
|
||||
"@mariozechner/pi-web-ui": "file:../",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"lit": "^3.3.1",
|
||||
"lucide": "^0.544.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^7.1.6"
|
||||
}
|
||||
}
|
||||
1
packages/web-ui/example/src/app.css
Normal file
1
packages/web-ui/example/src/app.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
@import "../../dist/app.css";
|
||||
104
packages/web-ui/example/src/custom-messages.ts
Normal file
104
packages/web-ui/example/src/custom-messages.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { Alert } from "@mariozechner/mini-lit/dist/Alert.js";
|
||||
import type { Message } from "@mariozechner/pi-ai";
|
||||
import type { AgentMessage, MessageRenderer } from "@mariozechner/pi-web-ui";
|
||||
import {
|
||||
defaultConvertToLlm,
|
||||
registerMessageRenderer,
|
||||
} from "@mariozechner/pi-web-ui";
|
||||
import { html } from "lit";
|
||||
|
||||
// ============================================================================
|
||||
// 1. EXTEND AppMessage TYPE VIA DECLARATION MERGING
|
||||
// ============================================================================
|
||||
|
||||
// Define custom message types
|
||||
export interface SystemNotificationMessage {
|
||||
role: "system-notification";
|
||||
message: string;
|
||||
variant: "default" | "destructive";
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
// Extend CustomAgentMessages interface via declaration merging
|
||||
// This must target pi-agent-core where CustomAgentMessages is defined
|
||||
declare module "@mariozechner/pi-agent-core" {
|
||||
interface CustomAgentMessages {
|
||||
"system-notification": SystemNotificationMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 2. CREATE CUSTOM RENDERER (TYPED TO SystemNotificationMessage)
|
||||
// ============================================================================
|
||||
|
||||
const systemNotificationRenderer: MessageRenderer<SystemNotificationMessage> = {
|
||||
render: (notification) => {
|
||||
// notification is fully typed as SystemNotificationMessage!
|
||||
return html`
|
||||
<div class="px-4">
|
||||
${Alert({
|
||||
variant: notification.variant,
|
||||
children: html`
|
||||
<div class="flex flex-col gap-1">
|
||||
<div>${notification.message}</div>
|
||||
<div class="text-xs opacity-70">
|
||||
${new Date(notification.timestamp).toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 3. REGISTER RENDERER
|
||||
// ============================================================================
|
||||
|
||||
export function registerCustomMessageRenderers() {
|
||||
registerMessageRenderer("system-notification", systemNotificationRenderer);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 4. HELPER TO CREATE CUSTOM MESSAGES
|
||||
// ============================================================================
|
||||
|
||||
export function createSystemNotification(
|
||||
message: string,
|
||||
variant: "default" | "destructive" = "default",
|
||||
): SystemNotificationMessage {
|
||||
return {
|
||||
role: "system-notification",
|
||||
message,
|
||||
variant,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 5. CUSTOM MESSAGE TRANSFORMER
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Custom message transformer that extends defaultConvertToLlm.
|
||||
* Handles system-notification messages by converting them to user messages.
|
||||
*/
|
||||
export function customConvertToLlm(messages: AgentMessage[]): Message[] {
|
||||
// First, handle our custom system-notification type
|
||||
const processed = messages.map((m): AgentMessage => {
|
||||
if (m.role === "system-notification") {
|
||||
const notification = m as SystemNotificationMessage;
|
||||
// Convert to user message with <system> tags
|
||||
return {
|
||||
role: "user",
|
||||
content: `<system>${notification.message}</system>`,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
return m;
|
||||
});
|
||||
|
||||
// Then use defaultConvertToLlm for standard handling
|
||||
return defaultConvertToLlm(processed);
|
||||
}
|
||||
473
packages/web-ui/example/src/main.ts
Normal file
473
packages/web-ui/example/src/main.ts
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
import "@mariozechner/mini-lit/dist/ThemeToggle.js";
|
||||
import { Agent, type AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import { getModel } from "@mariozechner/pi-ai";
|
||||
import {
|
||||
type AgentState,
|
||||
ApiKeyPromptDialog,
|
||||
AppStorage,
|
||||
ChatPanel,
|
||||
CustomProvidersStore,
|
||||
createJavaScriptReplTool,
|
||||
IndexedDBStorageBackend,
|
||||
// PersistentStorageDialog, // TODO: Fix - currently broken
|
||||
ProviderKeysStore,
|
||||
ProvidersModelsTab,
|
||||
ProxyTab,
|
||||
SessionListDialog,
|
||||
SessionsStore,
|
||||
SettingsDialog,
|
||||
SettingsStore,
|
||||
setAppStorage,
|
||||
} from "@mariozechner/pi-web-ui";
|
||||
import { html, render } from "lit";
|
||||
import { Bell, History, Plus, Settings } from "lucide";
|
||||
import "./app.css";
|
||||
import { icon } from "@mariozechner/mini-lit";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { Input } from "@mariozechner/mini-lit/dist/Input.js";
|
||||
import {
|
||||
createSystemNotification,
|
||||
customConvertToLlm,
|
||||
registerCustomMessageRenderers,
|
||||
} from "./custom-messages.js";
|
||||
|
||||
// Register custom message renderers
|
||||
registerCustomMessageRenderers();
|
||||
|
||||
// Create stores
|
||||
const settings = new SettingsStore();
|
||||
const providerKeys = new ProviderKeysStore();
|
||||
const sessions = new SessionsStore();
|
||||
const customProviders = new CustomProvidersStore();
|
||||
|
||||
// Gather configs
|
||||
const configs = [
|
||||
settings.getConfig(),
|
||||
SessionsStore.getMetadataConfig(),
|
||||
providerKeys.getConfig(),
|
||||
customProviders.getConfig(),
|
||||
sessions.getConfig(),
|
||||
];
|
||||
|
||||
// Create backend
|
||||
const backend = new IndexedDBStorageBackend({
|
||||
dbName: "pi-web-ui-example",
|
||||
version: 2, // Incremented for custom-providers store
|
||||
stores: configs,
|
||||
});
|
||||
|
||||
// Wire backend to stores
|
||||
settings.setBackend(backend);
|
||||
providerKeys.setBackend(backend);
|
||||
customProviders.setBackend(backend);
|
||||
sessions.setBackend(backend);
|
||||
|
||||
// Create and set app storage
|
||||
const storage = new AppStorage(
|
||||
settings,
|
||||
providerKeys,
|
||||
sessions,
|
||||
customProviders,
|
||||
backend,
|
||||
);
|
||||
setAppStorage(storage);
|
||||
|
||||
let currentSessionId: string | undefined;
|
||||
let currentTitle = "";
|
||||
let isEditingTitle = false;
|
||||
let agent: Agent;
|
||||
let chatPanel: ChatPanel;
|
||||
let agentUnsubscribe: (() => void) | undefined;
|
||||
|
||||
const generateTitle = (messages: AgentMessage[]): string => {
|
||||
const firstUserMsg = messages.find(
|
||||
(m) => m.role === "user" || m.role === "user-with-attachments",
|
||||
);
|
||||
if (
|
||||
!firstUserMsg ||
|
||||
(firstUserMsg.role !== "user" &&
|
||||
firstUserMsg.role !== "user-with-attachments")
|
||||
)
|
||||
return "";
|
||||
|
||||
let text = "";
|
||||
const content = firstUserMsg.content;
|
||||
|
||||
if (typeof content === "string") {
|
||||
text = content;
|
||||
} else {
|
||||
const textBlocks = content.filter((c: any) => c.type === "text");
|
||||
text = textBlocks.map((c: any) => c.text || "").join(" ");
|
||||
}
|
||||
|
||||
text = text.trim();
|
||||
if (!text) return "";
|
||||
|
||||
const sentenceEnd = text.search(/[.!?]/);
|
||||
if (sentenceEnd > 0 && sentenceEnd <= 50) {
|
||||
return text.substring(0, sentenceEnd + 1);
|
||||
}
|
||||
return text.length <= 50 ? text : `${text.substring(0, 47)}...`;
|
||||
};
|
||||
|
||||
const shouldSaveSession = (messages: AgentMessage[]): boolean => {
|
||||
const hasUserMsg = messages.some(
|
||||
(m: any) => m.role === "user" || m.role === "user-with-attachments",
|
||||
);
|
||||
const hasAssistantMsg = messages.some((m: any) => m.role === "assistant");
|
||||
return hasUserMsg && hasAssistantMsg;
|
||||
};
|
||||
|
||||
const saveSession = async () => {
|
||||
if (!storage.sessions || !currentSessionId || !agent || !currentTitle) return;
|
||||
|
||||
const state = agent.state;
|
||||
if (!shouldSaveSession(state.messages)) return;
|
||||
|
||||
try {
|
||||
// Create session data
|
||||
const sessionData = {
|
||||
id: currentSessionId,
|
||||
title: currentTitle,
|
||||
model: state.model!,
|
||||
thinkingLevel: state.thinkingLevel,
|
||||
messages: state.messages,
|
||||
createdAt: new Date().toISOString(),
|
||||
lastModified: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Create session metadata
|
||||
const metadata = {
|
||||
id: currentSessionId,
|
||||
title: currentTitle,
|
||||
createdAt: sessionData.createdAt,
|
||||
lastModified: sessionData.lastModified,
|
||||
messageCount: state.messages.length,
|
||||
usage: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 0,
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
total: 0,
|
||||
},
|
||||
},
|
||||
modelId: state.model?.id || null,
|
||||
thinkingLevel: state.thinkingLevel,
|
||||
preview: generateTitle(state.messages),
|
||||
};
|
||||
|
||||
await storage.sessions.save(sessionData, metadata);
|
||||
} catch (err) {
|
||||
console.error("Failed to save session:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const updateUrl = (sessionId: string) => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("session", sessionId);
|
||||
window.history.replaceState({}, "", url);
|
||||
};
|
||||
|
||||
const createAgent = async (initialState?: Partial<AgentState>) => {
|
||||
if (agentUnsubscribe) {
|
||||
agentUnsubscribe();
|
||||
}
|
||||
|
||||
agent = new Agent({
|
||||
initialState: initialState || {
|
||||
systemPrompt: `You are a helpful AI assistant with access to various tools.
|
||||
|
||||
Available tools:
|
||||
- JavaScript REPL: Execute JavaScript code in a sandboxed browser environment (can do calculations, get time, process data, create visualizations, etc.)
|
||||
- Artifacts: Create interactive HTML, SVG, Markdown, and text artifacts
|
||||
|
||||
Feel free to use these tools when needed to provide accurate and helpful responses.`,
|
||||
model: getModel("anthropic", "claude-sonnet-4-5-20250929"),
|
||||
thinkingLevel: "off",
|
||||
messages: [],
|
||||
tools: [],
|
||||
},
|
||||
// Custom transformer: convert custom messages to LLM-compatible format
|
||||
convertToLlm: customConvertToLlm,
|
||||
});
|
||||
|
||||
agentUnsubscribe = agent.subscribe((event: any) => {
|
||||
if (event.type === "state-update") {
|
||||
const messages = event.state.messages;
|
||||
|
||||
// Generate title after first successful response
|
||||
if (!currentTitle && shouldSaveSession(messages)) {
|
||||
currentTitle = generateTitle(messages);
|
||||
}
|
||||
|
||||
// Create session ID on first successful save
|
||||
if (!currentSessionId && shouldSaveSession(messages)) {
|
||||
currentSessionId = crypto.randomUUID();
|
||||
updateUrl(currentSessionId);
|
||||
}
|
||||
|
||||
// Auto-save
|
||||
if (currentSessionId) {
|
||||
saveSession();
|
||||
}
|
||||
|
||||
renderApp();
|
||||
}
|
||||
});
|
||||
|
||||
await chatPanel.setAgent(agent, {
|
||||
onApiKeyRequired: async (provider: string) => {
|
||||
return await ApiKeyPromptDialog.prompt(provider);
|
||||
},
|
||||
toolsFactory: (
|
||||
_agent,
|
||||
_agentInterface,
|
||||
_artifactsPanel,
|
||||
runtimeProvidersFactory,
|
||||
) => {
|
||||
// Create javascript_repl tool with access to attachments + artifacts
|
||||
const replTool = createJavaScriptReplTool();
|
||||
replTool.runtimeProvidersFactory = runtimeProvidersFactory;
|
||||
return [replTool];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const loadSession = async (sessionId: string): Promise<boolean> => {
|
||||
if (!storage.sessions) return false;
|
||||
|
||||
const sessionData = await storage.sessions.get(sessionId);
|
||||
if (!sessionData) {
|
||||
console.error("Session not found:", sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
currentSessionId = sessionId;
|
||||
const metadata = await storage.sessions.getMetadata(sessionId);
|
||||
currentTitle = metadata?.title || "";
|
||||
|
||||
await createAgent({
|
||||
model: sessionData.model,
|
||||
thinkingLevel: sessionData.thinkingLevel,
|
||||
messages: sessionData.messages,
|
||||
tools: [],
|
||||
});
|
||||
|
||||
updateUrl(sessionId);
|
||||
renderApp();
|
||||
return true;
|
||||
};
|
||||
|
||||
const newSession = () => {
|
||||
const url = new URL(window.location.href);
|
||||
url.search = "";
|
||||
window.location.href = url.toString();
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RENDER
|
||||
// ============================================================================
|
||||
const renderApp = () => {
|
||||
const app = document.getElementById("app");
|
||||
if (!app) return;
|
||||
|
||||
const appHtml = html`
|
||||
<div
|
||||
class="w-full h-screen flex flex-col bg-background text-foreground overflow-hidden"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-border shrink-0"
|
||||
>
|
||||
<div class="flex items-center gap-2 px-4 py-">
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
children: icon(History, "sm"),
|
||||
onClick: () => {
|
||||
SessionListDialog.open(
|
||||
async (sessionId) => {
|
||||
await loadSession(sessionId);
|
||||
},
|
||||
(deletedSessionId) => {
|
||||
// Only reload if the current session was deleted
|
||||
if (deletedSessionId === currentSessionId) {
|
||||
newSession();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
title: "Sessions",
|
||||
})}
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
children: icon(Plus, "sm"),
|
||||
onClick: newSession,
|
||||
title: "New Session",
|
||||
})}
|
||||
${currentTitle
|
||||
? isEditingTitle
|
||||
? html`<div class="flex items-center gap-2">
|
||||
${Input({
|
||||
type: "text",
|
||||
value: currentTitle,
|
||||
className: "text-sm w-64",
|
||||
onChange: async (e: Event) => {
|
||||
const newTitle = (
|
||||
e.target as HTMLInputElement
|
||||
).value.trim();
|
||||
if (
|
||||
newTitle &&
|
||||
newTitle !== currentTitle &&
|
||||
storage.sessions &&
|
||||
currentSessionId
|
||||
) {
|
||||
await storage.sessions.updateTitle(
|
||||
currentSessionId,
|
||||
newTitle,
|
||||
);
|
||||
currentTitle = newTitle;
|
||||
}
|
||||
isEditingTitle = false;
|
||||
renderApp();
|
||||
},
|
||||
onKeyDown: async (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
const newTitle = (
|
||||
e.target as HTMLInputElement
|
||||
).value.trim();
|
||||
if (
|
||||
newTitle &&
|
||||
newTitle !== currentTitle &&
|
||||
storage.sessions &&
|
||||
currentSessionId
|
||||
) {
|
||||
await storage.sessions.updateTitle(
|
||||
currentSessionId,
|
||||
newTitle,
|
||||
);
|
||||
currentTitle = newTitle;
|
||||
}
|
||||
isEditingTitle = false;
|
||||
renderApp();
|
||||
} else if (e.key === "Escape") {
|
||||
isEditingTitle = false;
|
||||
renderApp();
|
||||
}
|
||||
},
|
||||
})}
|
||||
</div>`
|
||||
: html`<button
|
||||
class="px-2 py-1 text-sm text-foreground hover:bg-secondary rounded transition-colors"
|
||||
@click=${() => {
|
||||
isEditingTitle = true;
|
||||
renderApp();
|
||||
requestAnimationFrame(() => {
|
||||
const input = app?.querySelector(
|
||||
'input[type="text"]',
|
||||
) as HTMLInputElement;
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
});
|
||||
}}
|
||||
title="Click to edit title"
|
||||
>
|
||||
${currentTitle}
|
||||
</button>`
|
||||
: html`<span class="text-base font-semibold text-foreground"
|
||||
>Pi Web UI Example</span
|
||||
>`}
|
||||
</div>
|
||||
<div class="flex items-center gap-1 px-2">
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
children: icon(Bell, "sm"),
|
||||
onClick: () => {
|
||||
// Demo: Inject custom message (will appear on next agent run)
|
||||
if (agent) {
|
||||
agent.steer(
|
||||
createSystemNotification(
|
||||
"This is a custom message! It appears in the UI but is never sent to the LLM.",
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
title: "Demo: Add Custom Notification",
|
||||
})}
|
||||
<theme-toggle></theme-toggle>
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
children: icon(Settings, "sm"),
|
||||
onClick: () =>
|
||||
SettingsDialog.open([new ProvidersModelsTab(), new ProxyTab()]),
|
||||
title: "Settings",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Panel -->
|
||||
${chatPanel}
|
||||
</div>
|
||||
`;
|
||||
|
||||
render(appHtml, app);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// INIT
|
||||
// ============================================================================
|
||||
async function initApp() {
|
||||
const app = document.getElementById("app");
|
||||
if (!app) throw new Error("App container not found");
|
||||
|
||||
// Show loading
|
||||
render(
|
||||
html`
|
||||
<div
|
||||
class="w-full h-screen flex items-center justify-center bg-background text-foreground"
|
||||
>
|
||||
<div class="text-muted-foreground">Loading...</div>
|
||||
</div>
|
||||
`,
|
||||
app,
|
||||
);
|
||||
|
||||
// TODO: Fix PersistentStorageDialog - currently broken
|
||||
// Request persistent storage
|
||||
// if (storage.sessions) {
|
||||
// await PersistentStorageDialog.request();
|
||||
// }
|
||||
|
||||
// Create ChatPanel
|
||||
chatPanel = new ChatPanel();
|
||||
|
||||
// Check for session in URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const sessionIdFromUrl = urlParams.get("session");
|
||||
|
||||
if (sessionIdFromUrl) {
|
||||
const loaded = await loadSession(sessionIdFromUrl);
|
||||
if (!loaded) {
|
||||
// Session doesn't exist, redirect to new session
|
||||
newSession();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await createAgent();
|
||||
}
|
||||
|
||||
renderApp();
|
||||
}
|
||||
|
||||
initApp();
|
||||
23
packages/web-ui/example/tsconfig.json
Normal file
23
packages/web-ui/example/tsconfig.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"*": ["./*"],
|
||||
"@mariozechner/pi-agent-core": ["../../agent/dist/index.d.ts"],
|
||||
"@mariozechner/pi-ai": ["../../ai/dist/index.d.ts"],
|
||||
"@mariozechner/pi-tui": ["../../tui/dist/index.d.ts"],
|
||||
"@mariozechner/pi-web-ui": ["../dist/index.d.ts"]
|
||||
},
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["../src"]
|
||||
}
|
||||
6
packages/web-ui/example/vite.config.ts
Normal file
6
packages/web-ui/example/vite.config.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss()],
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue