create-theokit 1.2.7 → 1.2.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-theokit",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "license": "Apache-2.0",
@@ -1,73 +1,30 @@
1
1
  import '@theokit/ui/styles.css'
2
2
 
3
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
- ]
4
+ import { ThemeSwitcher } from '@theokit/ui'
5
+ import { Bot } from 'lucide-react'
18
6
 
7
+ /**
8
+ * App shell — a slim top bar (product name + theme toggle) over the chat surface.
9
+ *
10
+ * Deliberately minimal: everything shown WORKS. A scaffold is an honest starting point, not a demo
11
+ * dashboard — so there is no fake cost meter, no fake token counter, and no dead History/Settings buttons.
12
+ * Add real chrome (a conversation list, usage from the stream, a settings route) as you build it.
13
+ */
19
14
  export default function RootLayout() {
20
15
  return (
21
16
  <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>
17
+ <header className="flex items-center justify-between border-border/60 border-b px-4 py-2">
18
+ <span className="inline-flex items-center gap-2">
19
+ <Bot className="size-5 text-primary" aria-hidden />
20
+ <span className="font-semibold text-sm tracking-tight">Theo Agent</span>
21
+ </span>
22
+ <ThemeSwitcher />
23
+ </header>
66
24
 
67
- <main className="flex h-full flex-col overflow-hidden">
68
- <Outlet />
69
- </main>
70
- </div>
25
+ <main className="flex h-full flex-col overflow-hidden">
26
+ <Outlet />
27
+ </main>
71
28
  </div>
72
29
  )
73
30
  }
@@ -9,17 +9,17 @@ import Page from './page'
9
9
  * must both type-check AND render. Renders the real React tree in jsdom.
10
10
  */
