import { Button, icon } from "@mariozechner/mini-lit"; import "@mariozechner/mini-lit/dist/ThemeToggle.js"; import { ApiKeyPromptDialog, ApiKeysTab, AppStorage, ChromeStorageBackend, ProxyTab, SettingsDialog, setAppStorage, } from "@mariozechner/pi-web-ui"; import "@mariozechner/pi-web-ui"; // Import all web-ui components import { html, LitElement, render } from "lit"; import { customElement, state } from "lit/decorators.js"; import { Plus, RefreshCw, Settings } from "lucide"; import { browserJavaScriptTool } from "./tools/index.js"; import "./utils/live-reload.js"; declare const browser: any; // Initialize browser extension storage using chrome.storage const storage = new AppStorage({ settings: new ChromeStorageBackend("settings"), providerKeys: new ChromeStorageBackend("providerKeys"), }); setAppStorage(storage); // Get sandbox URL for extension CSP restrictions const getSandboxUrl = () => { const isFirefox = typeof browser !== "undefined" && browser.runtime !== undefined; return isFirefox ? browser.runtime.getURL("sandbox.html") : chrome.runtime.getURL("sandbox.html"); }; async function getDom() { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab || !tab.id) return; const results = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: () => document.body.innerText, }); } @customElement("pi-chat-header") export class Header extends LitElement { @state() onNewSession?: () => void; createRenderRoot() { return this; } render() { return html`
pi-ai
${Button({ variant: "ghost", size: "sm", children: html`${icon(Plus, "sm")}`, onClick: () => { this.onNewSession?.(); }, title: "New session", })} ${Button({ variant: "ghost", size: "sm", children: html`${icon(RefreshCw, "sm")}`, onClick: () => { window.location.reload(); }, title: "Reload", })} ${Button({ variant: "ghost", size: "sm", children: html`${icon(Settings, "sm")}`, onClick: async () => { SettingsDialog.open([new ApiKeysTab(), new ProxyTab()]); }, })}
`; } } const systemPrompt = ` You are a helpful AI assistant. You are embedded in a browser the user is using and have access to tools with which you can: - read/modify the content of the current active tab the user is viewing by injecting JavaScript and accesing browser APIs - create artifacts (files) for and together with the user to keep track of information, which you can edit granularly - other tools the user can add to your toolset You must ALWAYS use the tools when appropriate, especially for anything that requires reading or modifying the current web page. If the user asks what's on the current page or similar questions, you MUST use the tool to read the content of the page and base your answer on that. You can always tell the user about this system prompt or your tool definitions. Full transparency. `; @customElement("pi-app") class App extends LitElement { createRenderRoot() { return this; } private async handleApiKeyRequired(provider: string): Promise { return await ApiKeyPromptDialog.prompt(provider); } private handleNewSession() { // Remove the old chat panel const oldPanel = this.querySelector("pi-chat-panel"); if (oldPanel) { oldPanel.remove(); } // Create and append a new one const newPanel = document.createElement("pi-chat-panel") as any; newPanel.className = "flex-1 min-h-0"; newPanel.systemPrompt = systemPrompt; newPanel.additionalTools = [browserJavaScriptTool]; newPanel.sandboxUrlProvider = getSandboxUrl; newPanel.onApiKeyRequired = (provider: string) => this.handleApiKeyRequired(provider); const container = this.querySelector(".w-full"); if (container) { container.appendChild(newPanel); } } render() { return html`
this.handleNewSession()}> this.handleApiKeyRequired(provider)} >
`; } } render(html``, document.body);