create-theokit 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/cli.js +4 -13
- package/dist/cli.js.map +1 -1
- package/package.json +10 -8
- package/templates/default/CLAUDE.md +2 -2
- package/templates/default/README.md.tmpl +38 -50
- package/templates/default/agents/chat.ts +22 -0
- package/templates/default/app/layout.tsx +71 -3
- package/templates/default/app/page.tsx +296 -61
- package/templates/default/dot-claude/skills/theokit-agents/SKILL.md +78 -44
- package/templates/default/dot-claude/skills/theokit-frontend/SKILL.md +24 -26
- package/templates/default/dot-claude/skills/theokit-ui/SKILL.md +32 -28
- package/templates/default/eslint.config.mjs +1 -5
- package/templates/default/package.json.tmpl +16 -9
- package/templates/default/theo.config.ts +1 -3
- package/templates/default/tsconfig.json +7 -14
- package/templates/default/types/jobs.d.ts +25 -0
- package/templates/services/agent-node/Dockerfile.tmpl +20 -0
- package/templates/services/agent-node/README.md +38 -0
- package/templates/services/agent-node/package.json.tmpl +18 -0
- package/templates/services/agent-node/src/index.ts +58 -0
- package/templates/services/agent-node/tsconfig.json +13 -0
- package/templates/default/AGENTS.md +0 -106
- package/templates/default/app/globals.css +0 -377
- package/templates/default/app.ts +0 -15
- package/templates/default/drizzle.config.ts +0 -10
- package/templates/default/server/db/index.ts +0 -10
- package/templates/default/server/db/schema.ts +0 -13
- package/templates/default/server/index.ts +0 -2
- package/templates/default/tests/tasks.test.ts +0 -18
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# {{name}}
|
|
2
2
|
|
|
3
|
-
Built with [TheoKit](https://theokit.dev) — Build the app your agent lives in
|
|
3
|
+
Built with [TheoKit](https://theokit.dev) — **Build the app your agent lives in.**
|
|
4
|
+
|
|
5
|
+
This is the default template: a working **agent chat surface** wired to a mock
|
|
6
|
+
streaming agent, ready for you to plug in your LLM. Run it and you immediately
|
|
7
|
+
see your agent talking — that's the point.
|
|
4
8
|
|
|
5
9
|
## Getting Started
|
|
6
10
|
|
|
@@ -8,75 +12,60 @@ Built with [TheoKit](https://theokit.dev) — Build the app your agent lives in.
|
|
|
8
12
|
# Install dependencies
|
|
9
13
|
npm install
|
|
10
14
|
|
|
11
|
-
#
|
|
12
|
-
npm run seed
|
|
13
|
-
|
|
14
|
-
# Start development server
|
|
15
|
+
# Start development server (HMR)
|
|
15
16
|
npm run dev
|
|
16
17
|
```
|
|
17
18
|
|
|
19
|
+
Open the app and chat with the agent. Edit the agent at `agents/chat.ts`
|
|
20
|
+
(pick your model / add tools) — the `@theokit/sdk` runtime resolves OpenRouter /
|
|
21
|
+
Anthropic / Ollama / OpenAI from env vars (see the file's header comment).
|
|
22
|
+
|
|
18
23
|
## Project Structure
|
|
19
24
|
|
|
20
25
|
```
|
|
21
26
|
{{name}}/
|
|
22
27
|
├── app/
|
|
23
|
-
│ ├── page.tsx #
|
|
24
|
-
│
|
|
25
|
-
│ └── globals.css # Global styles
|
|
28
|
+
│ ├── page.tsx # Chat surface (@theokit/ui: ChatThread, ChatComposer, …)
|
|
29
|
+
│ └── layout.tsx # Root layout
|
|
26
30
|
├── server/
|
|
27
|
-
│
|
|
28
|
-
│
|
|
29
|
-
│
|
|
30
|
-
|
|
31
|
-
│ │ └── [id].ts # GET/PUT/DELETE /api/tasks/:id
|
|
32
|
-
│ ├── db/
|
|
33
|
-
│ │ ├── schema.ts # Drizzle ORM schema (SQLite)
|
|
34
|
-
│ │ ├── index.ts # DB connection + auto-create tables
|
|
35
|
-
│ │ └── seed.ts # Seed data
|
|
36
|
-
│ └── index.ts # Server barrel (routes auto-discovered)
|
|
37
|
-
├── tests/
|
|
38
|
-
│ └── tasks.test.ts # API smoke test
|
|
31
|
+
│ └── routes/
|
|
32
|
+
│ ├── chat.ts # Streaming agent endpoint (mock — replace with your LLM)
|
|
33
|
+
│ └── health.ts # GET /api/health
|
|
34
|
+
├── tailwind.config.ts # TheoUI design tokens
|
|
39
35
|
├── theo.config.ts # TheoKit configuration
|
|
40
36
|
└── .env.example # Environment variables
|
|
41
37
|
```
|
|
42
38
|
|
|
43
|
-
##
|
|
39
|
+
## Key Concepts
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
- **Agent chat surface** — `app/page.tsx` renders `@theokit/ui` chat components
|
|
42
|
+
driven by the streaming endpoint.
|
|
43
|
+
- **`agents/chat.ts` (`defineAgent`)** — one file auto-served at `POST /api/agents/chat`,
|
|
44
|
+
streaming the ai-sdk `UIMessageStream` wire; `useAgent('/api/agents/chat')` on the client
|
|
45
|
+
reconstructs `UIMessage[]` you render via `message.parts`. `@theokit/sdk` owns
|
|
46
|
+
conversation persistence and provider resolution.
|
|
47
|
+
- **`defineAgentTool`** — declare typed tools (the template ships a
|
|
48
|
+
`current_time` example).
|
|
53
49
|
|
|
54
|
-
##
|
|
50
|
+
## About `@theokit/sdk`
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
`@theokit/sdk` is the agent runtime this template depends on. Its publish to the
|
|
53
|
+
public npm registry is **operator-deferred** — if `npm install` cannot resolve
|
|
54
|
+
it yet, scaffold the registry-free variant instead (see Bare mode below) and add
|
|
55
|
+
the SDK once it is published.
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
curl -X POST http://localhost:3000/api/tasks \
|
|
62
|
-
-H "Content-Type: application/json" \
|
|
63
|
-
-d '{"title":"New task","priority":"high"}'
|
|
57
|
+
## Bare mode (`--bare`)
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-H "Content-Type: application/json" \
|
|
68
|
-
-d '{"done":true}'
|
|
59
|
+
Want the minimal "Hello Theo" scaffold with **no** agent UI and **no** unpublished
|
|
60
|
+
registry dependencies? Scaffold with `--bare`:
|
|
69
61
|
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
```bash
|
|
63
|
+
npm create theokit my-app -- --template=default --bare
|
|
72
64
|
```
|
|
73
65
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- **Drizzle + SQLite** — Type-safe database, zero config
|
|
78
|
-
- **File-based routing** — `server/routes/tasks/[id].ts` → `/api/tasks/:id`
|
|
79
|
-
- **Seeds** — `npm run seed` loads sample data
|
|
66
|
+
`--bare` strips `@theokit/ui`, `@theokit/sdk`, `lucide-react`, and the Tailwind
|
|
67
|
+
toolchain, replaces `app/page.tsx` with a plain "Hello Theo" page, and removes
|
|
68
|
+
the mock chat route — a scaffold that always installs without registry access.
|
|
80
69
|
|
|
81
70
|
## Commands
|
|
82
71
|
|
|
@@ -85,7 +74,6 @@ npm run dev # Start dev server with HMR
|
|
|
85
74
|
npm run build # Build for production
|
|
86
75
|
npm run start # Run production build
|
|
87
76
|
npm run test # Run tests (vitest)
|
|
88
|
-
npm run seed # Seed database
|
|
89
77
|
npm run lint # ESLint check
|
|
90
78
|
npm run typecheck # TypeScript type check
|
|
91
79
|
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineAgent } from '@theokit/agents'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Chat agent — the zero-config `agents/*.ts` convention.
|
|
6
|
+
*
|
|
7
|
+
* This one file is auto-served at `POST /api/agents/chat` (dev + build), streaming the
|
|
8
|
+
* ai-sdk `UIMessageStream` that `useAgent('chat')` consumes on the client. No manual route,
|
|
9
|
+
* no manual client wiring. `@theokit/sdk` runs it; conversation turns auto-persist per
|
|
10
|
+
* session (the SDK owns storage).
|
|
11
|
+
*
|
|
12
|
+
* Provider: resolved from the environment — OPENROUTER_API_KEY (preferred — gateway to many
|
|
13
|
+
* models) OR ANTHROPIC_API_KEY / OPENAI_API_KEY. The `model` id is prefixed with the provider
|
|
14
|
+
* namespace so OpenRouter routes it upstream (see https://openrouter.ai/models).
|
|
15
|
+
*
|
|
16
|
+
* Add tools with `defineAgentTool` (from `theokit/server`) and pass them to `tools: [...]`.
|
|
17
|
+
*/
|
|
18
|
+
export default defineAgent({
|
|
19
|
+
input: z.object({ message: z.string() }),
|
|
20
|
+
model: 'openai/gpt-4o-mini',
|
|
21
|
+
system: 'You are a helpful assistant.',
|
|
22
|
+
})
|
|
@@ -1,5 +1,73 @@
|
|
|
1
|
-
import '
|
|
1
|
+
import '@theokit/ui/styles.css'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { Outlet } from 'react-router'
|
|
4
|
+
import { AgentProfile, ThemeSwitcher, CostMeter, type AgentProfileDescriptor } from '@theokit/ui'
|
|
5
|
+
import { Sidebar, TopNav, Tooltip, Badge } from '@usetheo/ui'
|
|
6
|
+
import { Bot, MessageSquare, History, Settings } from 'lucide-react'
|
|
7
|
+
|
|
8
|
+
const AGENTS: AgentProfileDescriptor[] = [
|
|
9
|
+
{
|
|
10
|
+
id: 'theo',
|
|
11
|
+
name: 'Theo',
|
|
12
|
+
description: 'Edit the agent at agents/chat.ts.',
|
|
13
|
+
tone: 'primary',
|
|
14
|
+
initials: 'TH',
|
|
15
|
+
badge: 'Online',
|
|
16
|
+
},
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
export default function RootLayout() {
|
|
20
|
+
return (
|
|
21
|
+
<div className="grid h-screen w-screen grid-rows-[auto_1fr] bg-background text-foreground">
|
|
22
|
+
<TopNav className="border-border/60 border-b px-4 py-2">
|
|
23
|
+
<TopNav.Left>
|
|
24
|
+
<Tooltip label="TheoKit agent surface" side="bottom">
|
|
25
|
+
<span className="inline-flex items-center gap-2">
|
|
26
|
+
<Bot className="size-5 text-primary" aria-hidden />
|
|
27
|
+
<span className="font-semibold text-sm tracking-tight">Theo Agent</span>
|
|
28
|
+
<Badge variant="outline" size="sm">
|
|
29
|
+
v0.1
|
|
30
|
+
</Badge>
|
|
31
|
+
</span>
|
|
32
|
+
</Tooltip>
|
|
33
|
+
</TopNav.Left>
|
|
34
|
+
<TopNav.Right>
|
|
35
|
+
<Tooltip label="Toggle theme" side="bottom" align="end">
|
|
36
|
+
<span>
|
|
37
|
+
<ThemeSwitcher />
|
|
38
|
+
</span>
|
|
39
|
+
</Tooltip>
|
|
40
|
+
</TopNav.Right>
|
|
41
|
+
</TopNav>
|
|
42
|
+
|
|
43
|
+
<div className="grid h-full grid-cols-[260px_1fr] overflow-hidden">
|
|
44
|
+
<Sidebar className="flex flex-col border-border/60 border-r p-3">
|
|
45
|
+
<Sidebar.Header>
|
|
46
|
+
<AgentProfile agents={AGENTS} activeId="theo" />
|
|
47
|
+
</Sidebar.Header>
|
|
48
|
+
|
|
49
|
+
<Sidebar.Section title="Workspace">
|
|
50
|
+
<Sidebar.Item icon={MessageSquare} active>
|
|
51
|
+
New conversation
|
|
52
|
+
</Sidebar.Item>
|
|
53
|
+
<Sidebar.Item icon={History}>History</Sidebar.Item>
|
|
54
|
+
<Sidebar.Item icon={Settings}>Settings</Sidebar.Item>
|
|
55
|
+
</Sidebar.Section>
|
|
56
|
+
|
|
57
|
+
<Sidebar.Footer className="mt-auto">
|
|
58
|
+
<CostMeter
|
|
59
|
+
compact
|
|
60
|
+
title="This session"
|
|
61
|
+
cost={0.0023}
|
|
62
|
+
delta={{ value: 0.0023, period: 'now' }}
|
|
63
|
+
/>
|
|
64
|
+
</Sidebar.Footer>
|
|
65
|
+
</Sidebar>
|
|
66
|
+
|
|
67
|
+
<main className="flex h-full flex-col overflow-hidden">
|
|
68
|
+
<Outlet />
|
|
69
|
+
</main>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
5
73
|
}
|
|
@@ -1,68 +1,303 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
ChatThread,
|
|
6
|
+
ChatMessage,
|
|
7
|
+
ChatComposer,
|
|
8
|
+
ToolCallCard,
|
|
9
|
+
AgentStreaming,
|
|
10
|
+
AgentErrorCard,
|
|
11
|
+
QuickActionChips,
|
|
12
|
+
ContextWindowBar,
|
|
13
|
+
type Message,
|
|
14
|
+
type QuickAction,
|
|
15
|
+
type ToolCallStatus,
|
|
16
|
+
} from '@theokit/ui'
|
|
17
|
+
import {
|
|
18
|
+
EmptyState,
|
|
19
|
+
CommandPalette,
|
|
20
|
+
Avatar,
|
|
21
|
+
Tooltip,
|
|
22
|
+
Button,
|
|
23
|
+
ScrollArea,
|
|
24
|
+
type CommandItem,
|
|
25
|
+
} from '@usetheo/ui'
|
|
26
|
+
import { Sparkles, Wrench, RotateCcw, Command } from 'lucide-react'
|
|
27
|
+
import { useAgent } from 'theokit/client'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default scaffold — an Agent Surface, composed entirely from TheoUI.
|
|
31
|
+
*
|
|
32
|
+
* ChatThread / ChatMessage → conversation
|
|
33
|
+
* ToolCallCard → expandable tool invocations
|
|
34
|
+
* AgentStreaming → streaming indicator
|
|
35
|
+
* AgentErrorCard → error display
|
|
36
|
+
* ChatComposer → bottom input bar
|
|
37
|
+
* EmptyState → first-load screen
|
|
38
|
+
* ContextWindowBar → context usage at top
|
|
39
|
+
* CommandPalette → ⌘K quick actions
|
|
40
|
+
* Avatar → assistant face in messages
|
|
41
|
+
* Tooltip → hints on icons
|
|
42
|
+
*
|
|
43
|
+
* `useAgent` binds to the `agents/chat.ts` endpoint, consumes the ai-sdk
|
|
44
|
+
* `UIMessageStream`, and handles AbortController cleanup + StrictMode safety.
|
|
45
|
+
* Edit `agents/chat.ts` to pick your model / add tools.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
type ConversationItem =
|
|
49
|
+
| { kind: 'message'; id: string; role: 'user' | 'assistant'; content: string; timestamp: string }
|
|
50
|
+
| {
|
|
51
|
+
kind: 'tool'
|
|
52
|
+
id: string
|
|
53
|
+
tool: string
|
|
54
|
+
target?: string
|
|
55
|
+
status: ToolCallStatus
|
|
56
|
+
output?: string
|
|
57
|
+
timestamp: string
|
|
58
|
+
}
|
|
59
|
+
| { kind: 'error'; id: string; message: string; timestamp: string }
|
|
60
|
+
|
|
61
|
+
const QUICK_ACTIONS: QuickAction[] = [
|
|
62
|
+
{ id: 'summarize', label: 'Summarize this page', icon: Sparkles },
|
|
63
|
+
{ id: 'tools', label: 'Show available tools', icon: Wrench },
|
|
64
|
+
{ id: 'reset', label: 'Start a new conversation', icon: RotateCcw },
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
const COMMAND_ITEMS: CommandItem[] = QUICK_ACTIONS.map((a) => ({
|
|
68
|
+
id: a.id,
|
|
69
|
+
label: a.label,
|
|
70
|
+
icon: a.icon,
|
|
71
|
+
group: 'Quick actions',
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
// Display-only context-window hint. The agent's real model lives in `agents/chat.ts`
|
|
75
|
+
// (`model: 'openai/gpt-4o-mini'`); wire real token counts from the stream when you need them.
|
|
76
|
+
const CONTEXT_USED = 4_200
|
|
77
|
+
const CONTEXT_TOTAL = 200_000
|
|
78
|
+
const MODEL_NAME = 'gpt-4o-mini'
|
|
79
|
+
|
|
80
|
+
const ASSISTANT_AVATAR = (
|
|
81
|
+
<Avatar size="sm" tone="primary">
|
|
82
|
+
<Avatar.Fallback>TH</Avatar.Fallback>
|
|
83
|
+
</Avatar>
|
|
84
|
+
)
|
|
85
|
+
const USER_AVATAR = (
|
|
86
|
+
<Avatar size="sm" tone="muted">
|
|
87
|
+
<Avatar.Fallback>YOU</Avatar.Fallback>
|
|
88
|
+
</Avatar>
|
|
89
|
+
)
|
|
90
|
+
|
|
1
91
|
export default function Page() {
|
|
92
|
+
const [composerValue, setComposerValue] = useState('')
|
|
93
|
+
const [userMessages, setUserMessages] = useState<ConversationItem[]>([])
|
|
94
|
+
const [paletteOpen, setPaletteOpen] = useState(false)
|
|
95
|
+
const { messages, send, status, reset } = useAgent<{ message: string }>('/api/agents/chat')
|
|
96
|
+
|
|
97
|
+
// ⌘K / Ctrl+K opens the CommandPalette.
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
function onKey(e: KeyboardEvent) {
|
|
100
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
101
|
+
e.preventDefault()
|
|
102
|
+
setPaletteOpen((v) => !v)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
window.addEventListener('keydown', onKey)
|
|
106
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
107
|
+
}, [])
|
|
108
|
+
|
|
109
|
+
// Derive the conversation view from the reconstructed assistant UIMessages
|
|
110
|
+
// (ai-sdk `UIMessageStream`): text parts → messages, tool parts → tool cards.
|
|
111
|
+
const items = useMemo<ConversationItem[]>(() => {
|
|
112
|
+
const ts = new Date().toISOString()
|
|
113
|
+
const agentItems: ConversationItem[] = []
|
|
114
|
+
for (const message of messages) {
|
|
115
|
+
if (message.role !== 'assistant') continue
|
|
116
|
+
message.parts.forEach((part, i) => {
|
|
117
|
+
const id = `${message.id}-${i}`
|
|
118
|
+
if (part.type === 'text') {
|
|
119
|
+
agentItems.push({
|
|
120
|
+
kind: 'message',
|
|
121
|
+
id,
|
|
122
|
+
role: 'assistant',
|
|
123
|
+
content: part.text,
|
|
124
|
+
timestamp: ts,
|
|
125
|
+
})
|
|
126
|
+
} else if (part.type === 'dynamic-tool') {
|
|
127
|
+
agentItems.push({
|
|
128
|
+
kind: 'tool',
|
|
129
|
+
id,
|
|
130
|
+
tool: part.toolName,
|
|
131
|
+
target:
|
|
132
|
+
part.input && typeof part.input === 'object'
|
|
133
|
+
? Object.entries(part.input as Record<string, unknown>)
|
|
134
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
135
|
+
.join(' ')
|
|
136
|
+
: undefined,
|
|
137
|
+
status:
|
|
138
|
+
part.state === 'output-available'
|
|
139
|
+
? 'success'
|
|
140
|
+
: part.state === 'output-error'
|
|
141
|
+
? 'error'
|
|
142
|
+
: 'running',
|
|
143
|
+
output:
|
|
144
|
+
part.state === 'output-available'
|
|
145
|
+
? typeof part.output === 'string'
|
|
146
|
+
? part.output
|
|
147
|
+
: JSON.stringify(part.output, null, 2)
|
|
148
|
+
: part.state === 'output-error'
|
|
149
|
+
? part.errorText
|
|
150
|
+
: undefined,
|
|
151
|
+
timestamp: ts,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
return [...userMessages, ...agentItems]
|
|
157
|
+
}, [userMessages, messages])
|
|
158
|
+
|
|
159
|
+
function handleSubmit(value: string) {
|
|
160
|
+
const trimmed = value.trim()
|
|
161
|
+
if (!trimmed) return
|
|
162
|
+
const id = `u-${userMessages.length}`
|
|
163
|
+
setUserMessages((prev) => [
|
|
164
|
+
...prev,
|
|
165
|
+
{ kind: 'message', id, role: 'user', content: trimmed, timestamp: new Date().toISOString() },
|
|
166
|
+
])
|
|
167
|
+
send({ message: trimmed })
|
|
168
|
+
setComposerValue('')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function handleQuickAction(id: string) {
|
|
172
|
+
setPaletteOpen(false)
|
|
173
|
+
if (id === 'reset') {
|
|
174
|
+
setUserMessages([])
|
|
175
|
+
reset()
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
const action = QUICK_ACTIONS.find((a) => a.id === id)
|
|
179
|
+
if (action) handleSubmit(action.label)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const isStreaming = status === 'streaming'
|
|
183
|
+
const isEmpty = items.length === 0 && !isStreaming
|
|
184
|
+
const hasError = status === 'error'
|
|
185
|
+
|
|
2
186
|
return (
|
|
3
|
-
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
187
|
+
<>
|
|
188
|
+
<ContextWindowBar
|
|
189
|
+
used={CONTEXT_USED}
|
|
190
|
+
total={CONTEXT_TOTAL}
|
|
191
|
+
trailing={MODEL_NAME}
|
|
192
|
+
label="Context window"
|
|
193
|
+
compact
|
|
194
|
+
className="border-border/60 border-b px-6 py-2"
|
|
195
|
+
/>
|
|
196
|
+
|
|
197
|
+
<ScrollArea className="flex-1">
|
|
198
|
+
<div className="mx-auto flex w-full max-w-3xl flex-col gap-4 px-6 py-6">
|
|
199
|
+
{isEmpty ? (
|
|
200
|
+
<EmptyState
|
|
201
|
+
eyebrow="Theo Agent"
|
|
202
|
+
icon={Sparkles}
|
|
203
|
+
title="What should we build today?"
|
|
204
|
+
description="Ask anything. This scaffold ships an agent at agents/chat.ts — edit it to pick your model or add tools."
|
|
205
|
+
action={<QuickActionChips actions={QUICK_ACTIONS} onSelect={handleQuickAction} />}
|
|
206
|
+
/>
|
|
207
|
+
) : (
|
|
208
|
+
<ChatThread>
|
|
209
|
+
{items.map((item) => {
|
|
210
|
+
if (item.kind === 'message') {
|
|
211
|
+
const message: Message = {
|
|
212
|
+
id: item.id,
|
|
213
|
+
role: item.role,
|
|
214
|
+
content: item.content,
|
|
215
|
+
timestamp: item.timestamp,
|
|
216
|
+
model: item.role === 'assistant' ? MODEL_NAME : undefined,
|
|
217
|
+
}
|
|
218
|
+
return (
|
|
219
|
+
<ChatMessage
|
|
220
|
+
key={item.id}
|
|
221
|
+
message={message}
|
|
222
|
+
avatar={item.role === 'assistant' ? ASSISTANT_AVATAR : USER_AVATAR}
|
|
223
|
+
/>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
if (item.kind === 'tool') {
|
|
227
|
+
return (
|
|
228
|
+
<ToolCallCard
|
|
229
|
+
key={item.id}
|
|
230
|
+
tool={item.tool}
|
|
231
|
+
icon={Wrench}
|
|
232
|
+
target={item.target}
|
|
233
|
+
status={item.status}
|
|
234
|
+
output={item.output}
|
|
235
|
+
timestamp={item.timestamp}
|
|
236
|
+
/>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
return (
|
|
240
|
+
<AgentErrorCard
|
|
241
|
+
key={item.id}
|
|
242
|
+
kind="model"
|
|
243
|
+
title="Agent error"
|
|
244
|
+
description={item.message}
|
|
245
|
+
/>
|
|
246
|
+
)
|
|
247
|
+
})}
|
|
248
|
+
{isStreaming && <AgentStreaming model={MODEL_NAME} />}
|
|
249
|
+
</ChatThread>
|
|
250
|
+
)}
|
|
53
251
|
</div>
|
|
252
|
+
</ScrollArea>
|
|
54
253
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
254
|
+
<div className="border-border/60 border-t bg-background/50 backdrop-blur">
|
|
255
|
+
<div className="mx-auto w-full max-w-3xl px-6 py-4">
|
|
256
|
+
{hasError && (
|
|
257
|
+
<div className="mb-3">
|
|
258
|
+
<AgentErrorCard
|
|
259
|
+
kind="network"
|
|
260
|
+
title="Stream ended with an error"
|
|
261
|
+
description="The connection to the agent endpoint was interrupted. Reset to try again."
|
|
262
|
+
action={
|
|
263
|
+
<Button variant="ghost" size="sm" onClick={() => reset()}>
|
|
264
|
+
Reset
|
|
265
|
+
</Button>
|
|
266
|
+
}
|
|
267
|
+
/>
|
|
268
|
+
</div>
|
|
269
|
+
)}
|
|
270
|
+
<ChatComposer
|
|
271
|
+
value={composerValue}
|
|
272
|
+
onValueChange={setComposerValue}
|
|
273
|
+
onSubmit={handleSubmit}
|
|
274
|
+
running={isStreaming}
|
|
275
|
+
placeholder="Ask the agent…"
|
|
276
|
+
leadingActions={
|
|
277
|
+
<Tooltip label="Open command palette (⌘K)" side="top">
|
|
278
|
+
<Button
|
|
279
|
+
type="button"
|
|
280
|
+
variant="ghost"
|
|
281
|
+
size="icon"
|
|
282
|
+
onClick={() => setPaletteOpen(true)}
|
|
283
|
+
aria-label="Open command palette"
|
|
284
|
+
>
|
|
285
|
+
<Command className="size-4" />
|
|
286
|
+
</Button>
|
|
287
|
+
</Tooltip>
|
|
288
|
+
}
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
65
291
|
</div>
|
|
66
|
-
|
|
292
|
+
|
|
293
|
+
<CommandPalette
|
|
294
|
+
open={paletteOpen}
|
|
295
|
+
onOpenChange={setPaletteOpen}
|
|
296
|
+
items={COMMAND_ITEMS}
|
|
297
|
+
onSelect={handleQuickAction}
|
|
298
|
+
placeholder="Run a command…"
|
|
299
|
+
emptyMessage="No matching commands."
|
|
300
|
+
/>
|
|
301
|
+
</>
|
|
67
302
|
)
|
|
68
303
|
}
|