11
11
  describe('default chat page', () => {
12
- it('opens with the agent greeting + quick actions + composer on first load', () => {
12
+ it('opens with the agent greeting + honest starter prompts + composer + a working New chat', () => {
13
13
  const { container } = render(<Page />)
14
- // The agent greets first — the transcript starts warm (like the terminal surface). ChatMessage renders
15
- // the text across markdown spans, so assert the assistant message container structurally, not by text.
14
+ // The agent greets first — the transcript starts warm. ChatMessage renders the text across markdown
15
+ // spans, so assert the assistant message container structurally, not by text.
16
16
  expect(container.querySelector('[data-theo-chat-message="assistant"]')).not.toBeNull()
17
- // … with the quick-action suggestions and the composer.
18
- expect(screen.getByText('Summarize this page')).toBeDefined()
19
- expect(screen.getByText('Show available tools')).toBeDefined()
20
- expect(screen.getByText('Start a new conversation')).toBeDefined()
21
- expect(screen.getByPlaceholderText('Ask the agent…')).toBeDefined()
22
- expect(screen.getByLabelText('Open command palette')).toBeDefined()
17
+ // Honest starter prompts (each sends a real message the agent can answer) + the composer.
18
+ expect(screen.getByText('What can you help me with?')).toBeDefined()
19
+ expect(screen.getByText('Write a haiku about TypeScript')).toBeDefined()
20
+ expect(screen.getByPlaceholderText('Message the agent…')).toBeDefined()
21
+ // `New chat` (reset) is real — no fake cost/token meters or dead History/Settings buttons here.
22
+ expect(screen.getByLabelText('New chat')).toBeDefined()
23
23
  })
24
24
 
25
25
  it('ChatMessage accepts a UIMessage and renders its message container (auto-dispatch)', () => {
@@ -8,102 +8,55 @@ import {
8
8
  AgentStreaming,
9
9
  AgentErrorCard,
10
10
  QuickActionChips,
11
- ContextWindowBar,
12
11
  type UIMessage,
13
12
  type QuickAction,
14
13
  } from '@theokit/ui'
15
- import { CommandPalette, Avatar, Tooltip, Button, ScrollArea, type CommandItem } from '@usetheo/ui'
16
- import { Sparkles, Wrench, RotateCcw, Command } from 'lucide-react'
14
+ import { Button, ScrollArea } from '@usetheo/ui'
15
+ import { Plus, Sparkles } from 'lucide-react'
17
16
  import { useAgent } from 'theokit/client'
18
17
 
19
18
  /**
20
- * Default scaffold — an Agent Surface, composed entirely from TheoUI.
19
+ * Default scaffold — a working agent chat, composed from TheoUI. Everything here FUNCTIONS: the thread
20
+ * streams real replies, `New chat` resets, the starter prompts send real messages, and the error card shows
21
+ * the real error. No fake cost/token meters.
21
22
  *
22
- * ChatThread / ChatMessage → conversation (ChatMessage auto-dispatches text,
23
- * tool-call, and reasoning parts of each UIMessage)
24
- * AgentStreaming → streaming indicator
25
- * AgentErrorCard → error display
26
- * ChatComposer → bottom input bar
27
- * QuickActionChips → first-load suggestions
28
- * ContextWindowBar → context usage at top
29
- * CommandPalette → ⌘K quick actions
30
- * Avatar / Tooltip → message faces + icon hints
31
- *
32
- * `useAgent` binds to the `agents/chat.ts` endpoint and consumes the ai-sdk `UIMessageStream`. It opens a
33
- * FRESH stream per send, so `messages` hold only the CURRENT turn (and carry no stable id) — we OWN the
34
- * transcript: `history` accumulates every finished turn with our own unique ids (`u-N` / `a-N` / `greeting`),
35
- * and the in-flight reply is shown live until it commits. This keeps the order correct, the history complete,
36
- * and every id unique. Edit `agents/chat.ts` to pick your model / add tools.
23
+ * `useAgent` opens a FRESH stream per send — its `messages` hold only the CURRENT turn (with no stable id),
24
+ * so we OWN the transcript: `history` accumulates each finished turn with our own unique ids, and the
25
+ * in-flight reply is shown live until it commits (correct order, complete history, unique ids). Edit
26
+ * `agents/chat.ts` to pick your model / add tools.
37
27
  */
38
28
 
39
- const QUICK_ACTIONS: QuickAction[] = [
40
- { id: 'summarize', label: 'Summarize this page', icon: Sparkles },
41
- { id: 'tools', label: 'Show available tools', icon: Wrench },
42
- { id: 'reset', label: 'Start a new conversation', icon: RotateCcw },
43
- ]
44
-
45
- const COMMAND_ITEMS: CommandItem[] = QUICK_ACTIONS.map((a) => ({
46
- id: a.id,
47
- label: a.label,
48
- icon: a.icon,
49
- group: 'Quick actions',
50
- }))
51
-
52
- // Display-only context-window hint. The agent's real model lives in `agents/chat.ts`
53
- // (`model: 'openai/gpt-4o-mini'`); wire real token counts from the stream when you need them.
54
- const CONTEXT_USED = 4_200
55
- const CONTEXT_TOTAL = 200_000
56
29
  const MODEL_NAME = 'gpt-4o-mini'
57
30
 
58
31
  /** The agent's opening line — so the conversation starts warm instead of empty. */
59
32
  const GREETING: UIMessage = {
60
33
  id: 'greeting',
61
34
  role: 'assistant',
62
- parts: [
63
- {
64
- type: 'text',
65
- text: "Hi — I'm your TheoKit agent. Ask me anything and I'll stream a reply. Try a quick action below or type your own.",
66
- },
67
- ],
35
+ parts: [{ type: 'text', text: "Hi — I'm your TheoKit agent. Ask me anything and I'll stream a reply." }],
68
36
  }
69
37
 
70
- const ASSISTANT_AVATAR = (
71
- <Avatar size="sm" tone="primary">
72
- <Avatar.Fallback>TH</Avatar.Fallback>
73
- </Avatar>
74
- )
75
- const USER_AVATAR = (
76
- <Avatar size="sm" tone="muted">
77
- <Avatar.Fallback>YOU</Avatar.Fallback>
78
- </Avatar>
79
- )
38
+ /** Honest starter prompts — each sends a real message the scaffold agent can actually answer. */
39
+ const STARTERS: QuickAction[] = [
40
+ { id: 'help', label: 'What can you help me with?', icon: Sparkles },
41
+ { id: 'haiku', label: 'Write a haiku about TypeScript', icon: Sparkles },
42
+ { id: 'async', label: 'Explain async/await in one paragraph', icon: Sparkles },
43
+ ]
80
44
 
81
45
  export default function Page() {
82
46
  const [composerValue, setComposerValue] = useState('')
83
- const [paletteOpen, setPaletteOpen] = useState(false)
84
47
  const [history, setHistory] = useState<UIMessage[]>([GREETING])
85
- const { messages, send, status, reset } = useAgent<{ message: string }>('/api/agents/chat')
86
-
87
- // ⌘K / Ctrl+K opens the CommandPalette.
88
- useEffect(() => {
89
- function onKey(e: KeyboardEvent) {
90
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
91
- e.preventDefault()
92
- setPaletteOpen((v) => !v)
93
- }
94
- }
95
- window.addEventListener('keydown', onKey)
96
- return () => window.removeEventListener('keydown', onKey)
97
- }, [])
48
+ const { messages, send, status, reset, error } = useAgent<{ message: string }>('/api/agents/chat')
98
49
 
99
50
  const isStreaming = status === 'streaming'
51
+ const hasError = status === 'error'
52
+
100
53
  const users = history.filter((m) => m.role === 'user').length
101
54
  const replies = history.filter((m) => m.role === 'assistant' && m.id !== 'greeting').length
102
55
  // A sent prompt is still awaiting its committed reply — the current turn is "in flight".
103
56
  const pending = users > replies
104
57
 
105
58
  // Merge the in-flight turn's parts into ONE assistant message with our own unique id (the SDK's ids are
106
- // empty, which would collide in the thread). `suffix` distinguishes the live copy from the committed one.
59
+ // empty, which would collide). `suffix` distinguishes the live copy from the committed one.
107
60
  const inflightReply = (suffix: string): UIMessage => ({
108
61
  id: `a-${String(replies)}${suffix}`,
109
62
  role: 'assistant',
@@ -133,45 +86,30 @@ export default function Page() {
133
86
  setComposerValue('')
134
87
  }
135
88
 
136
- function handleQuickAction(id: string) {
137
- setPaletteOpen(false)
138
- if (id === 'reset') {
139
- setHistory([GREETING])
140
- reset()
141
- return
142
- }
143
- const action = QUICK_ACTIONS.find((a) => a.id === id)
144
- // Quick-action labels are strings; only a string can be sent as a prompt.
145
- if (action && typeof action.label === 'string') handleSubmit(action.label)
89
+ function newChat() {
90
+ setHistory([GREETING])
91
+ reset()
92
+ setComposerValue('')
146
93
  }
147
94
 
148
- const hasError = status === 'error'
149
-
150
95
  return (
151
96
  <>
152
- <ContextWindowBar
153
- used={CONTEXT_USED}
154
- total={CONTEXT_TOTAL}
155
- trailing={MODEL_NAME}
156
- label="Context window"
157
- compact
158
- className="border-border/60 border-b px-6 py-2"
159
- />
160
-
161
97
  <ScrollArea className="flex-1">
162
98
  <div className="mx-auto flex w-full max-w-3xl flex-col gap-4 px-6 py-6">
163
99
  <ChatThread>
164
100
  {thread.map((message) => (
165
- <ChatMessage
166
- key={message.id}
167
- message={message}
168
- avatar={message.role === 'assistant' ? ASSISTANT_AVATAR : USER_AVATAR}
169
- />
101
+ <ChatMessage key={message.id} message={message} />
170
102
  ))}
171
103
  {isStreaming && <AgentStreaming model={MODEL_NAME} />}
172
104
  </ChatThread>
173
105
  {onlyGreeting && (
174
- <QuickActionChips actions={QUICK_ACTIONS} onSelect={handleQuickAction} />
106
+ <QuickActionChips
107
+ actions={STARTERS}
108
+ onSelect={(id) => {
109
+ const action = STARTERS.find((s) => s.id === id)
110
+ if (action && typeof action.label === 'string') handleSubmit(action.label)
111
+ }}
112
+ />
175
113
  )}
176
114
  </div>
177
115
  </ScrollArea>
@@ -182,11 +120,11 @@ export default function Page() {
182
120
  <div className="mb-3">
183
121
  <AgentErrorCard
184
122
  kind="network"
185
- title="Stream ended with an error"
186
- detail="The connection to the agent endpoint was interrupted. Reset to try again."
123
+ title="The agent stream ended with an error"
124
+ detail={error?.message ?? 'Something went wrong. Start a new chat to try again.'}
187
125
  actions={
188
- <Button variant="ghost" size="sm" onClick={() => reset()}>
189
- Reset
126
+ <Button variant="ghost" size="sm" onClick={newChat}>
127
+ New chat
190
128
  </Button>
191
129
  }
192
130
  />
@@ -197,32 +135,22 @@ export default function Page() {
197
135
  onValueChange={setComposerValue}
198
136
  onSubmit={handleSubmit}
199
137
  running={isStreaming}
200
- placeholder="Ask the agent…"
138
+ placeholder="Message the agent…"
201
139
  leadingActions={
202
- <Tooltip label="Open command palette (⌘K)" side="top">
203
- <Button
204
- type="button"
205
- variant="ghost"
206
- size="icon"
207
- onClick={() => setPaletteOpen(true)}
208
- aria-label="Open command palette"
209
- >
210
- <Command className="size-4" />
211
- </Button>
212
- </Tooltip>
140
+ <Button
141
+ type="button"
142
+ variant="ghost"
143
+ size="icon"
144
+ onClick={newChat}
145
+ aria-label="New chat"
146
+ title="New chat"
147
+ >
148
+ <Plus className="size-4" />
149
+ </Button>
213
150
  }
214
151
  />
215
152
  </div>
216
153
  </div>
217
-
218
- <CommandPalette
219
- open={paletteOpen}
220
- onOpenChange={setPaletteOpen}
221
- items={COMMAND_ITEMS}
222
- onSelect={handleQuickAction}
223
- placeholder="Run a command…"
224
- emptyMessage="No matching commands."
225
- />
226
154
  </>
227
155
  )
228
156
  }
@@ -15,11 +15,12 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "theokit": "^0.30.2",
18
+ "theokit": "^0.30.3",
19
19
  "@theokit/agents": "^0.35.2",
20
20
  "@theokit/sdk": "^2.13.0",
21
21
  "@theokit/ui": "^1.0.0",
22
22
  "@usetheo/ui": "^0.14.0",
23
+ "ai": "^7.0.0",
23
24
  "lucide-react": "^0.469.0",
24
25
  "react": "^19.0.0",
25
26
  "react-dom": "^19.0.0",