@pikku/assistant-ui 0.12.3 → 0.12.5

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,10 +1,22 @@
1
- import { createContext, useContext, useState, useMemo, type FunctionComponent } from 'react'
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ useMemo,
6
+ useEffect,
7
+ useRef,
8
+ type ComponentType,
9
+ type FunctionComponent,
10
+ type ReactNode,
11
+ } from 'react'
2
12
  import Markdown from 'react-markdown'
3
13
  import {
4
14
  AssistantRuntimeProvider,
5
15
  ThreadPrimitive,
6
16
  MessagePrimitive,
7
17
  ComposerPrimitive,
18
+ useComposerRuntime,
19
+ type ToolCallMessagePartComponent,
8
20
  } from '@assistant-ui/react'
9
21
  import {
10
22
  usePikkuAgentRuntime,
@@ -16,6 +28,7 @@ import {
16
28
  } from './use-pikku-agent-runtime.js'
17
29
 
18
30
  export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
31
+ initialPrompt?: string
19
32
  emptyMessage?: string
20
33
  /** Hide tool calls from the chat display.
21
34
  * - `true`: hide all non-approval tool calls
@@ -25,6 +38,19 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
25
38
  dark?: boolean
26
39
  /** Max width of the chat content area. Defaults to 768. Set to 'none' for full width. */
27
40
  maxWidth?: number | 'none'
41
+ /**
42
+ * Per-tool renderers. Map `toolName` → React component to replace the
43
+ * default expandable tool-call box for that tool. Enables generative-UI
44
+ * patterns: e.g. register a `renderWidget` tool on the agent and mount
45
+ * real UI (charts, diffs, cards) inline in the assistant bubble from the
46
+ * persisted tool-call args.
47
+ *
48
+ * Any tool without an entry here falls through to the default renderer
49
+ * (which still respects `hideToolCalls` and the approval-request UI).
50
+ */
51
+ toolComponents?: Record<string, ToolCallMessagePartComponent>
52
+ renderAssistantText?: (text: string) => ReactNode
53
+ generativeUIComponents?: Record<string, ComponentType<any>>
28
54
  }
29
55
 
