@ottocode/web-ui
v0.1.275
Published
Embeddable web UI for ottocode - pre-built static assets
Readme
@ottocode/web-ui
Pre-built, embeddable web UI for ottocode. This package contains the fully-built static assets from the ottocode web interface, ready to be served by any web server or framework.
Features
- 🎯 One-Line Integration - Import and serve with a single function call
- 📦 Pre-built Assets - No build step required in your project
- 🚀 Framework Agnostic - Works with Bun, Express, Fastify, Hono, or any HTTP server
- 🎨 Full Featured - Complete ottocode web interface with all functionality
- 📱 Responsive - Modern, mobile-friendly UI built with React and Tailwind CSS
- ⚡ Fast - Optimized production build with code splitting
- 🛣️ Smart Routing - Handles SPA routing and direct asset requests automatically
Installation
npm install @ottocode/web-ui
# or
yarn add @ottocode/web-ui
# or
pnpm add @ottocode/web-ui
# or
bun add @ottocode/web-uiQuick Start
Ultra-Simple (Recommended)
Just one line - everything is handled for you:
import { serveWebUI } from '@ottocode/web-ui';
Bun.serve({
port: 3000,
idleTimeout: 240, // IMPORTANT: prevents SSE timeout
fetch: serveWebUI({ prefix: '/ui' })
});
console.log('Web UI: http://localhost:3000/ui');⚠️ Important: Always set
idleTimeout: 240(or higher) inBun.serve()to prevent SSE connection timeouts. The web UI uses Server-Sent Events for real-time streaming, and Bun's default timeout of 10 seconds will cause connections to drop.
That's it! The web UI will be available at /ui with:
- ✅ Automatic SPA routing
- ✅ Asset path handling (both
/ui/assets/*and/assets/*) - ✅ Proper MIME types
- ✅ 404 fallbacks
- ✅ Real-time SSE streaming
With Custom Routes
Combine the web UI with your own API routes:
import { serveWebUI } from '@ottocode/web-ui';
const webUI = serveWebUI({ prefix: '/ui' });
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
// Your API routes
if (url.pathname === '/api/hello') {
return new Response(JSON.stringify({ message: 'Hello!' }), {
headers: { 'Content-Type': 'application/json' }
});
}
// Try web UI handler
const webUIResponse = await webUI(req);
if (webUIResponse) return webUIResponse;
// Final fallback
return new Response('Not found', { status: 404 });
}
});With Root Redirect
Automatically redirect / to /ui:
import { serveWebUI } from '@ottocode/web-ui';
Bun.serve({
port: 3000,
fetch: serveWebUI({
prefix: '/ui',
redirectRoot: true // '/' → '/ui'
})
});Different Prefix
Serve the UI at any path you want:
import { serveWebUI } from '@ottocode/web-ui';
Bun.serve({
port: 3000,
fetch: serveWebUI({ prefix: '/admin' })
});
console.log('Web UI: http://localhost:3000/admin');Custom Server URL
When serving both the API and web UI from the same server, you can configure the web UI to connect to your server instead of the default localhost:9100:
import { createApp } from '@ottocode/server';
import { serveWebUI } from '@ottocode/web-ui';
const port = parseInt(process.env.PORT || '3000', 10);
const host = process.env.HOST || '127.0.0.1';
const app = createApp();
const handleWebUI = serveWebUI({
prefix: '/ui',
serverUrl: `http://${host}:${port}`, // Explicit server URL
});
// Or let it auto-detect (recommended for same-server setup):
// const handleWebUI = serveWebUI({ prefix: '/ui' });
const server = Bun.serve({
port,
hostname: host,
async fetch(req) {
// Serve web UI first
const webUIResponse = await handleWebUI(req);
if (webUIResponse) return webUIResponse;
// Then API routes
return app.fetch(req);
},
});
console.log(`Server: http://${host}:${server.port}/ui`);Note: If you don't specify
serverUrl, the web UI will automatically detect the server URL from the incoming request. This is recommended when serving both the API and UI from the same server.
API Reference
serveWebUI(options?): (req: Request) => Promise<Response | null>
Creates a request handler that serves the web UI.
Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| prefix | string | '/ui' | URL prefix for the web UI |
| redirectRoot | boolean | false | Redirect / to the prefix |
| onNotFound | (req: Request) => Response \| null | null | Custom 404 handler |
| serverUrl | string | Auto-detected | API server URL for the web UI to connect to. If not provided, auto-detects from request (e.g., http://localhost:3000) |
Returns: A request handler function that returns:
Responseif the request matches a web UI routenullif the request should be handled by other routes
Example:
const handler = serveWebUI({
prefix: '/dashboard',
redirectRoot: true,
onNotFound: (req) => new Response('UI not found', { status: 404 })
});
Bun.serve({ port: 3000, fetch: handler });getWebUIPath(): string
Returns the absolute path to the directory containing all built web UI assets.
import { getWebUIPath } from '@ottocode/web-ui';
const assetsPath = getWebUIPath();
// => '/path/to/node_modules/@ottocode/web-ui/dist/web-assets'getIndexPath(): string
Returns the absolute path to the main index.html file.
import { getIndexPath } from '@ottocode/web-ui';
const indexPath = getIndexPath();
// => '/path/to/node_modules/@ottocode/web-ui/dist/web-assets/index.html'isWebUIAvailable(): boolean
Checks if the web UI assets are properly built and available.
import { isWebUIAvailable } from '@ottocode/web-ui';
if (!isWebUIAvailable()) {
console.error('Web UI assets not found!');
process.exit(1);
}Framework Examples
Bun (Recommended)
import { serveWebUI } from '@ottocode/web-ui';
Bun.serve({
port: 3000,
fetch: serveWebUI({ prefix: '/ui' })
});Express
import express from 'express';
import { getWebUIPath, getIndexPath } from '@ottocode/web-ui';
const app = express();
// Serve static assets
app.use('/ui', express.static(getWebUIPath()));
// SPA fallback
app.get('/ui/*', (req, res) => {
res.sendFile(getIndexPath());
});
app.listen(3000);Fastify
import Fastify from 'fastify';
import fastifyStatic from '@fastify/static';
import { getWebUIPath } from '@ottocode/web-ui';
const fastify = Fastify();
await fastify.register(fastifyStatic, {
root: getWebUIPath(),
prefix: '/ui/',
});
await fastify.listen({ port: 3000 });Hono
import { Hono } from 'hono';
import { serveStatic } from 'hono/bun';
import { getWebUIPath } from '@ottocode/web-ui';
const app = new Hono();
app.use('/ui/*', serveStatic({ root: getWebUIPath() }));
export default app;Node.js HTTP
import { createServer } from 'http';
import { serveWebUI } from '@ottocode/web-ui';
const handler = serveWebUI({ prefix: '/ui' });
createServer(async (req, res) => {
const request = new Request(`http://localhost${req.url}`);
const response = await handler(request);
if (response) {
res.writeHead(response.status, Object.fromEntries(response.headers));
res.end(await response.text());
} else {
res.writeHead(404);
res.end('Not found');
}
}).listen(3000);How It Works
The serveWebUI() function handles all the complexity for you:
- Prefixed Routes (
/ui/*): Strips the prefix and serves the requested file - Direct Asset Requests (
/assets/*,/vite.svg, etc.): Serves assets directly (for when HTML references them) - SPA Fallback: Returns
index.htmlfor any unmatched routes under the prefix - Security: Prevents directory traversal attacks
- MIME Types: Automatically sets correct Content-Type headers
- Cross-Runtime: Works in both Bun and Node.js
This pattern solves the common issue where Vite-built apps reference assets like /assets/index-*.js directly, which would 404 without special handling.
Important Notes
Single Page Application (SPA)
The web UI is a React-based SPA with client-side routing. The serveWebUI() handler automatically:
- Serves static assets from the web UI path
- Falls back to
index.htmlfor client-side routes - Handles both prefixed (
/ui/assets/*) and direct (/assets/*) asset requests
API Configuration
The web UI expects an API to be available. By default, it will try to connect to:
http://localhost:3000/api(in development)- Same origin
/api(in production)
You'll need to set up your API endpoints to handle ottocode requests. See the ottocode documentation for API implementation details.
CORS
If your API is on a different origin than the web UI, you'll need to configure CORS headers:
Bun.serve({
port: 3000,
async fetch(req) {
// Your routes...
const response = await handler(req);
// Add CORS headers
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return response;
}
});Examples
See the examples directory for complete working examples:
- Bun Server - Minimal example using Bun's HTTP server with
serveWebUI() - Express Server - Traditional Express.js integration
Development
This package is part of the ottocode monorepo. To build from source:
# Clone the repository
git clone https://github.com/yourusername/ottocode
cd ottocode
# Install dependencies
bun install
# Build the package
cd packages/web-ui
bun run buildThe build process:
- Builds the web app from
apps/webusing Vite - Copies the production build to
dist/web-assets - Compiles the TypeScript exports
- Generates type declarations
What's Included
The package includes:
- ✅ Complete ottocode web interface
- ✅ React 19 with optimized production build
- ✅ TailwindCSS for styling
- ✅ Code syntax highlighting
- ✅ Markdown rendering
- ✅ Real-time API communication
- ✅ Responsive mobile design
- ✅ Dark mode support (if configured)
- ✅ Smart request handler with automatic routing
Bundle Size
The production build is optimized and includes:
- Main JS bundle: ~1.1 MB (370 KB gzipped)
- CSS: ~31 KB (6.5 KB gzipped)
- Total initial load: ~376 KB gzipped
Browser Support
The web UI supports all modern browsers:
- Chrome/Edge (last 2 versions)
- Firefox (last 2 versions)
- Safari (last 2 versions)
Migration from Manual Setup
If you were using the old manual approach:
Before:
import { getWebUIPath, getIndexPath } from '@ottocode/web-ui';
// 50 lines of routing logic...After:
import { serveWebUI } from '@ottocode/web-ui';
Bun.serve({
port: 3000,
fetch: serveWebUI({ prefix: '/ui' })
});License
MIT
Related
- ottocode - The main CLI tool
- ottocode API Documentation - API implementation guide
Support
For issues, questions, or contributions:
