feat(prototypes): add pi-web-ui-lan prototype

LAN-accessible web UI for pi agent interaction.
This commit is contained in:
dan 2026-01-24 09:40:43 -08:00
parent 1e1965dc17
commit 131fb86852
7 changed files with 3072 additions and 0 deletions

View file

@ -0,0 +1,26 @@
# Pi Web UI (LAN prototype)
Minimal LAN-only web UI using `@mariozechner/pi-web-ui`.
## Run (LAN)
```bash
cd /home/dan/proj/skills/prototypes/pi-web-ui-lan
npm install
npm run dev:lan
```
Vite will print a LAN URL (e.g., `http://192.168.1.20:5173`). Open it on your phone connected to the same WiFi.
## Workflow
1. Start the dev server (`npm run dev:lan`).
2. Open the LAN URL on the phone.
3. Open Settings → Manage API Keys and add your provider key.
4. Chat in the UI.
## Notes / Limitations
- This prototype runs in **direct mode** (browser calls provider APIs directly).
- It does **not** access the local filesystem or repo yet.
- Next step for repo access: add a local RPC/agent backend and bridge it to the UI.

View file

@ -0,0 +1,12 @@
<!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 (LAN)</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View file

@ -0,0 +1,22 @@
{
"name": "pi-web-ui-lan",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"dev:lan": "vite --host 0.0.0.0 --port 5173",
"build": "vite build",
"preview": "vite preview --host 0.0.0.0 --port 5173"
},
"dependencies": {
"@mariozechner/pi-agent-core": "^0.49.3",
"@mariozechner/pi-ai": "^0.49.3",
"@mariozechner/pi-web-ui": "^0.49.3",
"lit": "^3.3.1"
},
"devDependencies": {
"typescript": "^5.7.3",
"vite": "^7.1.6"
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
import { Agent } from "@mariozechner/pi-agent-core";
import { getModel } from "@mariozechner/pi-ai";
import {
ApiKeyPromptDialog,
AppStorage,
ChatPanel,
IndexedDBStorageBackend,
ProviderKeysStore,
SessionsStore,
SettingsStore,
defaultConvertToLlm,
setAppStorage,
} from "@mariozechner/pi-web-ui";
import "@mariozechner/pi-web-ui/app.css";
const settings = new SettingsStore();
const providerKeys = new ProviderKeysStore();
const sessions = new SessionsStore();
const backend = new IndexedDBStorageBackend({
dbName: "pi-web-ui-lan",
version: 1,
stores: [settings.getConfig(), providerKeys.getConfig(), sessions.getConfig(), SessionsStore.getMetadataConfig()],
});
settings.setBackend(backend);
providerKeys.setBackend(backend);
sessions.setBackend(backend);
const storage = new AppStorage(settings, providerKeys, sessions, undefined, backend);
setAppStorage(storage);
const agent = new Agent({
initialState: {
systemPrompt: "You are a helpful assistant.",
model: getModel("google", "gemini-3-flash-preview"),
thinkingLevel: "off",
messages: [],
tools: [],
},
convertToLlm: defaultConvertToLlm,
});
const chatPanel = new ChatPanel();
await chatPanel.setAgent(agent, {
onApiKeyRequired: async (provider) => ApiKeyPromptDialog.prompt(provider),
});
document.getElementById("app")?.appendChild(chatPanel);

View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"useDefineForClassFields": false
},
"include": ["src/**/*"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from "vite";
export default defineConfig({
resolve: {
conditions: ["production", "browser", "module", "default"],
},
server: {
port: 5173,
},
});