30
56
  interface ChatColors {
@@ -91,7 +117,18 @@ const darkColors: ChatColors = {
91
117
  }
92
118
 
93
119
  const ColorsContext = createContext<ChatColors>(lightColors)
94
- const HideToolCallsContext = createContext<boolean | string[] | undefined>(undefined)
120
+ const HideToolCallsContext = createContext<boolean | string[] | undefined>(
121
+ undefined
122
+ )
123
+ const ToolComponentsContext = createContext<
124
+ Record<string, ToolCallMessagePartComponent> | undefined
125
+ >(undefined)
126
+ const GenerativeUIComponentsContext = createContext<
127
+ Record<string, ComponentType<any>> | undefined
128
+ >(undefined)
129
+ const RenderAssistantTextContext = createContext<
130
+ ((text: string) => ReactNode) | undefined
131
+ >(undefined)
95
132
 
96
133
  function shouldHideToolCall(
97
134
  hideToolCalls: boolean | string[] | undefined,
@@ -118,9 +155,7 @@ const ToolCallDisplay: FunctionComponent<{
118
155
  const approvalReason = (args as any)?.__approvalReason
119
156
  const displayArgs = { ...args }
120
157
  delete (displayArgs as any).__approvalReason
121
- const [responded, setResponded] = useState<'approved' | 'denied' | null>(
122
- null
123
- )
158
+ const [responded, setResponded] = useState<'approved' | 'denied' | null>(null)
124
159
 
125
160
  // Hide responded approval tool calls
126
161
  if (isApproval && responded && shouldHideToolCall(hideToolCalls, toolName)) {
@@ -157,7 +192,9 @@ const ToolCallDisplay: FunctionComponent<{
157
192
  Approval required
158
193
  </div>
159
194
  {approvalReason && (
160
- <div style={{ fontSize: 13, marginBottom: 4, color: colors.text }}>{approvalReason}</div>
195
+ <div style={{ fontSize: 13, marginBottom: 4, color: colors.text }}>
196
+ {approvalReason}
197
+ </div>
161
198
  )}
162
199
  <div style={{ fontSize: 12, color: colors.textMuted, marginBottom: 4 }}>
163
200
  The agent wants to call <code>{toolName}</code>
@@ -238,8 +275,12 @@ const ToolCallDisplay: FunctionComponent<{
238
275
  fontSize: 11,
239
276
  padding: '2px 6px',
240
277
  borderRadius: 3,
241
- background: responded === 'approved' ? colors.successBg : colors.errorBg,
242
- color: responded === 'approved' ? colors.successColor : colors.errorColor,
278
+ background:
279
+ responded === 'approved' ? colors.successBg : colors.errorBg,
280
+ color:
281
+ responded === 'approved'
282
+ ? colors.successColor
283
+ : colors.errorColor,
243
284
  }}
244
285
  >
245
286
  {responded}
@@ -277,7 +318,9 @@ const ToolCallDisplay: FunctionComponent<{
277
318
  {toolName}
278
319
  </span>
279
320
  {status.type === 'running' && (
280
- <span style={{ fontSize: 11, color: colors.textMuted }}>running...</span>
321
+ <span style={{ fontSize: 11, color: colors.textMuted }}>
322
+ running...
323
+ </span>
281
324
  )}
282
325
  {status.type === 'error' && (
283
326
  <span
@@ -334,7 +377,9 @@ const ToolCallDisplay: FunctionComponent<{
334
377
  </button>
335
378
  {expanded && (
336
379
  <div style={{ marginTop: 8 }}>
337
- <div style={{ fontSize: 12, color: colors.textMuted, marginBottom: 2 }}>
380
+ <div
381
+ style={{ fontSize: 12, color: colors.textMuted, marginBottom: 2 }}
382
+ >
338
383
  Arguments:
339
384
  </div>
340
385
  <pre
@@ -352,7 +397,12 @@ const ToolCallDisplay: FunctionComponent<{
352
397
  {result !== undefined && (
353
398
  <>
354
399
  <div
355
- style={{ fontSize: 12, color: colors.textMuted, marginTop: 8, marginBottom: 2 }}
400
+ style={{
401
+ fontSize: 12,
402
+ color: colors.textMuted,
403
+ marginTop: 8,
404
+ marginBottom: 2,
405
+ }}
356
406
  >
357
407
  Result:
358
408
  </div>
@@ -378,49 +428,141 @@ const ToolCallDisplay: FunctionComponent<{
378
428
  )
379
429
  }
380
430
 
381
- const MarkdownText: FunctionComponent<{ text: string; colors: ChatColors }> = ({ text, colors }) => {
382
- const components = useMemo(() => ({
383
- p: ({ children }: any) => (
384
- <p style={{ margin: '0 0 8px', fontSize: 14, lineHeight: 1.6, color: colors.text }}>{children}</p>
385
- ),
386
- strong: ({ children }: any) => (
387
- <strong style={{ fontWeight: 600, color: colors.text }}>{children}</strong>
388
- ),
389
- em: ({ children }: any) => (
390
- <em style={{ color: colors.text }}>{children}</em>
391
- ),
392
- ul: ({ children }: any) => (
393
- <ul style={{ margin: '4px 0 8px', paddingLeft: 20, fontSize: 14, color: colors.text }}>{children}</ul>
394
- ),
395
- ol: ({ children }: any) => (
396
- <ol style={{ margin: '4px 0 8px', paddingLeft: 20, fontSize: 14, color: colors.text }}>{children}</ol>
397
- ),
398
- li: ({ children }: any) => (
399
- <li style={{ marginBottom: 2, lineHeight: 1.6 }}>{children}</li>
400
- ),
401
- code: ({ children, className }: any) => {
402
- const isBlock = className?.startsWith('language-')
403
- if (isBlock) {
431
+ const MarkdownText: FunctionComponent<{ text: string; colors: ChatColors }> = ({
432
+ text,
433
+ colors,
434
+ }) => {
435
+ const components = useMemo(
436
+ () => ({
437
+ p: ({ children }: any) => (
438
+ <p
439
+ style={{
440
+ margin: '0 0 8px',
441
+ fontSize: 14,
442
+ lineHeight: 1.6,
443
+ color: colors.text,
444
+ }}
445
+ >
446
+ {children}
447
+ </p>
448
+ ),
449
+ strong: ({ children }: any) => (
450
+ <strong style={{ fontWeight: 600, color: colors.text }}>
451
+ {children}
452
+ </strong>
453
+ ),
454
+ em: ({ children }: any) => (
455
+ <em style={{ color: colors.text }}>{children}</em>
456
+ ),
457
+ ul: ({ children }: any) => (
458
+ <ul
459
+ style={{
460
+ margin: '4px 0 8px',
461
+ paddingLeft: 20,
462
+ fontSize: 14,
463
+ color: colors.text,
464
+ }}
465
+ >
466
+ {children}
467
+ </ul>
468
+ ),
469
+ ol: ({ children }: any) => (
470
+ <ol
471
+ style={{
472
+ margin: '4px 0 8px',
473
+ paddingLeft: 20,
474
+ fontSize: 14,
475
+ color: colors.text,
476
+ }}
477
+ >
478
+ {children}
479
+ </ol>
480
+ ),
481
+ li: ({ children }: any) => (
482
+ <li style={{ marginBottom: 2, lineHeight: 1.6 }}>{children}</li>
483
+ ),
484
+ code: ({ children, className }: any) => {
485
+ const isBlock = className?.startsWith('language-')
486
+ if (isBlock) {
487
+ return (
488
+ <pre
489
+ style={{
490
+ background: colors.codeBg,
491
+ padding: 10,
492
+ borderRadius: 4,
493
+ overflow: 'auto',
494
+ margin: '4px 0 8px',
495
+ fontSize: 12,
496
+ }}
497
+ >
498
+ <code style={{ color: colors.text }}>{children}</code>
499
+ </pre>
500
+ )
501
+ }
404
502
  return (
405
- <pre style={{ background: colors.codeBg, padding: 10, borderRadius: 4, overflow: 'auto', margin: '4px 0 8px', fontSize: 12 }}>
406
- <code style={{ color: colors.text }}>{children}</code>
407
- </pre>
503
+ <code
504
+ style={{
505
+ background: colors.codeBg,
506
+ padding: '1px 4px',
507
+ borderRadius: 3,
508
+ fontSize: 13,
509
+ color: colors.text,
510
+ }}
511
+ >
512
+ {children}
513
+ </code>
408
514
  )
409
- }
410
- return (
411
- <code style={{ background: colors.codeBg, padding: '1px 4px', borderRadius: 3, fontSize: 13, color: colors.text }}>
515
+ },
516
+ pre: ({ children }: any) => <>{children}</>,
517
+ h1: ({ children }: any) => (
518
+ <h3
519
+ style={{
520
+ margin: '8px 0 4px',
521
+ fontSize: 16,
522
+ fontWeight: 600,
523
+ color: colors.text,
524
+ }}
525
+ >
526
+ {children}
527
+ </h3>
528
+ ),
529
+ h2: ({ children }: any) => (
530
+ <h4
531
+ style={{
532
+ margin: '8px 0 4px',
533
+ fontSize: 15,
534
+ fontWeight: 600,
535
+ color: colors.text,
536
+ }}
537
+ >
538
+ {children}
539
+ </h4>
540
+ ),
541
+ h3: ({ children }: any) => (
542
+ <h5
543
+ style={{
544
+ margin: '8px 0 4px',
545
+ fontSize: 14,
546
+ fontWeight: 600,
547
+ color: colors.text,
548
+ }}
549
+ >
412
550
  {children}
413
- </code>
414
- )
415
- },
416
- pre: ({ children }: any) => <>{children}</>,
417
- h1: ({ children }: any) => <h3 style={{ margin: '8px 0 4px', fontSize: 16, fontWeight: 600, color: colors.text }}>{children}</h3>,
418
- h2: ({ children }: any) => <h4 style={{ margin: '8px 0 4px', fontSize: 15, fontWeight: 600, color: colors.text }}>{children}</h4>,
419
- h3: ({ children }: any) => <h5 style={{ margin: '8px 0 4px', fontSize: 14, fontWeight: 600, color: colors.text }}>{children}</h5>,
420
- a: ({ href, children }: any) => (
421
- <a href={href} target="_blank" rel="noopener noreferrer" style={{ color: colors.textMuted, textDecoration: 'underline' }}>{children}</a>
422
- ),
423
- }), [colors])
551
+ </h5>
552
+ ),
553
+ a: ({ href, children }: any) => (
554
+ <a
555
+ href={href}
556
+ target="_blank"
557
+ rel="noopener noreferrer"
558
+ style={{ color: colors.textMuted, textDecoration: 'underline' }}
559
+ >
560
+ {children}
561
+ </a>
562
+ ),
563
+ }),
564
+ [colors]
565
+ )
424
566
 
425
567
  return <Markdown components={components}>{text}</Markdown>
426
568
  }
@@ -456,7 +598,13 @@ const UserMessage: FunctionComponent = () => {
456
598
  <MessagePrimitive.Content
457
599
  components={{
458
600
  Text: ({ text }) => (
459
- <span style={{ fontSize: 14, whiteSpace: 'pre-wrap', color: colors.text }}>
601
+ <span
602
+ style={{
603
+ fontSize: 14,
604
+ whiteSpace: 'pre-wrap',
605
+ color: colors.text,
606
+ }}
607
+ >
460
608
  {text}
461
609
  </span>
462
610
  ),
@@ -470,6 +618,9 @@ const UserMessage: FunctionComponent = () => {
470
618
 
471
619
  const AssistantMessage: FunctionComponent = () => {
472
620
  const colors = useContext(ColorsContext)
621
+ const toolComponents = useContext(ToolComponentsContext)
622
+ const generativeUIComponents = useContext(GenerativeUIComponentsContext)
623
+ const renderAssistantText = useContext(RenderAssistantTextContext)
473
624
  return (
474
625
  <div
475
626
  style={{
@@ -491,10 +642,14 @@ const AssistantMessage: FunctionComponent = () => {
491
642
  >
492
643
  <MessagePrimitive.Content
493
644
  components={{
494
- Text: ({ text }) => (
495
- <MarkdownText text={text} colors={colors} />
496
- ),
645
+ Text: ({ text }) =>
646
+ renderAssistantText ? (
647
+ <>{renderAssistantText(text)}</>
648
+ ) : (
649
+ <MarkdownText text={text} colors={colors} />
650
+ ),
497
651
  tools: {
652
+ by_name: toolComponents,
498
653
  Fallback: (props) => (
499
654
  <ToolCallDisplay
500
655
  toolCallId={props.toolCallId}
@@ -506,6 +661,13 @@ const AssistantMessage: FunctionComponent = () => {
506
661
  />
507
662
  ),
508
663
  },
664
+ ...(generativeUIComponents
665
+ ? {
666
+ generativeUI: {
667
+ components: generativeUIComponents,
668
+ },
669
+ }
670
+ : {}),
509
671
  }}
510
672
  />
511
673
  <MessagePrimitive.If last>
@@ -530,61 +692,95 @@ const AssistantMessage: FunctionComponent = () => {
530
692
  )
531
693
  }
532
694
 
695
+ const ComposerPrefill: FunctionComponent<{ text?: string }> = ({ text }) => {
696
+ const composer = useComposerRuntime()
697
+ const filled = useRef(false)
698
+ useEffect(() => {
699
+ if (filled.current || !text) return
700
+ filled.current = true
701
+ if (composer.getState().text === '') composer.setText(text)
702
+ }, [text, composer])
703
+ return null
704
+ }
705
+
533
706
  const PikkuComposer: FunctionComponent<{ disabled?: boolean }> = ({
534
707
  disabled,
535
708
  }) => {
536
709
  const colors = useContext(ColorsContext)
710
+
537
711
  return (
538
712
  <div style={{ padding: '8px 0 16px' }}>
539
713
  <ComposerPrimitive.Root>
540
714
  <div
541
715
  style={{
542
- border: `1px solid ${colors.border}`,
543
- borderRadius: 12,
544
- overflow: 'hidden',
716
+ position: 'relative',
717
+ zIndex: 2,
545
718
  display: 'flex',
546
- alignItems: 'flex-end',
547
- padding: '6px 12px',
548
- gap: 8,
549
- ...(disabled ? { opacity: 0.5, pointerEvents: 'none' as const } : {}),
719
+ flexDirection: 'column',
720
+ gap: 10,
721
+ backgroundColor: colors.assistantBubble,
722
+ border: `1px solid ${colors.border}`,
723
+ borderRadius: 24,
724
+ padding: '14px 12px 10px',
725
+ boxShadow:
726
+ darkColors.text === colors.text
727
+ ? '0 14px 30px rgba(0,0,0,0.24)'
728
+ : '0 14px 30px rgba(0,0,0,0.08)',
729
+ ...(disabled
730
+ ? { opacity: 0.5, pointerEvents: 'none' as const }
731
+ : {}),
550
732
  }}
551
733
  >
552
734
  <ComposerPrimitive.Input
553
- placeholder={disabled ? 'Respond to approval request above...' : 'Message...'}
554
- rows={2}
555
- disabled={disabled}
735
+ placeholder={
736
+ disabled ? 'Respond to approval request above...' : 'Message...'
737
+ }
738
+ rows={1}
739
+ disabled={disabled ?? false}
556
740
  style={{
557
- flex: 1,
741
+ width: '100%',
742
+ background: 'transparent',
558
743
  border: 'none',
559
744
  outline: 'none',
560
- resize: 'none',
745
+ color: colors.text,
561
746
  fontSize: 14,
562
747
  fontFamily: 'inherit',
563
- padding: '4px 0',
564
- background: colors.inputBg,
565
- color: colors.text,
748
+ resize: 'none',
749
+ lineHeight: 1.5,
750
+ overflowY: 'auto',
566
751
  }}
567
752
  />
568
- <ComposerPrimitive.Send
569
- disabled={disabled}
753
+ <div
570
754
  style={{
571
- width: 28,
572
- height: 28,
573
- borderRadius: '50%',
574
- border: 'none',
575
- background: disabled ? colors.textMuted : colors.sendBg,
576
- color: colors.sendColor,
577
- cursor: disabled ? 'not-allowed' : 'pointer',
755
+ width: '100%',
578
756
  display: 'flex',
579
757
  alignItems: 'center',
580
- justifyContent: 'center',
581
- flexShrink: 0,
582
- marginBottom: 2,
583
- fontSize: 14,
758
+ gap: 6,
759
+ minWidth: 0,
760
+ justifyContent: 'flex-end',
584
761
  }}
585
762
  >
586
- &#9654;
587
- </ComposerPrimitive.Send>
763
+ <ComposerPrimitive.Send
764
+ disabled={disabled ?? false}
765
+ style={{
766
+ width: 30,
767
+ height: 30,
768
+ borderRadius: 999,
769
+ border: `1px solid ${colors.border}`,
770
+ background: '#e8e8e8',
771
+ color: '#111111',
772
+ cursor: disabled ? 'not-allowed' : 'pointer',
773
+ display: 'flex',
774
+ alignItems: 'center',
775
+ justifyContent: 'center',
776
+ flexShrink: 0,
777
+ transition:
778
+ 'background-color 150ms ease, color 150ms ease, border-color 150ms ease',
779
+ }}
780
+ >
781
+
782
+ </ComposerPrimitive.Send>
783
+ </div>
588
784
  </div>
589
785
  </ComposerPrimitive.Root>
590
786
  </div>
@@ -592,7 +788,17 @@ const PikkuComposer: FunctionComponent<{ disabled?: boolean }> = ({
592
788
  }
593
789
 
594
790
  export function PikkuAgentChat(props: PikkuAgentChatProps) {
595
- const { emptyMessage, hideToolCalls, dark, maxWidth = 768, ...runtimeOptions } = props
791
+ const {
792
+ emptyMessage,
793
+ hideToolCalls,
794
+ dark,
795
+ maxWidth = 768,
796
+ toolComponents,
797
+ renderAssistantText,
798
+ generativeUIComponents,
799
+ initialPrompt,
800
+ ...runtimeOptions
801
+ } = props
596
802
  const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } =
597
803
  usePikkuAgentRuntime(runtimeOptions)
598
804
 
@@ -600,76 +806,95 @@ export function PikkuAgentChat(props: PikkuAgentChatProps) {
600
806
 
601
807
  return (
602
808
  <ColorsContext.Provider value={colors}>
603
- <PikkuApprovalContext.Provider value={{ pendingApprovals, handleApproval }}>
604
- <HideToolCallsContext.Provider value={hideToolCalls}>
605
- <AssistantRuntimeProvider runtime={runtime}>
606
- <div
607
- style={{
608
- height: '100%',
609
- display: 'flex',
610
- flexDirection: 'column',
611
- background: colors.bg,
612
- }}
809
+ <PikkuApprovalContext.Provider
810
+ value={{ pendingApprovals, handleApproval }}
613
811
  >
614
- <ThreadPrimitive.Root
615
- style={{
616
- display: 'flex',
617
- flexDirection: 'column',
618
- flex: 1,
619
- minHeight: 0,
620
- }}
621
- >
622
- <ThreadPrimitive.Viewport
623
- style={{
624
- flex: 1,
625
- minHeight: 0,
626
- overflowY: 'auto',
627
- }}
628
- >
629
- <div
630
- style={{
631
- maxWidth: maxWidth === 'none' ? undefined : maxWidth,
632
- margin: '0 auto',
633
- padding: 16,
634
- display: 'flex',
635
- flexDirection: 'column',
636
- gap: 16,
637
- }}
812
+ <HideToolCallsContext.Provider value={hideToolCalls}>
813
+ <ToolComponentsContext.Provider value={toolComponents}>
814
+ <GenerativeUIComponentsContext.Provider
815
+ value={generativeUIComponents}
638
816
  >
639
- <ThreadPrimitive.Empty>
640
- <div
641
- style={{
642
- display: 'flex',
643
- alignItems: 'center',
644
- justifyContent: 'center',
645
- minHeight: 300,
646
- color: colors.textMuted,
647
- textAlign: 'center',
648
- fontSize: 14,
649
- }}
650
- >
651
- {emptyMessage ??
652
- (props.threadId
653
- ? 'Send a message to start the conversation.'
654
- : 'Start a new conversation.')}
655
- </div>
656
- </ThreadPrimitive.Empty>
657
- <ThreadPrimitive.Messages
658
- components={{
659
- UserMessage,
660
- AssistantMessage,
661
- }}
662
- />
663
- </div>
664
- </ThreadPrimitive.Viewport>
665
- <div style={{ maxWidth: maxWidth === 'none' ? undefined : maxWidth, margin: '0 auto', width: '100%', padding: '0 16px' }}>
666
- <PikkuComposer disabled={isAwaitingApproval} />
667
- </div>
668
- </ThreadPrimitive.Root>
669
- </div>
670
- </AssistantRuntimeProvider>
671
- </HideToolCallsContext.Provider>
672
- </PikkuApprovalContext.Provider>
817
+ <RenderAssistantTextContext.Provider value={renderAssistantText}>
818
+ <AssistantRuntimeProvider runtime={runtime}>
819
+ <ComposerPrefill text={initialPrompt} />
820
+ <div
821
+ style={{
822
+ height: '100%',
823
+ display: 'flex',
824
+ flexDirection: 'column',
825
+ background: colors.bg,
826
+ }}
827
+ >
828
+ <ThreadPrimitive.Root
829
+ style={{
830
+ display: 'flex',
831
+ flexDirection: 'column',
832
+ flex: 1,
833
+ minHeight: 0,
834
+ }}
835
+ >
836
+ <ThreadPrimitive.Viewport
837
+ style={{
838
+ flex: 1,
839
+ minHeight: 0,
840
+ overflowY: 'auto',
841
+ }}
842
+ >
843
+ <div
844
+ style={{
845
+ maxWidth:
846
+ maxWidth === 'none' ? undefined : maxWidth,
847
+ margin: '0 auto',
848
+ padding: 16,
849
+ display: 'flex',
850
+ flexDirection: 'column',
851
+ gap: 16,
852
+ }}
853
+ >
854
+ <ThreadPrimitive.Empty>
855
+ <div
856
+ style={{
857
+ display: 'flex',
858
+ alignItems: 'center',
859
+ justifyContent: 'center',
860
+ minHeight: 300,
861
+ color: colors.textMuted,
862
+ textAlign: 'center',
863
+ fontSize: 14,
864
+ }}
865
+ >
866
+ {emptyMessage ??
867
+ (props.threadId
868
+ ? 'Send a message to start the conversation.'
869
+ : 'Start a new conversation.')}
870
+ </div>
871
+ </ThreadPrimitive.Empty>
872
+ <ThreadPrimitive.Messages
873
+ components={{
874
+ UserMessage,
875
+ AssistantMessage,
876
+ }}
877
+ />
878
+ </div>
879
+ </ThreadPrimitive.Viewport>
880
+ <div
881
+ style={{
882
+ maxWidth: maxWidth === 'none' ? undefined : maxWidth,
883
+ margin: '0 auto',
884
+ width: '100%',
885
+ padding: '0 16px',
886
+ }}
887
+ >
888
+ <PikkuComposer disabled={isAwaitingApproval} />
889
+ </div>
890
+ </ThreadPrimitive.Root>
891
+ </div>
892
+ </AssistantRuntimeProvider>
893
+ </RenderAssistantTextContext.Provider>
894
+ </GenerativeUIComponentsContext.Provider>
895
+ </ToolComponentsContext.Provider>
896
+ </HideToolCallsContext.Provider>
897
+ </PikkuApprovalContext.Provider>
673
898
  </ColorsContext.Provider>
674
899
  )
675
900
  }