@pikku/assistant-ui 0.12.0 → 0.12.2

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.
@@ -1,4 +1,5 @@
1
- import { useState, type FunctionComponent } from 'react'
1
+ import { createContext, useContext, useState, useMemo, type FunctionComponent } from 'react'
2
+ import Markdown from 'react-markdown'
2
3
  import {
3
4
  AssistantRuntimeProvider,
4
5
  ThreadPrimitive,
@@ -7,20 +8,103 @@ import {
7
8
  } from '@assistant-ui/react'
8
9
  import {
9
10
  usePikkuAgentRuntime,
11
+ PikkuApprovalContext,
12
+ usePikkuApproval,
13
+ resolvePikkuToolStatus,
14
+ type PikkuToolStatus,
10
15
  type PikkuAgentRuntimeOptions,
11
16
  } from './use-pikku-agent-runtime.js'
12
17
 
13
18
  export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
14
19
  emptyMessage?: string
20
+ /** Hide tool calls from the chat display.
21
+ * - `true`: hide all non-approval tool calls
22
+ * - `string[]`: hide tool calls matching these names
23
+ */
24
+ hideToolCalls?: boolean | string[]
25
+ dark?: boolean
26
+ }
27
+
28
+ interface ChatColors {
29
+ bg: string
30
+ userBubble: string
31
+ assistantBubble: string
32
+ text: string
33
+ textMuted: string
34
+ border: string
35
+ codeBg: string
36
+ inputBg: string
37
+ sendBg: string
38
+ sendColor: string
39
+ approvalBg: string
40
+ approvalBorder: string
41
+ successBg: string
42
+ successColor: string
43
+ errorBg: string
44
+ errorColor: string
45
+ }
46
+
47
+ const lightColors: ChatColors = {
48
+ bg: '#ffffff',
49
+ userBubble: '#e3f2fd',
50
+ assistantBubble: '#f5f5f5',
51
+ text: '#1a1a1a',
52
+ textMuted: '#888',
53
+ border: '#ddd',
54
+ codeBg: '#f5f5f5',
55
+ inputBg: 'transparent',
56
+ sendBg: '#1976d2',
57
+ sendColor: '#fff',
58
+ approvalBg: '#fef9e7',
59
+ approvalBorder: '#e9a211',
60
+ successBg: '#e8f5e9',
61
+ successColor: '#2e7d32',
62
+ errorBg: '#ffebee',
63
+ errorColor: '#c62828',
64
+ }
65
+
66
+ const darkColors: ChatColors = {
67
+ bg: 'transparent',
68
+ userBubble: 'rgba(0, 230, 138, 0.1)',
69
+ assistantBubble: '#1e1e2e',
70
+ text: '#e0e0e8',
71
+ textMuted: '#8888a0',
72
+ border: '#2a2a3e',
73
+ codeBg: '#0e0e16',
74
+ inputBg: 'transparent',
75
+ sendBg: '#00cc7a',
76
+ sendColor: '#0a0a0f',
77
+ approvalBg: 'rgba(233, 162, 17, 0.1)',
78
+ approvalBorder: '#e9a211',
79
+ successBg: 'rgba(0, 230, 138, 0.1)',
80
+ successColor: '#00e68a',
81
+ errorBg: 'rgba(220, 38, 38, 0.1)',
82
+ errorColor: '#f87171',
83
+ }
84
+
85
+ const ColorsContext = createContext<ChatColors>(lightColors)
86
+ const HideToolCallsContext = createContext<boolean | string[] | undefined>(undefined)
87
+
88
+ function shouldHideToolCall(
89
+ hideToolCalls: boolean | string[] | undefined,
90
+ toolName: string
91
+ ): boolean {
92
+ if (!hideToolCalls) return false
93
+ if (hideToolCalls === true) return true
94
+ return hideToolCalls.includes(toolName)
15
95
  }
16
96
 
