Basic skeleton for running an ag-ui demo using starlette and nextjs
This commit is contained in:
parent
b9c22e634c
commit
e3b07db8b9
26 changed files with 16682 additions and 0 deletions
23
examples/ag-ui-research/.env.example
Normal file
23
examples/ag-ui-research/.env.example
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# QA Provider for the research agent (ollama, openai, anthropic, etc.)
|
||||
QA_PROVIDER=ollama
|
||||
|
||||
# QA Model name
|
||||
QA_MODEL=gpt-oss:latest
|
||||
|
||||
# Ollama base URL (only needed if using ollama provider)
|
||||
# For Docker: http://host.docker.internal:11434
|
||||
# For local development: http://localhost:11434
|
||||
OLLAMA_BASE_URL=http://host.docker.internal:11434
|
||||
|
||||
# Path to the LanceDB database
|
||||
# For Docker: /app/data/haiku_rag.lancedb
|
||||
# For local development: ./haiku_rag.lancedb
|
||||
DB_PATH=haiku_rag.lancedb
|
||||
|
||||
# API keys (set as needed for your QA provider)
|
||||
# OPENAI_API_KEY=your-key-here
|
||||
# ANTHROPIC_API_KEY=your-key-here
|
||||
|
||||
# Embedding provider configuration (optional, defaults will be used)
|
||||
# EMBEDDING_PROVIDER=openai
|
||||
# EMBEDDING_MODEL=text-embedding-3-small
|
||||
47
examples/ag-ui-research/README.md
Normal file
47
examples/ag-ui-research/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Haiku.rag Interactive Research Assistant
|
||||
|
||||
Interactive research assistant powered by **Haiku.rag**, **Pydantic AI**, and **AG-UI** protocol. Ask complex questions and watch the multi-agent research process unfold in real-time with synchronized state between backend and frontend.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker and Docker Compose
|
||||
- Ollama running on host (or configure another QA provider)
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd haiku.rag/examples/ag-ui-research
|
||||
```
|
||||
|
||||
2. **Configure environment** (optional, defaults to Ollama with gpt-oss:latest)
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env to customize provider/model or add API keys
|
||||
```
|
||||
|
||||
See [haiku.rag configuration docs](https://ggozad.github.io/haiku.rag/configuration/) for provider setup.
|
||||
|
||||
3. **Prepare your knowledge base**
|
||||
|
||||
Create and populate a haiku.rag database:
|
||||
```bash
|
||||
# Create a data directory
|
||||
mkdir -p data
|
||||
# Add documents (requires haiku-rag installed locally)
|
||||
haiku-rag add "Your documents here" --db data/haiku_rag.lancedb
|
||||
# Or add from files
|
||||
haiku-rag add-src document.pdf --db data/haiku_rag.lancedb
|
||||
```
|
||||
|
||||
4. **Start the application**
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
5. **Open the application**
|
||||
- Frontend: http://localhost:3000
|
||||
12
examples/ag-ui-research/backend/.gitignore
vendored
Normal file
12
examples/ag-ui-research/backend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
.env
|
||||
.venv
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
27
examples/ag-ui-research/backend/Dockerfile
Normal file
27
examples/ag-ui-research/backend/Dockerfile
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Enable bytecode compilation
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
|
||||
# Copy from the cache instead of linking since it's a mounted volume
|
||||
ENV UV_LINK_MODE=copy
|
||||
|
||||
# Install dependencies
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||
uv sync --frozen --no-install-project --no-dev
|
||||
|
||||
# Copy the project into the image
|
||||
COPY . .
|
||||
|
||||
# Sync the project
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-dev
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
# Run with uv
|
||||
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||
17
examples/ag-ui-research/backend/README.md
Normal file
17
examples/ag-ui-research/backend/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Haiku.rag Research Assistant Backend
|
||||
|
||||
FastAPI backend for the haiku.rag interactive research assistant, using Pydantic AI with AG-UI protocol support.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
uv run python main.py
|
||||
```
|
||||
|
||||
The server starts on `http://localhost:8000` and uses [haiku.rag configuration](https://ggozad.github.io/haiku.rag/configuration/).
|
||||
|
||||
## Endpoints
|
||||
|
||||
- `GET /health` - Health check
|
||||
- `POST /agent` - AG-UI protocol endpoint
|
||||
70
examples/ag-ui-research/backend/agent.py
Normal file
70
examples/ag-ui-research/backend/agent.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"""Pydantic AI research agent for haiku.rag with AG-UI protocol."""
|
||||
|
||||
from ag_ui.core import EventType, StateSnapshotEvent
|
||||
from pydantic import BaseModel
|
||||
from pydantic_ai import Agent, RunContext
|
||||
from pydantic_ai.ag_ui import StateDeps
|
||||
|
||||
from haiku.rag.config import Config
|
||||
from haiku.rag.graph.common import get_model
|
||||
|
||||
|
||||
class ResearchState(BaseModel):
|
||||
"""Shared state between research agent and frontend."""
|
||||
|
||||
question: str = ""
|
||||
status: str = "idle"
|
||||
current_iteration: int = 0
|
||||
max_iterations: int = 2
|
||||
confidence: float = 0.0
|
||||
plan: list[dict] = []
|
||||
findings: list[dict] = []
|
||||
final_report: dict | None = None
|
||||
|
||||
|
||||
def _as_state_snapshot(ctx: RunContext[StateDeps[ResearchState]]) -> StateSnapshotEvent:
|
||||
"""Helper to create a state snapshot event for AG-UI."""
|
||||
return StateSnapshotEvent(type=EventType.STATE_SNAPSHOT, snapshot=ctx.deps.state)
|
||||
|
||||
|
||||
def create_agent(
|
||||
qa_provider: str = Config.QA_PROVIDER, qa_model: str = Config.QA_MODEL
|
||||
) -> Agent[StateDeps[ResearchState], str]:
|
||||
"""Create and configure the research agent.
|
||||
|
||||
Args:
|
||||
qa_provider: QA provider for the agent (default: from Config.QA_PROVIDER)
|
||||
qa_model: Model name to use (default: from Config.QA_MODEL)
|
||||
"""
|
||||
agent = Agent(
|
||||
model=get_model(qa_provider, qa_model),
|
||||
deps_type=StateDeps[ResearchState],
|
||||
instructions="""You are a research assistant powered by haiku.rag.
|
||||
|
||||
You help users conduct deep research on complex questions by:
|
||||
- Breaking down questions into sub-questions
|
||||
- Searching through a knowledge base
|
||||
- Evaluating findings for completeness and confidence
|
||||
- Synthesizing comprehensive reports with citations
|
||||
|
||||
The state is shared with the frontend application, showing research progress in real-time.
|
||||
|
||||
Currently, tools are placeholder stubs. Full integration with haiku.rag research pipeline
|
||||
will be implemented in the next phase.""",
|
||||
)
|
||||
|
||||
@agent.tool
|
||||
async def get_research_status(ctx: RunContext[StateDeps[ResearchState]]) -> dict:
|
||||
"""Get the current research state and progress."""
|
||||
return {
|
||||
"question": ctx.deps.state.question,
|
||||
"status": ctx.deps.state.status,
|
||||
"iteration": ctx.deps.state.current_iteration,
|
||||
"max_iterations": ctx.deps.state.max_iterations,
|
||||
"confidence": ctx.deps.state.confidence,
|
||||
"has_plan": len(ctx.deps.state.plan) > 0,
|
||||
"findings_count": len(ctx.deps.state.findings),
|
||||
"has_report": ctx.deps.state.final_report is not None,
|
||||
}
|
||||
|
||||
return agent
|
||||
63
examples/ag-ui-research/backend/main.py
Normal file
63
examples/ag-ui-research/backend/main.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
"""Main entry point for the haiku.rag AG-UI research assistant backend."""
|
||||
|
||||
from agent import ResearchState, create_agent
|
||||
from pydantic_ai.ag_ui import StateDeps
|
||||
from starlette.applications import Starlette
|
||||
from starlette.middleware import Middleware
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.routing import Mount, Route
|
||||
|
||||
from haiku.rag.config import Config
|
||||
|
||||
# Create research agent instance using haiku.rag config
|
||||
agent = create_agent()
|
||||
|
||||
|
||||
async def health(request):
|
||||
"""Health check endpoint."""
|
||||
return JSONResponse(
|
||||
{
|
||||
"status": "healthy",
|
||||
"agent_model": str(agent.model),
|
||||
"qa_provider": Config.QA_PROVIDER,
|
||||
"qa_model": Config.QA_MODEL,
|
||||
"ollama_base_url": Config.OLLAMA_BASE_URL,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# Convert PydanticAI agent to AG-UI compatible ASGI app
|
||||
ag_ui_app = agent.to_ag_ui(deps=StateDeps(ResearchState())) # type: ignore[arg-type]
|
||||
|
||||
# Mount the AG-UI app at /agent and add health endpoint
|
||||
app = Starlette(
|
||||
routes=[
|
||||
Route("/health", health),
|
||||
Mount("/agent", ag_ui_app),
|
||||
],
|
||||
middleware=[
|
||||
Middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:3000", "http://frontend:3000"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
print("Starting haiku.rag research assistant backend...")
|
||||
print(f"Agent model: {agent.model}")
|
||||
print(f"QA provider: {Config.QA_PROVIDER}")
|
||||
print(f"QA model: {Config.QA_MODEL}")
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=True,
|
||||
)
|
||||
26
examples/ag-ui-research/backend/pyproject.toml
Normal file
26
examples/ag-ui-research/backend/pyproject.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[project]
|
||||
name = "haiku-rag-research-assistant"
|
||||
version = "0.1.0"
|
||||
description = "Haiku.rag research assistant with AG-UI protocol support"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"starlette>=0.45.2",
|
||||
"uvicorn[standard]>=0.34.2",
|
||||
"pydantic-ai-slim[ag-ui,openai]>=1.1.0",
|
||||
"python-dotenv>=1.0.1",
|
||||
"haiku-rag>=0.12.1",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pyright>=1.1.406",
|
||||
"ruff>=0.13.0",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["."]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
4011
examples/ag-ui-research/backend/uv.lock
Normal file
4011
examples/ag-ui-research/backend/uv.lock
Normal file
File diff suppressed because it is too large
Load diff
50
examples/ag-ui-research/docker-compose.yml
Normal file
50
examples/ag-ui-research/docker-compose.yml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- QA_PROVIDER=${QA_PROVIDER:-ollama}
|
||||
- QA_MODEL=${QA_MODEL:-gpt-oss:latest}
|
||||
- DB_PATH=${DB_PATH:-/app/data/haiku_rag.lancedb}
|
||||
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
- /app/.venv
|
||||
- ./data:/app/data
|
||||
networks:
|
||||
- ag-ui-network
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- BACKEND_URL=http://backend:8000
|
||||
volumes:
|
||||
- ./frontend:/app
|
||||
- /app/node_modules
|
||||
- /app/.next
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- ag-ui-network
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
ag-ui-network:
|
||||
driver: bridge
|
||||
7
examples/ag-ui-research/frontend/.dockerignore
Normal file
7
examples/ag-ui-research/frontend/.dockerignore
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
node_modules
|
||||
.next
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
npm-debug.log
|
||||
.env*.local
|
||||
39
examples/ag-ui-research/frontend/.gitignore
vendored
Normal file
39
examples/ag-ui-research/frontend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# env files
|
||||
.env*.local
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
12
examples/ag-ui-research/frontend/Dockerfile
Normal file
12
examples/ag-ui-research/frontend/Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Development Dockerfile for Next.js frontend
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
|
||||
# Run in development mode with hot reload
|
||||
CMD ["npm", "run", "dev"]
|
||||
33
examples/ag-ui-research/frontend/app/api/copilotkit/route.ts
Normal file
33
examples/ag-ui-research/frontend/app/api/copilotkit/route.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { HttpAgent } from "@ag-ui/client";
|
||||
import {
|
||||
CopilotRuntime,
|
||||
copilotRuntimeNextJSAppRouterEndpoint,
|
||||
ExperimentalEmptyAdapter,
|
||||
} from "@copilotkit/runtime";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
// Service adapter for multi-agent support (empty since we only have one agent)
|
||||
const serviceAdapter = new ExperimentalEmptyAdapter();
|
||||
|
||||
// Connect CopilotKit to PydanticAI via HttpAgent
|
||||
// The HttpAgent creates a bridge between the Next.js frontend and the Python backend
|
||||
// It communicates with the server created by agent.to_ag_ui()
|
||||
const runtime = new CopilotRuntime({
|
||||
agents: {
|
||||
// "research_agent" maps to the agent name used in useCoAgent() on the frontend
|
||||
research_agent: new HttpAgent({
|
||||
url: `${process.env.BACKEND_URL || "http://backend:8000"}/agent`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
// Next.js API route handler that proxies requests between frontend and backend
|
||||
export async function POST(request: NextRequest) {
|
||||
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
|
||||
runtime,
|
||||
serviceAdapter,
|
||||
endpoint: "/api/copilotkit",
|
||||
});
|
||||
|
||||
return handleRequest(request);
|
||||
}
|
||||
24
examples/ag-ui-research/frontend/app/globals.css
Normal file
24
examples/ag-ui-research/frontend/app/globals.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
font-family:
|
||||
system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(to bottom, #f8f9fa, #e9ecef);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
20
examples/ag-ui-research/frontend/app/layout.tsx
Normal file
20
examples/ag-ui-research/frontend/app/layout.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Haiku.rag Research Assistant",
|
||||
description:
|
||||
"Interactive research powered by Haiku.rag, Pydantic AI, and AG-UI",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
9
examples/ag-ui-research/frontend/app/page.tsx
Normal file
9
examples/ag-ui-research/frontend/app/page.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import Agent from "@/components/Agent";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main>
|
||||
<Agent />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
34
examples/ag-ui-research/frontend/biome.json
Normal file
34
examples/ag-ui-research/frontend/biome.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab"
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"enabled": true,
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
122
examples/ag-ui-research/frontend/components/Agent.tsx
Normal file
122
examples/ag-ui-research/frontend/components/Agent.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
CopilotKit,
|
||||
useCoAgent,
|
||||
useCoAgentStateRender,
|
||||
} from "@copilotkit/react-core";
|
||||
import { CopilotSidebar } from "@copilotkit/react-ui";
|
||||
import "@copilotkit/react-ui/styles.css";
|
||||
import StateDisplay from "./StateDisplay";
|
||||
|
||||
interface ResearchState {
|
||||
question: string;
|
||||
status: string;
|
||||
current_iteration: number;
|
||||
max_iterations: number;
|
||||
confidence: number;
|
||||
plan: Array<Record<string, unknown>>;
|
||||
findings: Array<Record<string, unknown>>;
|
||||
final_report: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
function AgentContent() {
|
||||
// Use useCoAgent to sync state with the backend research agent
|
||||
const { state } = useCoAgent<ResearchState>({
|
||||
name: "research_agent",
|
||||
initialState: {
|
||||
question: "",
|
||||
status: "idle",
|
||||
current_iteration: 0,
|
||||
max_iterations: 2,
|
||||
confidence: 0.0,
|
||||
plan: [],
|
||||
findings: [],
|
||||
final_report: null,
|
||||
},
|
||||
});
|
||||
|
||||
// Render state updates from the research agent
|
||||
useCoAgentStateRender<ResearchState>({
|
||||
name: "research_agent",
|
||||
render: ({ state: newState }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
background: "#e6f7ff",
|
||||
borderRadius: "4px",
|
||||
marginBottom: "0.5rem",
|
||||
border: "1px solid #91d5ff",
|
||||
}}
|
||||
>
|
||||
<strong>Research Update:</strong> Status: {newState.status},
|
||||
Iteration: {newState.current_iteration}/{newState.max_iterations},
|
||||
Confidence: {(newState.confidence * 100).toFixed(0)}%
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", height: "100vh" }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "2rem",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: "800px",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
<header style={{ marginBottom: "2rem" }}>
|
||||
<h1
|
||||
style={{
|
||||
fontSize: "2.5rem",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "0.5rem",
|
||||
color: "#1a202c",
|
||||
}}
|
||||
>
|
||||
Haiku.rag Research Assistant
|
||||
</h1>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "1.125rem",
|
||||
color: "#4a5568",
|
||||
lineHeight: "1.6",
|
||||
}}
|
||||
>
|
||||
Interactive research powered by <strong>Haiku.rag</strong>,{" "}
|
||||
<strong>Pydantic AI</strong>, and <strong>AG-UI</strong>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<StateDisplay state={state} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CopilotSidebar
|
||||
defaultOpen={true}
|
||||
clickOutsideToClose={false}
|
||||
labels={{
|
||||
title: "Research Assistant",
|
||||
initial:
|
||||
"Hello! I can help you conduct deep research on complex questions using the haiku.rag knowledge base. Ask me anything!",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Agent() {
|
||||
return (
|
||||
<CopilotKit runtimeUrl="/api/copilotkit" agent="research_agent">
|
||||
<AgentContent />
|
||||
</CopilotKit>
|
||||
);
|
||||
}
|
||||
277
examples/ag-ui-research/frontend/components/StateDisplay.tsx
Normal file
277
examples/ag-ui-research/frontend/components/StateDisplay.tsx
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
"use client";
|
||||
|
||||
interface ResearchState {
|
||||
question: string;
|
||||
status: string;
|
||||
current_iteration: number;
|
||||
max_iterations: number;
|
||||
confidence: number;
|
||||
plan: Array<Record<string, unknown>>;
|
||||
findings: Array<Record<string, unknown>>;
|
||||
final_report: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
interface StateDisplayProps {
|
||||
state: ResearchState;
|
||||
}
|
||||
|
||||
export default function StateDisplay({ state }: StateDisplayProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
borderRadius: "8px",
|
||||
padding: "1.5rem",
|
||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
||||
marginTop: "2rem",
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
style={{
|
||||
fontSize: "1.5rem",
|
||||
fontWeight: "600",
|
||||
marginBottom: "1rem",
|
||||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
Research State
|
||||
</h2>
|
||||
<p
|
||||
style={{
|
||||
color: "#4a5568",
|
||||
lineHeight: "1.6",
|
||||
marginBottom: "1rem",
|
||||
fontSize: "0.875rem",
|
||||
}}
|
||||
>
|
||||
This state is shared between the research agent and the frontend via the
|
||||
AG-UI protocol.
|
||||
</p>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gap: "1rem",
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
background: "#f7fafc",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #e2e8f0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
marginBottom: "0.25rem",
|
||||
}}
|
||||
>
|
||||
Question
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "1.125rem",
|
||||
fontWeight: "bold",
|
||||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
{state.question || "No question yet"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(2, 1fr)",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
background: "#f7fafc",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #e2e8f0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
marginBottom: "0.25rem",
|
||||
}}
|
||||
>
|
||||
Status
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "1.125rem",
|
||||
fontWeight: "bold",
|
||||
color: state.status === "idle" ? "#718096" : "#38a169",
|
||||
}}
|
||||
>
|
||||
{state.status}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
background: "#f7fafc",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #e2e8f0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
marginBottom: "0.25rem",
|
||||
}}
|
||||
>
|
||||
Confidence
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "1.5rem",
|
||||
fontWeight: "bold",
|
||||
color:
|
||||
state.confidence > 0.8
|
||||
? "#38a169"
|
||||
: state.confidence > 0.5
|
||||
? "#d69e2e"
|
||||
: "#e53e3e",
|
||||
}}
|
||||
>
|
||||
{(state.confidence * 100).toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
background: "#f7fafc",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #e2e8f0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
marginBottom: "0.25rem",
|
||||
}}
|
||||
>
|
||||
Progress
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "1.125rem",
|
||||
fontWeight: "bold",
|
||||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
{state.current_iteration} / {state.max_iterations}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
background: "#f7fafc",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #e2e8f0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
marginBottom: "0.25rem",
|
||||
}}
|
||||
>
|
||||
Plan Items
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "1.125rem",
|
||||
fontWeight: "bold",
|
||||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
{state.plan.length}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
background: "#f7fafc",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #e2e8f0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
marginBottom: "0.25rem",
|
||||
}}
|
||||
>
|
||||
Findings
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "1.125rem",
|
||||
fontWeight: "bold",
|
||||
color: "#2d3748",
|
||||
}}
|
||||
>
|
||||
{state.findings.length}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
padding: "1rem",
|
||||
background: "#f7fafc",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #e2e8f0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.875rem",
|
||||
color: "#718096",
|
||||
marginBottom: "0.25rem",
|
||||
}}
|
||||
>
|
||||
Final Report
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "1.125rem",
|
||||
fontWeight: "bold",
|
||||
color: state.final_report ? "#38a169" : "#a0aec0",
|
||||
}}
|
||||
>
|
||||
{state.final_report ? "Ready" : "Not ready"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
examples/ag-ui-research/frontend/next.config.ts
Normal file
7
examples/ag-ui-research/frontend/next.config.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
11690
examples/ag-ui-research/frontend/package-lock.json
generated
Normal file
11690
examples/ag-ui-research/frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
29
examples/ag-ui-research/frontend/package.json
Normal file
29
examples/ag-ui-research/frontend/package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "ag-ui-frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"check": "biome check app components",
|
||||
"format": "biome check --write app components"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ag-ui/client": "^0.0.40",
|
||||
"@copilotkit/react-core": "^1.10.6",
|
||||
"@copilotkit/react-ui": "^1.10.6",
|
||||
"@copilotkit/runtime": "^1.10.6",
|
||||
"next": "15.5.5",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.2.6",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
0
examples/ag-ui-research/frontend/public/.gitkeep
Normal file
0
examples/ag-ui-research/frontend/public/.gitkeep
Normal file
27
examples/ag-ui-research/frontend/tsconfig.json
Normal file
27
examples/ag-ui-research/frontend/tsconfig.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
6
examples/ag-ui-research/package-lock.json
generated
Normal file
6
examples/ag-ui-research/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "haiku-ag-ui",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
Loading…
Reference in a new issue