import { i18n } from "@mariozechner/mini-lit"; import { Dialog, DialogContent, DialogHeader, } from "@mariozechner/mini-lit/dist/Dialog.js"; import { Input } from "@mariozechner/mini-lit/dist/Input.js"; import { Label } from "@mariozechner/mini-lit/dist/Label.js"; import { Switch } from "@mariozechner/mini-lit/dist/Switch.js"; import { getProviders } from "@mariozechner/pi-ai"; import { html, LitElement, type TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import "../components/ProviderKeyInput.js"; import { getAppStorage } from "../storage/app-storage.js"; // Base class for settings tabs export abstract class SettingsTab extends LitElement { abstract getTabName(): string; protected createRenderRoot() { return this; } } // API Keys Tab @customElement("api-keys-tab") export class ApiKeysTab extends SettingsTab { getTabName(): string { return i18n("API Keys"); } render(): TemplateResult { const providers = getProviders(); return html`

${i18n( "Configure API keys for LLM providers. Keys are stored locally in your browser.", )}

${providers.map( (provider) => html``, )}
`; } } // Proxy Tab @customElement("proxy-tab") export class ProxyTab extends SettingsTab { @state() private proxyEnabled = false; @state() private proxyUrl = "http://localhost:3001"; override async connectedCallback() { super.connectedCallback(); // Load proxy settings when tab is connected try { const storage = getAppStorage(); const enabled = await storage.settings.get("proxy.enabled"); const url = await storage.settings.get("proxy.url"); if (enabled !== null) this.proxyEnabled = enabled; if (url !== null) this.proxyUrl = url; } catch (error) { console.error("Failed to load proxy settings:", error); } } private async saveProxySettings() { try { const storage = getAppStorage(); await storage.settings.set("proxy.enabled", this.proxyEnabled); await storage.settings.set("proxy.url", this.proxyUrl); } catch (error) { console.error("Failed to save proxy settings:", error); } } getTabName(): string { return i18n("Proxy"); } render(): TemplateResult { return html`

${i18n( "Allows browser-based apps to bypass CORS restrictions when calling LLM providers. Required for Z-AI and Anthropic with OAuth token.", )}

${i18n("Use CORS Proxy")} ${Switch({ checked: this.proxyEnabled, onChange: (checked: boolean) => { this.proxyEnabled = checked; this.saveProxySettings(); }, })}
${Label({ children: i18n("Proxy URL") })} ${Input({ type: "text", value: this.proxyUrl, disabled: !this.proxyEnabled, onInput: (e) => { this.proxyUrl = (e.target as HTMLInputElement).value; }, onChange: () => this.saveProxySettings(), })}

${i18n( "Format: The proxy must accept requests as /?url=", )}

`; } } @customElement("settings-dialog") export class SettingsDialog extends LitElement { @property({ type: Array, attribute: false }) tabs: SettingsTab[] = []; @state() private isOpen = false; @state() private activeTabIndex = 0; protected createRenderRoot() { return this; } static async open(tabs: SettingsTab[]) { const dialog = new SettingsDialog(); dialog.tabs = tabs; dialog.isOpen = true; document.body.appendChild(dialog); } private setActiveTab(index: number) { this.activeTabIndex = index; } private renderSidebarItem(tab: SettingsTab, index: number): TemplateResult { const isActive = this.activeTabIndex === index; return html` `; } private renderMobileTab(tab: SettingsTab, index: number): TemplateResult { const isActive = this.activeTabIndex === index; return html` `; } render() { if (this.tabs.length === 0) { return html``; } return Dialog({ isOpen: this.isOpen, onClose: () => { this.isOpen = false; this.remove(); }, width: "min(1000px, 90vw)", height: "min(800px, 90vh)", backdropClassName: "bg-black/50 backdrop-blur-sm", children: html` ${DialogContent({ className: "h-full p-6", children: html`
${DialogHeader({ title: i18n("Settings") })}
${this.tabs.map((tab, index) => this.renderMobileTab(tab, index), )}
${this.tabs.map( (tab, index) => html`
${tab}
`, )}
`, })} `, }); } }