> ## Documentation Index
> Fetch the complete documentation index at: https://autonomy.computer/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Coding Agents: How to create custom user interfaces for Autonomy apps

> Build custom user interfaces with Nextjs, Svelte, React, Vue, Angular, or other frameworks. Create streaming chat interfaces and integrate with APIs running on the Autonomy Computer for sophisticated AI applications.

# A guide for coding agents to create custom user interfaces for Autonomy apps

> **Key Terms**: See the [definitions section of the main guide](https://autonomy.computer/docs/_for-coding-agents.md#definitions).

*Keywords: custom ui, nextjs, react, streaming chat, fastapi, frontend*

Build custom user interfaces with modern web frameworks (Next.js, React, Vue, Svelte), serve static files with FastAPI, and create streaming chat interfaces with real-time AI responses.

## Define a custom UI

Autonomy apps can serve compiled static sites using FastAPI. The site calls APIs defined within the app.

### Project Structure

```bash theme={null}
your-app/
├── autonomy.yaml
├── images/main/
│   ├── Dockerfile
│   ├── main.py
│   └── public/           # Compiled UI files go here
└── ui/                   # UI source code goes here
    ├── package.json
    ...
```

Pick a framework that outputs static files (Next.js, React, Vue, Angular, Svelte), configure it to output static files, and copy the output to `images/main/public`.

### Package Manager and Build Scripts

```json theme={null}
{
  "scripts": {
    "dev": "[framework dev command]",
    "build": "[framework build command]",
    "build-autonomy": "npm run build && rm -rf ../images/main/public/* && cp -r [output-dir]/* ../images/main/public/",
    "security-check": "npm audit && npm outdated",
    ...
  }
}
```

### FastAPI Backend Configuration

Configure `images/main/main.py`:

```python theme={null}
from autonomy import Agent, HttpServer, Model, Node, NodeDep
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
import os
import time

app = FastAPI()

class ChatRequest(BaseModel):
    text: str

@app.post("/api/chat")
async def chat(request: ChatRequest, node: NodeDep):
    agent_name = f"chat_{int(time.time() * 1000)}"

    try:
        agent = await Agent.start(
            node=node,
            name=agent_name,
            instructions="You are a helpful assistant.",
            model=Model("claude-sonnet-4-v1")
        )

        response = await agent.send(request.text)
        reply = response[-1].content.text

        return {"reply": reply}

    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

    finally:
        if agent_name:
            await Agent.stop(node, agent_name)

# Serve static files (must be last)
if os.path.exists("public"):
    app.mount("/", StaticFiles(directory="public", html=True), name="static")

Node.start(http_server=HttpServer(app=app))
```

### UI Integration Pattern

Basic API integration (framework-agnostic):

```javascript theme={null}
// Fetch data from Autonomy backend
async function sendMessage(text) {
  const response = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ text })
  })

  if (!response.ok) {
    throw new Error('Failed to send message')
  }

  return await response.json()
}

// Example usage in component
function handleSubmit(text) {
  sendMessage(text)
    .then(data => {
      // Update UI with data.reply
    })
    .catch(error => {
      console.error('Error:', error)
      // Handle error in UI
    })
}
```

## Building a Custom Streaming API and UI

When building AI applications, providing real-time streaming responses creates a much better user experience than waiting for complete responses. This guide shows you how to build a minimal but effective streaming chat interface that displays responses character by character.

### Backend: Streaming API with Autonomy

Here's a minimal FastAPI server that streams AI responses:

```python theme={null}
from autonomy import Agent, HttpServer, Model, Node, NodeDep
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json
from dataclasses import asdict, is_dataclass
from enum import Enum

def json_serializer(obj):
    if is_dataclass(obj):
        return asdict(obj)
    if isinstance(obj, Enum):
        return obj.value
    if hasattr(obj, '__dict__'):
        return obj.__dict__
    return str(obj)

app = FastAPI()
agent = None

@app.post("/chat")
async def chat(request: dict, node: NodeDep):
    global agent

    # Initialize agent once and reuse
    if not agent:
        try:
            agent = await Agent.start(
                node=node,
                name="streaming-agent",
                instructions="You are a helpful assistant. Give clear, engaging responses.",
                model=Model("claude-sonnet-4-v1")
            )
        except Exception as e:
            if "AlreadyExists" not in str(e):
                raise e

    message = request.get("message", "")
    scope = request.get("scope", None)
    conversation = request.get("conversation", None)

    async def stream_response():
        try:
            async for response in agent.send_stream(message, scope, conversation, timeout=60):
                yield json.dumps(response.snippet, default=json_serializer) + "\n"
        except Exception as e:
            yield json.dumps({"type": "error", "message": str(e)}) + "\n"

    return StreamingResponse(stream_response(), media_type="application/json")

Node.start(http_server=HttpServer(app=app))
```

**Key Concepts**: Agent reuse avoids setup overhead. Use `StreamingResponse` with newline-delimited JSON. Handle errors gracefully with custom JSON serializer for complex objects.

### Frontend: Character-by-Character Streaming

```js theme={null}
async function appendCharByChar(text, delay = 5) {
    for (const char of text) {
        out.textContent += char;
        await new Promise((resolve) => setTimeout(resolve, delay));
        chatArea.scrollTop = chatArea.scrollHeight;
    }
}

async function processLine(line) {
    if (line.trim()) {
        const parsed = JSON.parse(line);
        for (const message of parsed.messages) {
            if (message?.content?.text) {
                await appendCharByChar(message.content.text);
            }
        }
    }
}

async function send(message) {
    input.disabled = true;
    addMessage(message, true);
    out = createAssistantMessage();
    const response = await fetch("/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ message }),
    });
    const decoder = new TextDecoder();
    let buffer = "";
    for await (const chunk of response.body) {
        buffer += decoder.decode(chunk, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop();
        if (!out.textContent) input.value = "";
        for (const line of lines) await processLine(line);
    }
    await processLine(buffer);
    input.disabled = false;
    input.focus();
}
```

**How it works**: `appendCharByChar` iterates each character with 5ms delay. Auto-scrolls on each character. Buffers incomplete JSON lines. Disables input during responses.

### Complete HTML Structure

```html theme={null}
<!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Minimal Streaming Chat</title>
        <style>
            body {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                max-width: 800px;
                margin: 0 auto;
                padding: 20px;
                background: #f5f5f5;
            }

            .container {
                background: white;
                border-radius: 12px;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
                overflow: hidden;
            }

            .chat-area {
                height: 400px;
                overflow-y: auto;
                padding: 20px;
                background: #fafafa;
            }

            .message {
                margin: 15px 0;
                padding: 12px 16px;
                border-radius: 18px;
                max-width: 80%;
                word-wrap: break-word;
            }

            .user-message {
                background: #007bff;
                color: white;
                margin-left: auto;
                text-align: right;
            }

            .assistant-message {
                background: white;
                border: 1px solid #e1e5e9;
                margin-right: auto;
            }

            .input-container {
                padding: 20px;
                background: white;
                border-top: 1px solid #e1e5e9;
                display: flex;
                gap: 12px;
            }

            #in {
                flex: 1;
                padding: 12px 16px;
                border: 2px solid #e1e5e9;
                border-radius: 24px;
                outline: none;
                font-size: 14px;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="header">
                <h1>Minimal Streaming Chat</h1>
                <p>Chat with an AI assistant using streaming responses</p>
            </div>
            <div id="chatArea" class="chat-area"></div>
            <div class="input-container">
                <input type="text" id="in" placeholder="Type your message here..." />
                <button id="sendButton">Send</button>
            </div>
        </div>
        <!-- JavaScript continues... -->
    </body>
</html>
```

**Key Takeaways**: Character-by-character with `setTimeout` is simple and reliable. Buffer incomplete JSON lines. Reuse agents. Stream errors as JSON. Disable input during responses and auto-scroll.

## Related Guides

* [How to create custom APIs](https://autonomy.computer/docs/_for-coding-agents/create-custom-apis.md)
* [How to use memory, conversation, and scope in Autonomy agents](https://autonomy.computer/docs/_for-coding-agents/memory.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.