diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml new file mode 100644 index 0000000..491999e --- /dev/null +++ b/.github/workflows/demo.yml @@ -0,0 +1,50 @@ +name: Demo + +# The live demo on GitHub Pages: a static build of the UI whose API answers +# and "AI" run in the visitor's browser (demo/). No server, no keys. +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + name: Build and test the demo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + - run: npm ci + - run: npm run build:demo + - run: npx playwright install --with-deps chromium + - run: npx playwright test -c playwright.demo.config.ts + env: + DEMO_SKIP_BUILD: '1' + - uses: actions/configure-pages@v6 + - uses: actions/upload-pages-artifact@v5 + with: + path: node_modules/.cache/opencode-web-demo/public + + deploy: + name: Deploy to GitHub Pages + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v5 diff --git a/README.md b/README.md index 2f8987e..15ad8f9 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Built with **Nuxt 4 + Nuxt UI**, keeping the opencode look & feel. +**[▶ Try the live demo](https://juanmandev.github.io/opencode-web/)**: the real UI running entirely in your browser with a tiny scripted AI. Ask it to "fix the bug", "run the tests" or "delegate a review to a subagent". No server, no keys, nothing leaves your browser. + Chat view Screenshots are generated by CI against a mock opencode server and refreshed on every release. @@ -31,6 +33,7 @@ One origin means no CORS, no double auth, and SSE streaming included. **It works | --- | --- | | 🗂️ **Project picker** | Start page with known projects and recents. Open any folder by path or browse the server filesystem. Everything is scoped per project via opencode's `?directory=` API. | | 💬 **Live chat** | SSE streaming with markdown, collapsible thinking, tool calls with input/output, per-step tokens & cost, timestamps, abort, a prompt queue, and permission and question prompts that survive reloads. Message actions: copy, read aloud, edit & resend, fork. | +| ⌨️ **Autocomplete like opencode** | `@` mentions files, folders and subagents (attached the way opencode does), `/` runs commands, `!` switches to shell mode. On phones the options row hides while the keyboard is open. | | 🧠 **Model + think level** | Model picker across all providers with pricing & context details, think-level selector for reasoning models, agent picker. Persisted per project. | | 🔌 **MCP manager** | Per-project and global MCP status, off / ask / auto modes per server and per tool, presets, and add remote or local servers from a catalog, a URL, a command line, pasted JSON or a form. **Per-conversation MCP selection** in the prompt box. Tool lists are discovered from the servers themselves over **Streamable HTTP, legacy SSE or stdio**. Every server has a **details view**: why it fails and how to fix it, its tools with full descriptions and parameters, an **editor** that never exposes stored secrets, and a step-by-step **connection test**. [More →](docs/mcp.md) | | 🤖 **Agent control** | One click registers this app's MCP server with opencode, so **the chat can manage opencode itself**: test, add and reconnect MCP servers, reload the config (after running replies finish), read and prompt other chats, see task lists, and manage schedules. State-changing tools ask first. [More →](docs/mcp.md#agent-control) | @@ -65,6 +68,32 @@ One origin means no CORS, no double auth, and SSE streaming included. **It works Fullscreen app viewer Apps on a phone + + A failing MCP server explains why + Tools of an MCP server with parameters + MCP settings on a phone + + + Diagnose & fix a server + Tools with parameters + Settings on a phone + + + Subagent card in the chat + Orchestrator with a subagent per MCP server + Scheduled prompts + + + Subagents in the chat + Subagent per MCP + Scheduled prompts + + + The live demo: a scripted in-browser AI fixing a bug + + + Live demo: everything runs in your browser + ## Quick start (Docker Compose) @@ -208,6 +237,8 @@ NUXT_OPENCODE_URL=http://127.0.0.1:4517 npm run dev npm run typecheck # vue-tsc npm run test:unit # vitest: MCP client (HTTP + SSE), redaction, directory encoding npm run build && npm run test:e2e # playwright e2e against the mock, incl. MCP UI apps and mobile +npm run build:demo && node demo/serve.mjs # the static in-browser demo at http://127.0.0.1:3340/opencode-web/ +npm run test:demo # its Playwright suite SCREENSHOTS=1 npm run test:e2e # also regenerate docs/screenshots # real opencode (any version) + a mock model; COMPAT_PROTOCOL=v2 forces the v2 protocol OPENCODE_VERSION=1.18.34 npx playwright test -c playwright.compat.config.ts diff --git a/app/assets/css/main.css b/app/assets/css/main.css index df07a06..d2357ea 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -196,6 +196,28 @@ body { right: 0; } +/* action rows: one line on wide screens; on phones every button takes the + full width. `.oc-actions` (modal footers, primary action last in the + markup) stacks with the primary on top; `.oc-actions-col` keeps order */ +.oc-actions, +.oc-actions-col { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; + width: 100%; +} +.oc-actions { justify-content: flex-end; } +@media (max-width: 639px) { + .oc-actions { flex-direction: column-reverse; align-items: stretch; } + .oc-actions-col { flex-direction: column; align-items: stretch; } + .oc-actions > *, + .oc-actions-col > * { width: 100%; justify-content: center; } + .oc-actions > .oc-spacer, + .oc-actions-col > .oc-spacer { display: none; } + .oc-actions-col > .oc-note { text-align: center; } +} + /* nav tiles in the project sidebar */ .oc-tile { transition: background-color 0.15s ease, color 0.15s ease, transform 0.12s ease; diff --git a/app/components/AgentControlCard.vue b/app/components/AgentControlCard.vue index 7a7ca84..f171bc8 100644 --- a/app/components/AgentControlCard.vue +++ b/app/components/AgentControlCard.vue @@ -94,7 +94,7 @@ const TOOLS = [ > -
+
- Saved in the global config; new chats get the tools. + Saved in the global config; new chats get the tools.
diff --git a/app/components/McpNestCard.vue b/app/components/McpNestCard.vue index 8ad1794..0a29b31 100644 --- a/app/components/McpNestCard.vue +++ b/app/components/McpNestCard.vue @@ -210,10 +210,10 @@ function fmt(ts?: number) { /> -
+
- + diff --git a/app/components/McpServerModal.vue b/app/components/McpServerModal.vue index 0e116f3..d5a5b9b 100644 --- a/app/components/McpServerModal.vue +++ b/app/components/McpServerModal.vue @@ -571,10 +571,10 @@ const title = computed(() => creating.value ? 'Add MCP server' : props.name || ' - -
- - + +
+ +
@@ -668,10 +668,10 @@ const title = computed(() => creating.value ? 'Add MCP server' : props.name || ' -
+
Save to - - + + defined in the {{ info.definedIn }} config: saving here adds an override @@ -740,7 +740,7 @@ const title = computed(() => creating.value ? 'Add MCP server' : props.name || '