17
97
  const ToolCallDisplay: FunctionComponent<{
98
+ toolCallId: string
18
99
  toolName: string
19
100
  args: Record<string, unknown>
20
101
  result?: unknown
21
- status: { type: string }
102
+ status: PikkuToolStatus
22
103
  addResult?: (result: unknown) => void
23
- }> = ({ toolName, args, result, status, addResult }) => {
104
+ }> = ({ toolCallId, toolName, args, result, status, addResult }) => {
105
+ const colors = useContext(ColorsContext)
106
+ const hideToolCalls = useContext(HideToolCallsContext)
107
+ const { handleApproval } = usePikkuApproval()
24
108
  const [expanded, setExpanded] = useState(false)
25
109
  const isApproval = status.type === 'requires-action'
26
110
  const approvalReason = (args as any)?.__approvalReason
@@ -30,15 +114,25 @@ const ToolCallDisplay: FunctionComponent<{
30
114
  null
31
115
  )
32
116
 
117
+ // Hide responded approval tool calls
118
+ if (isApproval && responded && shouldHideToolCall(hideToolCalls, toolName)) {
119
+ return null
120
+ }
121
+
122
+ // Hide non-approval tool calls
123
+ if (!isApproval && shouldHideToolCall(hideToolCalls, toolName)) {
124
+ return null
125
+ }
126
+
33
127
  if (isApproval && !responded) {
34
128
  return (
35
129
  <div
36
130
  style={{
37
- border: '1px solid #e9a211',
131
+ border: `1px solid ${colors.approvalBorder}`,
38
132
  borderRadius: 6,
39
133
  padding: 12,
40
134
  margin: '4px 0',
41
- backgroundColor: '#fef9e7',
135
+ backgroundColor: colors.approvalBg,
42
136
  }}
43
137
  >
44
138
  <div
@@ -49,24 +143,26 @@ const ToolCallDisplay: FunctionComponent<{
49
143
  marginBottom: 8,
50
144
  fontWeight: 600,
51
145
  fontSize: 13,
146
+ color: colors.text,
52
147
  }}
53
148
  >
54
149
  Approval required
55
150
  </div>
56
151
  {approvalReason && (
57
- <div style={{ fontSize: 13, marginBottom: 4 }}>{approvalReason}</div>
152
+ <div style={{ fontSize: 13, marginBottom: 4, color: colors.text }}>{approvalReason}</div>
58
153
  )}
59
- <div style={{ fontSize: 12, color: '#666', marginBottom: 4 }}>
154
+ <div style={{ fontSize: 12, color: colors.textMuted, marginBottom: 4 }}>
60
155
  The agent wants to call <code>{toolName}</code>
61
156
  </div>
62
157
  <pre
63
158
  style={{
64
159
  fontSize: 11,
65
- background: '#f5f5f5',
160
+ background: colors.codeBg,
66
161
  padding: 8,
67
162
  borderRadius: 4,
68
163
  overflow: 'auto',
69
164
  marginBottom: 8,
165
+ color: colors.text,
70
166
  }}
71
167
  >
72
168
  {JSON.stringify(displayArgs, null, 2)}
@@ -75,15 +171,16 @@ const ToolCallDisplay: FunctionComponent<{
75
171
  <button
76
172
  onClick={() => {
77
173
  setResponded('approved')
174
+ handleApproval(toolCallId, true)
78
175
  addResult?.({ approved: true })
79
176
  }}
80
177
  style={{
81
178
  padding: '4px 12px',
82
179
  fontSize: 12,
83
- border: '1px solid #2e7d32',
180
+ border: `1px solid ${colors.successColor}`,
84
181
  borderRadius: 4,
85
- background: '#e8f5e9',
86
- color: '#2e7d32',
182
+ background: colors.successBg,
183
+ color: colors.successColor,
87
184
  cursor: 'pointer',
88
185
  }}
89
186
  >
@@ -92,15 +189,16 @@ const ToolCallDisplay: FunctionComponent<{
92
189
  <button
93
190
  onClick={() => {
94
191
  setResponded('denied')
192
+ handleApproval(toolCallId, false)
95
193
  addResult?.({ approved: false })
96
194
  }}
97
195
  style={{
98
196
  padding: '4px 12px',
99
197
  fontSize: 12,
100
- border: '1px solid #c62828',
198
+ border: `1px solid ${colors.errorColor}`,
101
199
  borderRadius: 4,
102
- background: '#ffebee',
103
- color: '#c62828',
200
+ background: colors.errorBg,
201
+ color: colors.errorColor,
104
202
  cursor: 'pointer',
105
203
  }}
106
204
  >
@@ -115,7 +213,7 @@ const ToolCallDisplay: FunctionComponent<{
115
213
  return (
116
214
  <div
117
215
  style={{
118
- border: '1px solid #ddd',
216
+ border: `1px solid ${colors.border}`,
119
217
  borderRadius: 6,
120
218
  padding: 8,
121
219
  margin: '4px 0',
@@ -123,6 +221,7 @@ const ToolCallDisplay: FunctionComponent<{
123
221
  alignItems: 'center',
124
222
  gap: 8,
125
223
  fontSize: 13,
224
+ color: colors.text,
126
225
  }}
127
226
  >
128
227
  <span style={{ fontWeight: 500 }}>{toolName}</span>
@@ -131,8 +230,8 @@ const ToolCallDisplay: FunctionComponent<{
131
230
  fontSize: 11,
132
231
  padding: '2px 6px',
133
232
  borderRadius: 3,
134
- background: responded === 'approved' ? '#e8f5e9' : '#ffebee',
135
- color: responded === 'approved' ? '#2e7d32' : '#c62828',
233
+ background: responded === 'approved' ? colors.successBg : colors.errorBg,
234
+ color: responded === 'approved' ? colors.successColor : colors.errorColor,
136
235
  }}
137
236
  >
138
237
  {responded}
@@ -144,7 +243,7 @@ const ToolCallDisplay: FunctionComponent<{
144
243
  return (
145
244
  <div
146
245
  style={{
147
- border: '1px solid #ddd',
246
+ border: `1px solid ${colors.border}`,
148
247
  borderRadius: 6,
149
248
  padding: 8,
150
249
  margin: '4px 0',
@@ -162,6 +261,7 @@ const ToolCallDisplay: FunctionComponent<{
162
261
  width: '100%',
163
262
  padding: 0,
164
263
  fontSize: 13,
264
+ color: colors.text,
165
265
  }}
166
266
  >
167
267
  <span>{expanded ? '\u25BC' : '\u25B6'}</span>
@@ -169,16 +269,42 @@ const ToolCallDisplay: FunctionComponent<{
169
269
  {toolName}
170
270
  </span>
171
271
  {status.type === 'running' && (
172
- <span style={{ fontSize: 11, color: '#888' }}>running...</span>
272
+ <span style={{ fontSize: 11, color: colors.textMuted }}>running...</span>
273
+ )}
274
+ {status.type === 'error' && (
275
+ <span
276
+ style={{
277
+ fontSize: 11,
278
+ padding: '1px 5px',
279
+ borderRadius: 3,
280
+ background: colors.errorBg,
281
+ color: colors.errorColor,
282
+ }}
283
+ >
284
+ error
285
+ </span>
286
+ )}
287
+ {status.type === 'denied' && (
288
+ <span
289
+ style={{
290
+ fontSize: 11,
291
+ padding: '1px 5px',
292
+ borderRadius: 3,
293
+ background: colors.errorBg,
294
+ color: colors.errorColor,
295
+ }}
296
+ >
297
+ denied
298
+ </span>
173
299
  )}
174
- {status.type === 'complete' && (
300
+ {status.type === 'completed' && (
175
301
  <span
176
302
  style={{
177
303
  fontSize: 11,
178
304
  padding: '1px 5px',
179
305
  borderRadius: 3,
180
- background: '#e8f5e9',
181
- color: '#2e7d32',
306
+ background: colors.successBg,
307
+ color: colors.successColor,
182
308
  }}
183
309
  >
184
310
  done
@@ -187,16 +313,17 @@ const ToolCallDisplay: FunctionComponent<{
187
313
  </button>
188
314
  {expanded && (
189
315
  <div style={{ marginTop: 8 }}>
190
- <div style={{ fontSize: 12, color: '#888', marginBottom: 2 }}>
316
+ <div style={{ fontSize: 12, color: colors.textMuted, marginBottom: 2 }}>
191
317
  Arguments:
192
318
  </div>
193
319
  <pre
194
320
  style={{
195
321
  fontSize: 11,
196
- background: '#f5f5f5',
322
+ background: colors.codeBg,
197
323
  padding: 8,
198
324
  borderRadius: 4,
199
325
  overflow: 'auto',
326
+ color: colors.text,
200
327
  }}
201
328
  >
202
329
  {JSON.stringify(displayArgs, null, 2)}
@@ -204,17 +331,18 @@ const ToolCallDisplay: FunctionComponent<{
204
331
  {result !== undefined && (
205
332
  <>
206
333
  <div
207
- style={{ fontSize: 12, color: '#888', marginTop: 8, marginBottom: 2 }}
334
+ style={{ fontSize: 12, color: colors.textMuted, marginTop: 8, marginBottom: 2 }}
208
335
  >
209
336
  Result:
210
337
  </div>
211
338
  <pre
212
339
  style={{
213
340
  fontSize: 11,
214
- background: '#f5f5f5',
341
+ background: colors.codeBg,
215
342
  padding: 8,
216
343
  borderRadius: 4,
217
344
  overflow: 'auto',
345
+ color: colors.text,
218
346
  }}
219
347
  >
220
348
  {typeof result === 'string'
@@ -229,169 +357,237 @@ const ToolCallDisplay: FunctionComponent<{
229
357
  )
230
358
  }
231
359
 
232
- const UserMessage: FunctionComponent = () => (
233
- <div
234
- style={{
235
- display: 'flex',
236
- justifyContent: 'flex-end',
237
- width: '100%',
238
- }}
239
- >
240
- <div style={{ maxWidth: '80%' }}>
241
- <div
242
- style={{
243
- fontSize: 12,
244
- color: '#888',
245
- marginBottom: 4,
246
- textAlign: 'right',
247
- }}
248
- >
249
- You
250
- </div>
251
- <div
252
- style={{
253
- padding: 12,
254
- borderRadius: 12,
255
- backgroundColor: '#e3f2fd',
256
- }}
257
- >
258
- <MessagePrimitive.Content
259
- components={{
260
- Text: ({ text }) => (
261
- <span style={{ fontSize: 14, whiteSpace: 'pre-wrap' }}>
262
- {text}
263
- </span>
264
- ),
360
+ const MarkdownText: FunctionComponent<{ text: string; colors: ChatColors }> = ({ text, colors }) => {
361
+ const components = useMemo(() => ({
362
+ p: ({ children }: any) => (
363
+ <p style={{ margin: '0 0 8px', fontSize: 14, lineHeight: 1.6, color: colors.text }}>{children}</p>
364
+ ),
365
+ strong: ({ children }: any) => (
366
+ <strong style={{ fontWeight: 600, color: colors.text }}>{children}</strong>
367
+ ),
368
+ em: ({ children }: any) => (
369
+ <em style={{ color: colors.text }}>{children}</em>
370
+ ),
371
+ ul: ({ children }: any) => (
372
+ <ul style={{ margin: '4px 0 8px', paddingLeft: 20, fontSize: 14, color: colors.text }}>{children}</ul>
373
+ ),
374
+ ol: ({ children }: any) => (
375
+ <ol style={{ margin: '4px 0 8px', paddingLeft: 20, fontSize: 14, color: colors.text }}>{children}</ol>
376
+ ),
377
+ li: ({ children }: any) => (
378
+ <li style={{ marginBottom: 2, lineHeight: 1.6 }}>{children}</li>
379
+ ),
380
+ code: ({ children, className }: any) => {
381
+ const isBlock = className?.startsWith('language-')
382
+ if (isBlock) {
383
+ return (
384
+ <pre style={{ background: colors.codeBg, padding: 10, borderRadius: 4, overflow: 'auto', margin: '4px 0 8px', fontSize: 12 }}>
385
+ <code style={{ color: colors.text }}>{children}</code>
386
+ </pre>
387
+ )
388
+ }
389
+ return (
390
+ <code style={{ background: colors.codeBg, padding: '1px 4px', borderRadius: 3, fontSize: 13, color: colors.text }}>
391
+ {children}
392
+ </code>
393
+ )
394
+ },
395
+ pre: ({ children }: any) => <>{children}</>,
396
+ h1: ({ children }: any) => <h3 style={{ margin: '8px 0 4px', fontSize: 16, fontWeight: 600, color: colors.text }}>{children}</h3>,
397
+ h2: ({ children }: any) => <h4 style={{ margin: '8px 0 4px', fontSize: 15, fontWeight: 600, color: colors.text }}>{children}</h4>,
398
+ h3: ({ children }: any) => <h5 style={{ margin: '8px 0 4px', fontSize: 14, fontWeight: 600, color: colors.text }}>{children}</h5>,
399
+ a: ({ href, children }: any) => (
400
+ <a href={href} target="_blank" rel="noopener noreferrer" style={{ color: colors.textMuted, textDecoration: 'underline' }}>{children}</a>
401
+ ),
402
+ }), [colors])
403
+
404
+ return <Markdown components={components}>{text}</Markdown>
405
+ }
406
+
407
+ const UserMessage: FunctionComponent = () => {
408
+ const colors = useContext(ColorsContext)
409
+ return (
410
+ <div
411
+ style={{
412
+ display: 'flex',
413
+ justifyContent: 'flex-end',
414
+ width: '100%',
415
+ }}
416
+ >
417
+ <div style={{ maxWidth: '80%' }}>
418
+ <div
419
+ style={{
420
+ fontSize: 12,
421
+ color: colors.textMuted,
422
+ marginBottom: 4,
423
+ textAlign: 'right',
265
424
  }}
266
- />
425
+ >
426
+ You
427
+ </div>
428
+ <div
429
+ style={{
430
+ padding: 12,
431
+ borderRadius: 12,
432
+ backgroundColor: colors.userBubble,
433
+ }}
434
+ >
435
+ <MessagePrimitive.Content
436
+ components={{
437
+ Text: ({ text }) => (
438
+ <span style={{ fontSize: 14, whiteSpace: 'pre-wrap', color: colors.text }}>
439
+ {text}
440
+ </span>
441
+ ),
442
+ }}
443
+ />
444
+ </div>
267
445
  </div>
268
446
  </div>
269
- </div>
270
- )
447
+ )
448
+ }
271
449
 
272
- const AssistantMessage: FunctionComponent = () => (
273
- <div
274
- style={{
275
- display: 'flex',
276
- justifyContent: 'flex-start',
277
- width: '100%',
278
- }}
279
- >
280
- <div style={{ maxWidth: '80%' }}>
281
- <div style={{ fontSize: 12, color: '#888', marginBottom: 4 }}>
282
- Assistant
283
- </div>
284
- <div
285
- style={{
286
- padding: 12,
287
- borderRadius: 12,
288
- backgroundColor: '#f5f5f5',
289
- }}
290
- >
291
- <MessagePrimitive.Content
292
- components={{
293
- Text: ({ text }) => (
294
- <span style={{ fontSize: 14, whiteSpace: 'pre-wrap' }}>
295
- {text}
296
- </span>
297
- ),
298
- tools: {
299
- Fallback: (props) => (
300
- <ToolCallDisplay
301
- toolName={props.toolName}
302
- args={props.args as Record<string, unknown>}
303
- result={props.result}
304
- status={props.status}
305
- addResult={props.addResult}
306
- />
307
- ),
308
- },
450
+ const AssistantMessage: FunctionComponent = () => {
451
+ const colors = useContext(ColorsContext)
452
+ return (
453
+ <div
454
+ style={{
455
+ display: 'flex',
456
+ justifyContent: 'flex-start',
457
+ width: '100%',
458
+ }}
459
+ >
460
+ <div style={{ maxWidth: '80%' }}>
461
+ <div style={{ fontSize: 12, color: colors.textMuted, marginBottom: 4 }}>
462
+ Assistant
463
+ </div>
464
+ <div
465
+ style={{
466
+ padding: 12,
467
+ borderRadius: 12,
468
+ backgroundColor: colors.assistantBubble,
309
469
  }}
310
- />
311
- <MessagePrimitive.If last>
312
- <ThreadPrimitive.If running>
313
- <div
314
- style={{
315
- display: 'flex',
316
- alignItems: 'center',
317
- gap: 6,
318
- marginTop: 8,
319
- fontSize: 13,
320
- color: '#888',
321
- }}
322
- >
323
- Thinking...
324
- </div>
325
- </ThreadPrimitive.If>
326
- </MessagePrimitive.If>
470
+ >
471
+ <MessagePrimitive.Content
472
+ components={{
473
+ Text: ({ text }) => (
474
+ <MarkdownText text={text} colors={colors} />
475
+ ),
476
+ tools: {
477
+ Fallback: (props) => (
478
+ <ToolCallDisplay
479
+ toolCallId={props.toolCallId}
480
+ toolName={props.toolName}
481
+ args={props.args as Record<string, unknown>}
482
+ result={props.result}
483
+ status={resolvePikkuToolStatus(props.status, props.result)}
484
+ addResult={props.addResult}
485
+ />
486
+ ),
487
+ },
488
+ }}
489
+ />
490
+ <MessagePrimitive.If last>
491
+ <ThreadPrimitive.If running>
492
+ <div
493
+ style={{
494
+ display: 'flex',
495
+ alignItems: 'center',
496
+ gap: 6,
497
+ marginTop: 8,
498
+ fontSize: 13,
499
+ color: colors.textMuted,
500
+ }}
501
+ >
502
+ Thinking...
503
+ </div>
504
+ </ThreadPrimitive.If>
505
+ </MessagePrimitive.If>
506
+ </div>
327
507
  </div>
328
508
  </div>
329
- </div>
330
- )
509
+ )
510
+ }
331
511
 
332
- const PikkuComposer: FunctionComponent = () => (
333
- <div style={{ padding: '8px 0 16px' }}>
334
- <ComposerPrimitive.Root>
335
- <div
336
- style={{
337
- border: '1px solid #ddd',
338
- borderRadius: 12,
339
- overflow: 'hidden',
340
- display: 'flex',
341
- alignItems: 'flex-end',
342
- padding: '6px 12px',
343
- gap: 8,
344
- }}
345
- >
346
- <ComposerPrimitive.Input
347
- placeholder="Message..."
348
- rows={2}
349
- style={{
350
- flex: 1,
351
- border: 'none',
352
- outline: 'none',
353
- resize: 'none',
354
- fontSize: 14,
355
- fontFamily: 'inherit',
356
- padding: '4px 0',
357
- background: 'transparent',
358
- }}
359
- />
360
- <ComposerPrimitive.Send
512
+ const PikkuComposer: FunctionComponent<{ disabled?: boolean }> = ({
513
+ disabled,
514
+ }) => {
515
+ const colors = useContext(ColorsContext)
516
+ return (
517
+ <div style={{ padding: '8px 0 16px' }}>
518
+ <ComposerPrimitive.Root>
519
+ <div
361
520
  style={{
362
- width: 28,
363
- height: 28,
364
- borderRadius: '50%',
365
- border: 'none',
366
- background: '#1976d2',
367
- color: '#fff',
368
- cursor: 'pointer',
521
+ border: `1px solid ${colors.border}`,
522
+ borderRadius: 12,
523
+ overflow: 'hidden',
369
524
  display: 'flex',
370
- alignItems: 'center',
371
- justifyContent: 'center',
372
- flexShrink: 0,
373
- marginBottom: 2,
374
- fontSize: 14,
525
+ alignItems: 'flex-end',
526
+ padding: '6px 12px',
527
+ gap: 8,
528
+ ...(disabled ? { opacity: 0.5, pointerEvents: 'none' as const } : {}),
375
529
  }}
376
530
  >
377
- &#9654;
378
- </ComposerPrimitive.Send>
379
- </div>
380
- </ComposerPrimitive.Root>
381
- </div>
382
- )
531
+ <ComposerPrimitive.Input
532
+ placeholder={disabled ? 'Respond to approval request above...' : 'Message...'}
533
+ rows={2}
534
+ disabled={disabled}
535
+ style={{
536
+ flex: 1,
537
+ border: 'none',
538
+ outline: 'none',
539
+ resize: 'none',
540
+ fontSize: 14,
541
+ fontFamily: 'inherit',
542
+ padding: '4px 0',
543
+ background: colors.inputBg,
544
+ color: colors.text,
545
+ }}
546
+ />
547
+ <ComposerPrimitive.Send
548
+ disabled={disabled}
549
+ style={{
550
+ width: 28,
551
+ height: 28,
552
+ borderRadius: '50%',
553
+ border: 'none',
554
+ background: disabled ? colors.textMuted : colors.sendBg,
555
+ color: colors.sendColor,
556
+ cursor: disabled ? 'not-allowed' : 'pointer',
557
+ display: 'flex',
558
+ alignItems: 'center',
559
+ justifyContent: 'center',
560
+ flexShrink: 0,
561
+ marginBottom: 2,
562
+ fontSize: 14,
563
+ }}
564
+ >
565
+ &#9654;
566
+ </ComposerPrimitive.Send>
567
+ </div>
568
+ </ComposerPrimitive.Root>
569
+ </div>
570
+ )
571
+ }
383
572
 
384
573
  export function PikkuAgentChat(props: PikkuAgentChatProps) {
385
- const { emptyMessage, ...runtimeOptions } = props
386
- const runtime = usePikkuAgentRuntime(runtimeOptions)
574
+ const { emptyMessage, hideToolCalls, dark, ...runtimeOptions } = props
575
+ const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } =
576
+ usePikkuAgentRuntime(runtimeOptions)
577
+
578
+ const colors = dark ? darkColors : lightColors
387
579
 
388
580
  return (
581
+ <ColorsContext.Provider value={colors}>
582
+ <PikkuApprovalContext.Provider value={{ pendingApprovals, handleApproval }}>
583
+ <HideToolCallsContext.Provider value={hideToolCalls}>
389
584
  <AssistantRuntimeProvider runtime={runtime}>
390
585
  <div
391
586
  style={{
392
587
  height: '100%',
393
588
  display: 'flex',
394
589
  flexDirection: 'column',
590
+ background: colors.bg,
395
591
  }}
396
592
  >
397
593
  <ThreadPrimitive.Root
@@ -426,7 +622,7 @@ export function PikkuAgentChat(props: PikkuAgentChatProps) {
426
622
  alignItems: 'center',
427
623
  justifyContent: 'center',
428
624
  minHeight: 300,
429
- color: '#888',
625
+ color: colors.textMuted,
430
626
  textAlign: 'center',
431
627
  fontSize: 14,
432
628
  }}
@@ -446,10 +642,13 @@ export function PikkuAgentChat(props: PikkuAgentChatProps) {
446
642
  </div>
447
643
  </ThreadPrimitive.Viewport>
448
644
  <div style={{ maxWidth: 768, margin: '0 auto', width: '100%', padding: '0 16px' }}>
449
- <PikkuComposer />
645
+ <PikkuComposer disabled={isAwaitingApproval} />
450
646
  </div>
451
647
  </ThreadPrimitive.Root>
452
648
  </div>
453
649
  </AssistantRuntimeProvider>
650
+ </HideToolCallsContext.Provider>
651
+ </PikkuApprovalContext.Provider>
652
+ </ColorsContext.Provider>
454
653
  )
455
654
  }