chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:18 +08:00
commit 6d5d58c1a9
18293 changed files with 3502153 additions and 0 deletions
@@ -0,0 +1,8 @@
node_modules
.next
__pycache__
*.pyc
.git
.env
.env.local
agent/.venv
@@ -0,0 +1,51 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnpm-store
/.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*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
.mastra/
# python
venv
.venv
__pycache__
@@ -0,0 +1,55 @@
# Stage 1: Build Next.js frontend
FROM node:20-slim AS frontend
WORKDIR /app
COPY package.json ./
RUN npm install --ignore-scripts
# Fix CVE: Railway scans package.json, so we update it too
RUN npm install next@latest && \
node -e "const p=require('./package.json'); p.dependencies.next='^16'; require('fs').writeFileSync('package.json',JSON.stringify(p,null,2))"
COPY src/ ./src/
COPY public/ ./public/
COPY next.config.ts tsconfig.json postcss.config.mjs ./
RUN npm run build
# Stage 2: Production image with Python + Node
FROM python:3.12-slim AS runner
# Install Node.js 20 + uv
RUN apt-get update && \
apt-get install -y --no-install-recommends curl ca-certificates && \
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y --no-install-recommends nodejs && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Install uv by copying from the official image (avoids curl|sh pipe-swallow bug
# where a 5xx on astral.sh silently produces an exit-0 layer with no uv binary).
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
ENV PATH="/root/.local/bin:$PATH"
WORKDIR /app
# Install Python dependencies from lockfile
COPY agent/ ./agent/
RUN cd agent && uv sync --frozen
# Copy Next.js build output and production node_modules
COPY --from=frontend /app/.next ./.next
COPY --from=frontend /app/node_modules ./node_modules
COPY --from=frontend /app/package.json ./
COPY --from=frontend /app/public ./public
# Copy entrypoint
COPY entrypoint.sh ./
RUN chmod +x entrypoint.sh
EXPOSE 3000
ENV NODE_ENV=production
CMD ["./entrypoint.sh"]
@@ -0,0 +1,21 @@
The MIT License
Copyright (c) Atai Barkai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,132 @@
# CopilotKit <> Microsoft Agent Framework (Python)
This is a starter template for building CopilotKit experiences using the [Microsoft Agent Framework](https://aka.ms/agent-framework). It ships with a Next.js UI and a FastAPI server that exposes a Microsoft Agent Framework agent over the AG-UI protocol, so you can study and customize both sides of the stack.
## Prerequisites
- OpenAI or Azure OpenAI credentials (for the Microsoft Agent Framework agent)
- Python 3.12+
- uv
- Node.js 20+
- Any of the following package managers:
- npm (default)
- [pnpm](https://pnpm.io/installation)
- [yarn](https://classic.yarnpkg.com/lang/en/docs/install/)
- [bun](https://bun.sh/)
## Getting Started
1. Install dependencies using your preferred package manager:
```bash
# Using npm (default)
npm install
# Using pnpm
pnpm install
# Using yarn
yarn install
# Using bun
bun install
```
> **Note:** This automatically sets up the Python environment as well.
>
> If you have manual issues, you can run:
>
> ```sh
> npm run install:agent
> ```
2. Set up your agent credentials. The backend automatically uses Azure when the Azure env vars below are present; otherwise it falls back to OpenAI. Create a `.env` file inside the `agent` folder with one of the following configurations:
**OpenAI**
```
OPENAI_API_KEY=sk-...your-openai-key-here...
OPENAI_CHAT_MODEL_ID=gpt-4o-mini
```
**Azure OpenAI**
```
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=gpt-4o-mini
# If you are not relying on az login:
# AZURE_OPENAI_API_KEY=...
```
3. Start the development server:
```bash
# Using npm (default)
npm run dev
# Using pnpm
pnpm dev
# Using yarn
yarn dev
# Using bun
bun run dev
```
This will start both the UI and the Microsoft Agent Framework server concurrently.
## Available Scripts
The following scripts can also be run using your preferred package manager:
- `dev` Starts both UI and agent servers in development mode
- `dev:debug` Starts development servers with debug logging enabled
- `dev:ui` Starts only the Next.js UI server
- `dev:agent` Starts only the Microsoft Agent Framework server
- `build` Builds the Next.js application for production
- `start` Starts the production server
- `lint` Runs ESLint for code linting
- `install:agent` Installs Python dependencies for the agent
## Documentation
The main UI component is in `src/app/page.tsx`. You can:
- Modify the theme colors and styling
- Add new frontend actions
- Customize the CopilotKit sidebar appearance
## 📚 Documentation
- [Microsoft Agent Framework](https://aka.ms/agent-framework) Learn more about Microsoft Agent Framework and its features
- [CopilotKit Documentation](https://docs.copilotkit.ai) Explore CopilotKits capabilities
- [Next.js Documentation](https://nextjs.org/docs) Learn about Next.js features and API
## Contributing
Feel free to submit issues and enhancement requests! This starter is designed to be easily extensible.
## License
This project is licensed under the MIT License see the LICENSE file for details.
## Troubleshooting
### Agent Connection Issues
If you see "I'm having trouble connecting to my tools", make sure:
1. The Microsoft Agent Framework agent is running on port 8000
2. Your OpenAI/Azure credentials are set correctly
3. Both servers started successfully
### Python Dependencies
If you encounter Python import errors:
```bash
cd agent
uv sync
uv run src/main.py
```
@@ -0,0 +1,7 @@
[project]
name = "agent"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = ["agent-framework-ag-ui>=1.0.0b251117", "python-dotenv"]
@@ -0,0 +1,126 @@
from __future__ import annotations
from textwrap import dedent
from typing import Annotated
from agent_framework import ChatAgent, ChatClientProtocol, ai_function
from agent_framework_ag_ui import AgentFrameworkAgent
from pydantic import Field
STATE_SCHEMA: dict[str, object] = {
"proverbs": {
"type": "array",
"items": {"type": "string"},
"description": "Ordered list of the user's saved proverbs.",
}
}
PREDICT_STATE_CONFIG: dict[str, dict[str, str]] = {
"proverbs": {
"tool": "update_proverbs",
"tool_argument": "proverbs",
}
}
@ai_function(
name="update_proverbs",
description=(
"Replace the entire list of proverbs with the provided values. "
"Always include every proverb you want to keep."
),
)
def update_proverbs(
proverbs: Annotated[
list[str],
Field(
description=(
"The complete source of truth for the user's proverbs. "
"Maintain ordering and include the full list on each call."
)
),
],
) -> str:
"""Persist the provided set of proverbs."""
return f"Proverbs updated. Tracking {len(proverbs)} item(s)."
@ai_function(
name="get_weather",
description="Share a quick weather update for a location. Use this to render the frontend weather card.",
)
def get_weather(
location: Annotated[
str,
Field(
description="The city or region to describe. Use fully spelled out names."
),
],
) -> str:
"""Return a short natural language weather summary."""
normalized = location.strip().title() or "the requested location"
return (
f"The weather in {normalized} is mild with a light breeze. "
"Skies are mostly clear—perfect for planning something fun."
)
@ai_function(
name="go_to_moon",
description="Request a playful human-in-the-loop confirmation before launching a mission to the moon.",
approval_mode="always_require",
)
def go_to_moon() -> str:
"""Request human approval before continuing."""
return "Mission control requested. Awaiting human approval for the lunar launch."
def create_agent(chat_client: ChatClientProtocol) -> AgentFrameworkAgent:
"""Instantiate the CopilotKit demo agent backed by Microsoft Agent Framework."""
base_agent = ChatAgent(
name="proverbs_agent",
instructions=dedent(
"""
You help users brainstorm, organize, and refine proverbs while coordinating UI updates.
State sync:
- The current list of proverbs is provided in the conversation context.
- When you add, remove, or reorder proverbs, call `update_proverbs` with the full list.
Never send partial updates—always include every proverb that should exist.
- CRITICAL: When asked to "add" a proverb, you must:
1. First, identify ALL existing proverbs from the conversation history
2. Create EXACTLY ONE new proverb (never more than one unless explicitly requested)
3. Call update_proverbs with: [all existing proverbs] + [the one new proverb]
Example: Current: ["A", "B"] -> After adding: ["A", "B", "C"] (NOT ["A", "B", "C", "D", "E"])
- When asked to "remove" a proverb, remove exactly ONE item unless user specifies otherwise.
Tool usage rules:
- When user asks to go to the moon, you MUST call the `go_to_moon` tool immediately. Do NOT ask for approval
yourself—the tool's approval workflow and the client UI will handle it.
Frontend integrations:
- `get_weather` renders a weather card in the UI. Only call this tool when the user explicitly
asks for weather. Do NOT call it after unrelated tasks or approvals.
- `go_to_moon` requires explicit user approval before you proceed. Only use it when a
user asks to launch or travel to the moon. Always call the tool instead of asking manually.
Conversation tips:
- Reference the latest proverb list before suggesting changes.
- Keep responses concise and friendly unless the user requests otherwise.
- After you finish executing tools for the user's request, provide a brief, final assistant
message summarizing exactly what changed. Do NOT call additional tools or switch topics
after that summary unless the user asks. ALWAYS send this conversational summary so the message persists.
""".strip()
),
chat_client=chat_client,
tools=[update_proverbs, get_weather, go_to_moon],
)
return AgentFrameworkAgent(
agent=base_agent,
name="CopilotKitMicrosoftAgentFrameworkAgent",
description="Manages proverbs, weather snippets, and human-in-the-loop moon launches.",
state_schema=STATE_SCHEMA,
predict_state_config=PREDICT_STATE_CONFIG,
require_confirmation=False, # Allow immediate state updates with follow-up messages
)
@@ -0,0 +1,79 @@
from __future__ import annotations
import os
import uvicorn
from agent_framework._clients import ChatClientProtocol
from azure.identity import DefaultAzureCredential
from agent_framework.azure import AzureOpenAIChatClient
from agent_framework.openai import OpenAIChatClient
from agent_framework_ag_ui import add_agent_framework_fastapi_endpoint
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from agent import create_agent
load_dotenv()
def _build_chat_client() -> ChatClientProtocol:
try:
if bool(os.getenv("AZURE_OPENAI_ENDPOINT")):
# Azure OpenAI setup - uses environment variables by default
# Optionally can pass deployment_name explicitly
deployment_name = os.getenv(
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", "gpt-4o-mini"
)
return AzureOpenAIChatClient(
credential=DefaultAzureCredential(),
deployment_name=deployment_name,
endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
)
if bool(os.getenv("OPENAI_API_KEY")):
# OpenAI setup - requires explicit model_id and api_key
return OpenAIChatClient(
model_id=os.getenv("OPENAI_CHAT_MODEL_ID", "gpt-4o-mini"),
api_key=os.getenv("OPENAI_API_KEY"),
)
raise ValueError(
"Either AZURE_OPENAI_ENDPOINT or OPENAI_API_KEY environment variable is required"
)
except Exception as exc: # pragma: no cover
raise RuntimeError(
"Unable to initialize the chat client. Double-check your API credentials as documented in README.md."
) from exc
chat_client = _build_chat_client()
my_agent = create_agent(chat_client)
app = FastAPI(title="CopilotKit + Microsoft Agent Framework (Python)")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
add_agent_framework_fastapi_endpoint(
app=app,
agent=my_agent,
path="/",
)
@app.get("/health")
async def health():
return {"status": "ok"}
if __name__ == "__main__":
host = os.getenv("AGENT_HOST", "0.0.0.0")
port = int(os.getenv("AGENT_PORT", "8000"))
uvicorn.run("main:app", host=host, port=port, reload=True)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,74 @@
services:
aimock:
image: ghcr.io/copilotkit/aimock:latest
volumes:
- ./fixtures:/fixtures:ro
command:
["--fixtures", "/fixtures", "--host", "0.0.0.0", "--validate-on-load"]
agent:
build:
context: ./agent
dockerfile: ../docker/Dockerfile.agent
environment:
- OPENAI_API_KEY=test-key-for-aimock
- OPENAI_BASE_URL=http://aimock:4010/v1
depends_on:
aimock:
condition: service_started
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import socket; s=socket.create_connection(('localhost',8000),2); s.close()",
]
interval: 5s
timeout: 5s
retries: 30
start_period: 30s
app:
build:
context: .
dockerfile: docker/Dockerfile.app
environment:
- AGENT_URL=http://agent:8000
depends_on:
agent:
condition: service_healthy
healthcheck:
test:
[
"CMD",
"node",
"-e",
"const http = require('http'); const req = http.get('http://localhost:3000/', (res) => { process.exit(res.statusCode === 200 ? 0 : 1); }); req.on('error', () => process.exit(1)); req.setTimeout(2000, () => { req.destroy(); process.exit(1); });",
]
interval: 5s
timeout: 10s
retries: 30
start_period: 60s
tests:
image: mcr.microsoft.com/playwright:v1.52.0-noble
working_dir: /tests
volumes:
- ../../../showcase/tests:/tests
- test-results:/tests/test-results
environment:
- STARTER=${STARTER:-ms-agent-framework-python}
- STARTER_URL=http://app:3000
depends_on:
app:
condition: service_healthy
command:
[
"bash",
"-c",
"cd /tests && npm install --no-audit --no-fund 2>/dev/null && npx playwright install chromium && npx playwright test starter-smoke --reporter=list",
]
volumes:
test-results:
@@ -0,0 +1,18 @@
FROM python:3.12-slim
# Install uv by copying from the official image (avoids curl|sh pipe-swallow bug
# where a 5xx on astral.sh silently produces an exit-0 layer with no uv binary).
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
ENV PYTHONPATH=/app/src
WORKDIR /app
COPY pyproject.toml uv.lock ./
COPY src/ ./src/
RUN uv sync
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
@@ -0,0 +1,27 @@
FROM node:22-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json ./
RUN npm install --ignore-scripts
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN node -e "const fs=require('fs'); const f='next.config.ts'; let c=fs.readFileSync(f,'utf8'); if(!c.includes('ignoreBuildErrors')){c=c.replace('};',' typescript: { ignoreBuildErrors: true },\n};'); fs.writeFileSync(f,c);}"
RUN node -e "const fs=require('fs'); const f='next.config.ts'; let c=fs.readFileSync(f,'utf8'); if(!c.includes('output')){c=c.replace('};',' output: \"standalone\",\n};'); fs.writeFileSync(f,c);}"
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000 HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
@@ -0,0 +1,17 @@
#!/bin/bash
set -e
# Start Python agent on port 8000 (override PORT which Railway sets to 3000)
cd /app/agent
PYTHONPATH=/app/agent/src AGENT_PORT=8000 PORT=8000 /app/agent/.venv/bin/python -m uvicorn src.main:app --host 0.0.0.0 --port 8000 &
AGENT_PID=$!
cd /app
# Start Next.js frontend
PORT=${PORT:-3000} npx next start --port ${PORT:-3000} &
NEXT_PID=$!
wait -n $AGENT_PID $NEXT_PID
EXIT_CODE=$?
kill $AGENT_PID $NEXT_PID 2>/dev/null || true
exit $EXIT_CODE
@@ -0,0 +1,16 @@
{
"fixtures": [
{
"match": { "userMessage": "Hello" },
"response": {
"content": "Hello! I'm the ms-agent-framework-python AI assistant. How can I help you?"
}
},
{
"match": {},
"response": {
"content": "You're currently running against aimock (a mock LLM server). This response is a catch-all for requests that don't match any test fixture. To use a real LLM: (1) Add your OPENAI_API_KEY to .env, (2) Remove or unset OPENAI_BASE_URL from your environment so requests go to OpenAI instead of aimock, (3) Restart with `pnpm dev`."
}
}
]
}
@@ -0,0 +1,25 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
serverExternalPackages: ["@copilotkit/runtime"],
env: {
// The public Threads UI flag is DERIVED from the server-side license token.
// Set COPILOTKIT_LICENSE_TOKEN (only) to enable Threads — do not set this
// flag directly. NOTE: NEXT_PUBLIC_* resolves at BUILD time while the
// runtime reads the token per-request, so the UI gate and runtime agree
// only when the token is present at build time (the standard `next dev` /
// host-build flow).
NEXT_PUBLIC_COPILOTKIT_THREADS_ENABLED: process.env.COPILOTKIT_LICENSE_TOKEN
? "true"
: "false",
},
typescript: {
// The verbatim demo (useFrontendTool/useRenderToolCall parameter shapes)
// has type drift against the installed @copilotkit/react-core@1.55.2 v2
// typings — the same demo-vs-installed-types mismatch the sibling examples
// (mastra, langgraph-python) absorb with this flag.
ignoreBuildErrors: true,
},
};
export default nextConfig;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
{
"name": "copilotkit-microsoft-agent-framework-starter",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "concurrently \"npm run dev:ui\" \"npm run dev:agent\" --names ui,agent --prefix-colors blue,green --kill-others",
"dev:debug": "LOG_LEVEL=debug npm run dev",
"dev:agent": "./scripts/run-agent.sh || scripts\\run-agent.bat",
"dev:ui": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint",
"install:agent": "./scripts/setup-agent.sh || scripts\\setup-agent.bat",
"postinstall": "npm run install:agent"
},
"dependencies": {
"@ag-ui/client": "0.0.57",
"@copilotkit/react-core": "1.62.3",
"@copilotkit/runtime": "1.62.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"hono": "^4.12.10",
"lucide-react": "^0.525.0",
"next": "16.0.8",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"tailwind-merge": "^3.5.0",
"zod": "^3.24.4"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"concurrently": "^9.1.2",
"tailwindcss": "^4",
"typescript": "^5"
},
"overrides": {
"@ag-ui/client": "0.0.57",
"@ag-ui/core": "0.0.57",
"@ag-ui/encoder": "0.0.57",
"@ag-ui/proto": "0.0.57"
}
}
@@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;
@@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

After

Width:  |  Height:  |  Size: 385 B

@@ -0,0 +1,6 @@
@echo off
REM Navigate to the agent directory
cd /d %~dp0\..\agent
REM Run the agent using uv
uv run src/main.py
@@ -0,0 +1,7 @@
#!/bin/bash
# Navigate to the agent directory
cd "$(dirname "$0")/../agent" || exit 1
# Run the agent using uv
uv run src/main.py
@@ -0,0 +1,6 @@
@echo off
REM Navigate to the agent directory
cd /d "%~dp0\..\agent" || exit /b 1
REM Install dependencies and create virtual environment using uv
uv sync
@@ -0,0 +1,7 @@
#!/bin/bash
# Navigate to the agent directory
cd "$(dirname "$0")/../agent" || exit 1
# Install dependencies and create virtual environment using uv
uv sync
@@ -0,0 +1,44 @@
import {
CopilotRuntime,
CopilotKitIntelligence,
InMemoryAgentRunner,
createCopilotEndpoint,
} from "@copilotkit/runtime/v2";
import { HttpAgent } from "@ag-ui/client";
import { handle } from "hono/vercel";
// 1. Create the CopilotRuntime instance and utilize the Microsoft Agent Framework
// AG-UI integration to set up the connection.
const runtime = new CopilotRuntime({
agents: {
// Our FastAPI endpoint URL
default: new HttpAgent({
url: process.env.AGENT_URL || "http://localhost:8000/",
}),
},
// --- copilotkit:intelligence (remove this block to opt out) ---
...(process.env.COPILOTKIT_LICENSE_TOKEN
? {
intelligence: new CopilotKitIntelligence({
apiKey: process.env.INTELLIGENCE_API_KEY ?? "",
apiUrl: process.env.INTELLIGENCE_API_URL ?? "http://localhost:4201",
wsUrl:
process.env.INTELLIGENCE_GATEWAY_WS_URL ?? "ws://localhost:4401",
}),
identifyUser: () => ({ id: "demo-user", name: "Demo User" }),
licenseToken: process.env.COPILOTKIT_LICENSE_TOKEN,
}
: { runner: new InMemoryAgentRunner() }),
// --- /copilotkit:intelligence ---
});
// 2. Build a Next.js API route that handles the CopilotKit runtime requests.
const app = createCopilotEndpoint({
runtime,
basePath: "/api/copilotkit",
});
export const GET = handle(app);
export const POST = handle(app);
export const PATCH = handle(app);
export const DELETE = handle(app);
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,75 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
/* Threads-panel theme-map — values pulled verbatim from CopilotKit's V2
design system (the same one this example's CopilotSidebar renders with;
see @copilotkit/react-core/src/v2/styles/globals.css). The drawer is a
left-side companion to that LIGHT sidebar, so it inherits the sidebar's
exact neutral-gray palette, charcoal "primary" (NOT a brand accent — the
sidebar's primary buttons are near-black), and 0.625rem radius scale.
This keeps the threads panel reading as one product with the chat. */
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
/* Match the V2 sidebar's radius scale exactly (--radius: 0.625rem there). */
--radius: 0.625rem;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
/* This example loads no custom web font — its body uses the system Arial
stack — so the drawer follows the same stack (and a system monospace for
the locked-state command well). */
--font-body: Arial, Helvetica, sans-serif;
--font-code: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
/* This example's CopilotSidebar (CopilotKit's V2 chat) stays LIGHT regardless
of the OS color scheme, so the threads panel is held light too — otherwise it
would clash with the sidebar it sits beside. The panel reads --foreground for
its titles/body text, which the dark-mode block above flips. The demo content
(CopilotSidebar + ProverbsCard) uses its own colors, not these tokens, so
re-pinning a stable light foreground/background on the threads layout wrapper
keeps the panel readable without affecting anything else. The confirm dialog
renders in a portal on <body>, so re-pin there as well. Values mirror the V2
light theme (oklch(0.145 0 0) / oklch(1 0 0)). */
.threadsLayout,
body > [role="presentation"] {
--foreground: oklch(0.145 0 0);
--background: oklch(1 0 0);
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
body,
html {
height: 100%;
}
@@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { CopilotKit } from "@copilotkit/react-core/v2";
import "./globals.css";
import "@copilotkit/react-core/v2/styles.css";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
{/*
suppressHydrationWarning: browser extensions (e.g. Grammarly) inject
attributes like data-gr-ext-installed onto <body> before React hydrates,
which would otherwise surface as a hydration mismatch on first load.
This only relaxes the check for <body>'s own attributes (one level deep);
everything rendered inside <body> is still fully hydration-checked.
*/}
<body className={"antialiased"} suppressHydrationWarning>
{/* Force REST transport so runtime-info + threads both hit the multi-route endpoint (auto-detect races the lazily-compiled API route in next dev). */}
<CopilotKit runtimeUrl="/api/copilotkit" useSingleEndpoint={false}>
{children}
</CopilotKit>
</body>
</html>
);
}
@@ -0,0 +1,50 @@
.layout {
display: grid;
/*
Reserve the desktop drawer's width (its default 320px) as a fixed first
column so the layout doesn't shift when the client-only <CopilotThreadsDrawer>
mounts after hydration. On mobile the drawer is an off-canvas overlay (out
of flow), so the column collapses and the content fills the width.
*/
grid-template-columns: var(--cpk-drawer-reserved-width, 320px) minmax(0, 1fr);
/* Drawer sets --cpk-drawer-reserved-width to 0 on desktop-collapse, so the
reserved column collapses and the chat reclaims the space. */
transition: grid-template-columns 0.2s ease;
/*
Bound the single grid row to the viewport (minmax(0,1fr) lets it shrink
below content) so a long thread list scrolls INTERNALLY in the drawer with
the header pinned, instead of the drawer growing past the viewport and the
page scrolling the header away.
*/
grid-template-rows: minmax(0, 1fr);
height: 100dvh;
width: 100%;
overflow: hidden;
}
.mainPanel {
/*
Pin the content to the SECOND track explicitly. The client-only drawer
renders nothing during SSR, so without this the content would flow into the
reserved first column at first paint and then jump once the drawer mounts.
*/
grid-column: 2;
min-width: 0;
height: 100dvh;
overflow: hidden;
}
/*
Mobile (≤768px): the drawer is an off-canvas overlay — collapse to a single
track. MUST come after the base rules (media queries add no specificity, so a
later same-specificity base rule would otherwise win and leak the two-column
desktop layout onto mobile).
*/
@media (max-width: 768px) {
.layout {
grid-template-columns: minmax(0, 1fr);
}
.mainPanel {
grid-column: auto;
}
}
@@ -0,0 +1,170 @@
"use client";
import { ProverbsCard } from "@/components/proverbs";
import { WeatherCard } from "@/components/weather";
import { MoonCard } from "@/components/moon";
import type { AgentState } from "@/lib/types";
import {
useAgent,
useConfigureSuggestions,
useFrontendTool,
useRenderTool,
useHumanInTheLoop,
CopilotSidebar,
CopilotChatConfigurationProvider,
CopilotThreadsDrawer,
} from "@copilotkit/react-core/v2";
import { useEffect, useState } from "react";
import { z } from "zod";
import styles from "./page.module.css";
export default function CopilotKitPage() {
const [themeColor, setThemeColor] = useState("#6366f1");
// 🪁 Frontend Actions: https://docs.copilotkit.ai/microsoft-agent-framework/frontend-actions
useFrontendTool({
name: "setThemeColor",
parameters: z.object({
themeColor: z
.string()
.describe("The theme color to set. Make sure to pick nice colors."),
}),
async handler({ themeColor }) {
setThemeColor(themeColor);
return `Theme color set to ${themeColor}`;
},
});
// 🪁 Suggestions: https://docs.copilotkit.ai/microsoft-agent-framework
useConfigureSuggestions(
{
suggestions: [
{
title: "Generative UI",
message: "Get the weather in San Francisco.",
},
{
title: "Frontend Tools",
message: "Set the theme to green.",
},
{
title: "Human In the Loop",
message: "Please go to the moon.",
},
{
title: "Write Agent State",
message: "Add a proverb about AI.",
},
{
title: "Update Agent State",
message:
"Please remove 1 random proverb from the list if there are any.",
},
{
title: "Read Agent State",
message: "What are the proverbs?",
},
],
available: "always",
},
[],
);
return (
/*
One UNCONTROLLED CopilotChatConfigurationProvider (no `threadId` prop) owns
the active thread for the whole surface. The SDK <CopilotThreadsDrawer> drives it
directly — selecting a row sets the active thread, "+ New" resets to a
fresh thread — with no host thread-state. The proverbs/weather/moon content
and the CopilotSidebar read the same active thread from the provider (the
content's `useAgent()` falls back to it). A *controlled* provider would
block "+ New" from resetting, so uncontrolled-inside-provider is required.
`.threadsLayout` (globals.css) pins the light theme vars the drawer +
sidebar inherit; the SDK drawer follows them by token inheritance.
*/
<CopilotChatConfigurationProvider agentId="default">
<div className={`${styles.layout} threadsLayout`}>
{/* SDK threads drawer (replaces the hand-rolled fork). License-gated: the locked view's Upgrade CTA opens the Intelligence docs by default. */}
<CopilotThreadsDrawer agentId="default" />
<div className={styles.mainPanel}>
<main
style={
{
"--copilot-kit-primary-color": themeColor,
} as React.CSSProperties
}
>
<YourMainContent themeColor={themeColor} />
<CopilotSidebar
defaultOpen={true}
labels={{
modalHeaderTitle: "Popup Assistant",
welcomeMessageText:
"👋 Hi, there! You're chatting with an agent.",
}}
/>
</main>
</div>
</div>
</CopilotChatConfigurationProvider>
);
}
function YourMainContent({ themeColor }: { themeColor: string }) {
// 🪁 Shared State: https://docs.copilotkit.ai/microsoft-agent-framework/shared-state
// V2: useAgent returns the agent; read agent.state and write via agent.setState.
const { agent } = useAgent({ agentId: "default" });
const state = (agent.state as AgentState | undefined) ?? { proverbs: [] };
const setState = (next: AgentState) => agent.setState(next);
// Seed an initial proverb once (the V2 agent starts with empty state).
useEffect(() => {
if ((agent.state as AgentState | undefined)?.proverbs === undefined) {
agent.setState({
proverbs: [
"CopilotKit may be new, but it's the best thing since sliced bread.",
],
});
}
}, [agent]);
//🪁 Generative UI: https://docs.copilotkit.ai/microsoft-agent-framework/generative-ui
useRenderTool(
{
name: "get_weather",
parameters: z.object({
location: z.string(),
}),
render: ({ status, parameters }) => {
const location =
status === "inProgress" ? "" : (parameters.location ?? "");
return <WeatherCard location={location} themeColor={themeColor} />;
},
},
[themeColor],
);
// 🪁 Human In the Loop: https://docs.copilotkit.ai/microsoft-agent-framework/human-in-the-loop
useHumanInTheLoop(
{
name: "go_to_moon",
description: "Go to the moon on request.",
render: ({ respond, status }) => {
return (
<MoonCard themeColor={themeColor} status={status} respond={respond} />
);
},
},
[themeColor],
);
return (
<div
style={{ backgroundColor: themeColor }}
className="h-screen flex justify-center items-center flex-col transition-colors duration-300"
>
<ProverbsCard state={state} setState={setState} />
</div>
);
}
@@ -0,0 +1,84 @@
import { useState } from "react";
export interface MoonCardProps {
themeColor: string;
status: "inProgress" | "executing" | "complete";
respond?: (response: string) => void;
}
export function MoonCard({ themeColor, status, respond }: MoonCardProps) {
const [decision, setDecision] = useState<"launched" | "aborted" | null>(null);
const handleLaunch = () => {
setDecision("launched");
respond?.("You have permission to go to the moon.");
};
const handleAbort = () => {
setDecision("aborted");
respond?.(
"You do not have permission to go to the moon. The user you're talking to rejected the request.",
);
};
return (
<div
style={{ backgroundColor: themeColor }}
className="rounded-2xl shadow-xl max-w-md w-full mt-6"
>
<div className="bg-white/20 backdrop-blur-md p-8 w-full rounded-2xl">
{/* Show decision or prompt */}
{decision === "launched" ? (
<div className="text-center">
<div className="text-7xl mb-4">🌕</div>
<h2 className="text-2xl font-bold text-white mb-2">
Mission Launched
</h2>
<p className="text-white/90">We made it to the moon!</p>
</div>
) : decision === "aborted" ? (
<div className="text-center">
<div className="text-7xl mb-4"></div>
<h2 className="text-2xl font-bold text-white mb-2">
Mission Aborted
</h2>
<p className="text-white/90">Staying on Earth 🌍</p>
</div>
) : (
<>
<div className="text-center mb-6">
<div className="text-7xl mb-4">🚀</div>
<h2 className="text-2xl font-bold text-white mb-2">
Ready for Launch?
</h2>
<p className="text-white/90">Mission to the Moon 🌕</p>
</div>
{/* Launch Buttons */}
{status === "executing" && (
<div className="flex gap-3">
<button
onClick={handleLaunch}
className="flex-1 px-6 py-4 rounded-xl bg-white text-black font-bold
shadow-lg hover:shadow-xl transition-all
hover:scale-105 active:scale-95"
>
🚀 Launch!
</button>
<button
onClick={handleAbort}
className="flex-1 px-6 py-4 rounded-xl bg-black/20 text-white font-bold
border-2 border-white/30 shadow-lg
transition-all hover:scale-105 active:scale-95
hover:bg-black/30"
>
Abort
</button>
</div>
)}
</>
)}
</div>
</div>
);
}
@@ -0,0 +1,49 @@
import { AgentState } from "@/lib/types";
export interface ProverbsCardProps {
state: AgentState;
setState: (state: AgentState) => void;
}
export function ProverbsCard({ state, setState }: ProverbsCardProps) {
// `state` is undefined until the agent syncs (V2 useAgent), so guard it.
const proverbs = state?.proverbs ?? [];
return (
<div className="bg-white/20 backdrop-blur-md p-8 rounded-2xl shadow-xl max-w-2xl w-full">
<h1 className="text-4xl font-bold text-white mb-2 text-center">
Proverbs
</h1>
<p className="text-gray-200 text-center italic mb-6">
This is a demonstrative page, but it could be anything you want! 🪁
</p>
<hr className="border-white/20 my-6" />
<div className="flex flex-col gap-3">
{proverbs.map((proverb, index) => (
<div
key={index}
className="bg-white/15 p-4 rounded-xl text-white relative group hover:bg-white/20 transition-all"
>
<p className="pr-8">{proverb}</p>
<button
onClick={() =>
setState({
...state,
proverbs: proverbs.filter((_, i) => i !== index),
})
}
className="absolute right-3 top-3 opacity-0 group-hover:opacity-100 transition-opacity
bg-red-500 hover:bg-red-600 text-white rounded-full h-6 w-6 flex items-center justify-center"
>
</button>
</div>
))}
</div>
{proverbs.length === 0 && (
<p className="text-center text-white/80 italic my-8">
No proverbs yet. Ask the assistant to add some!
</p>
)}
</div>
);
}
@@ -0,0 +1,52 @@
import * as React from "react";
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] disabled:pointer-events-none disabled:opacity-50 cursor-pointer",
{
variants: {
variant: {
default:
"bg-[var(--primary)] text-[var(--primary-foreground)] hover:opacity-90",
secondary:
"bg-[var(--secondary)] text-[var(--secondary-foreground)] hover:opacity-80",
outline:
"border border-[var(--border)] bg-[var(--background)] hover:bg-[var(--secondary)]",
ghost:
"hover:bg-[var(--secondary)] hover:text-[var(--secondary-foreground)]",
destructive:
"bg-[var(--destructive)] text-[var(--destructive-foreground)] hover:opacity-90",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-6",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
export interface ButtonProps
extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
),
);
Button.displayName = "Button";
export { Button, buttonVariants };
@@ -0,0 +1,85 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-[var(--radius)] border border-[var(--border)] bg-[var(--card)] text-[var(--card-foreground)] shadow-sm",
className,
)}
{...props}
/>
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-[var(--muted-foreground)]", className)}
{...props}
/>
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
};
@@ -0,0 +1,69 @@
// Simple sun icon for the weather card
function SunIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-14 h-14 text-yellow-200"
>
<circle cx="12" cy="12" r="5" />
<path
d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"
strokeWidth="2"
stroke="currentColor"
/>
</svg>
);
}
// Weather card component where the location and themeColor are based on what the agent
// sets via tool calls.
export function WeatherCard({
location,
themeColor,
}: {
location?: string;
themeColor: string;
}) {
return (
<div
style={{ backgroundColor: themeColor }}
className="rounded-xl shadow-xl mt-6 mb-4 max-w-md w-full"
>
<div className="bg-white/20 p-4 w-full">
<div className="flex items-center justify-between">
<div>
<h3 className="text-xl font-bold text-white capitalize">
{location}
</h3>
<p className="text-white">Current Weather</p>
</div>
<SunIcon />
</div>
<div className="mt-4 flex items-end justify-between">
<div className="text-3xl font-bold text-white">70°</div>
<div className="text-sm text-white">Clear skies</div>
</div>
<div className="mt-4 pt-4 border-t border-white">
<div className="grid grid-cols-3 gap-2 text-center">
<div>
<p className="text-white text-xs">Humidity</p>
<p className="text-white font-medium">45%</p>
</div>
<div>
<p className="text-white text-xs">Wind</p>
<p className="text-white font-medium">5 mph</p>
</div>
<div>
<p className="text-white text-xs">Feels Like</p>
<p className="text-white font-medium">72°</p>
</div>
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,4 @@
// State of the agent, make sure this aligns with your agent's state.
export type AgentState = {
proverbs: string[];
};
@@ -0,0 +1,7 @@
import { clsx } from "clsx";
import type { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
@@ -0,0 +1,33 @@
{
"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": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules"]
}