@budibase/frontend-core 3.26.1 → 3.26.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@budibase/frontend-core",
3
- "version": "3.26.1",
3
+ "version": "3.26.2",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
@@ -19,5 +19,5 @@
19
19
  "shortid": "2.2.15",
20
20
  "socket.io-client": "^4.7.5"
21
21
  },
22
- "gitHead": "3ee1bf769467a216a2e44443e01810cb1b076dfc"
22
+ "gitHead": "908ec20f1124a11b0cbe3bed8d2bc4a04e856cfe"
23
23
  }
@@ -4,6 +4,7 @@
4
4
  notifications,
5
5
  Icon,
6
6
  ProgressCircle,
7
+ Body,
7
8
  } from "@budibase/bbui"
8
9
  import type {
9
10
  ChatConversation,
@@ -11,7 +12,6 @@
11
12
  AgentMessageMetadata,
12
13
  } from "@budibase/types"
13
14
  import { Header } from "@budibase/shared-core"
14
- import BBAI from "../../icons/BBAI.svelte"
15
15
  import { tick } from "svelte"
16
16
  import { createAPIClient } from "@budibase/frontend-core"
17
17
  import { Chat } from "@ai-sdk/svelte"
@@ -34,6 +34,7 @@
34
34
  onchatsaved?: (_event: {
35
35
  detail: { chatId?: string; chat: ChatConversationLike }
36
36
  }) => void
37
+ isAgentPreviewChat?: boolean
37
38
  }
38
39
 
39
40
  let {
@@ -42,6 +43,7 @@
42
43
  persistConversation = true,
43
44
  conversationStarters = [],
44
45
  onchatsaved,
46
+ isAgentPreviewChat = false,
45
47
  }: Props = $props()
46
48
 
47
49
  let API = $state(
@@ -58,6 +60,46 @@
58
60
  let textareaElement = $state<HTMLTextAreaElement>()
59
61
  let expandedTools = $state<Record<string, boolean>>({})
60
62
  let inputValue = $state("")
63
+ let reasoningTimers = $state<Record<string, number>>({})
64
+
65
+ $effect(() => {
66
+ const interval = setInterval(() => {
67
+ let updated = false
68
+ const newTimers = { ...reasoningTimers }
69
+
70
+ for (const message of messages) {
71
+ if (message.role !== "assistant") continue
72
+ const createdAt = message.metadata?.createdAt
73
+ const completedAt = message.metadata?.completedAt
74
+
75
+ for (const [index, part] of (message.parts ?? []).entries()) {
76
+ if (!isReasoningUIPart(part)) continue
77
+
78
+ const id = `${message.id}-reasoning-${index}`
79
+
80
+ if (completedAt && createdAt) {
81
+ const finalElapsed = (completedAt - createdAt) / 1000
82
+ if (newTimers[id] !== finalElapsed) {
83
+ newTimers[id] = finalElapsed
84
+ updated = true
85
+ }
86
+ } else if (part.state === "streaming" && createdAt) {
87
+ const newElapsed = (Date.now() - createdAt) / 1000
88
+ if (newTimers[id] !== newElapsed) {
89
+ newTimers[id] = newElapsed
90
+ updated = true
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ if (updated) {
97
+ reasoningTimers = newTimers
98
+ }
99
+ }, 100)
100
+
101
+ return () => clearInterval(interval)
102
+ })
61
103
 
62
104
  let resolvedChatAppId = $state<string | undefined>()
63
105
  let resolvedConversationId = $state<string | undefined>()
@@ -129,7 +171,10 @@
129
171
  )
130
172
  let hasMessages = $derived(Boolean(chat?.messages?.length))
131
173
  let showConversationStarters = $derived(
132
- !isBusy && !hasMessages && conversationStarters.length > 0
174
+ !isBusy &&
175
+ !hasMessages &&
176
+ conversationStarters.length > 0 &&
177
+ !isAgentPreviewChat
133
178
  )
134
179
 
135
180
  let lastChatId = $state<string | undefined>(chat?._id)
@@ -315,6 +360,20 @@
315
360
  {/each}
316
361
  </div>
317
362
  </div>
363
+ {:else}
364
+ <div class="empty-state">
365
+ <div class="empty-state-icon">
366
+ <Icon
367
+ name="chat-circle"
368
+ size="L"
369
+ weight="fill"
370
+ color="var(--spectrum-global-color-gray-500)"
371
+ />
372
+ </div>
373
+ <Body size="S" color="var(--spectrum-global-color-gray-700)">
374
+ Your conversation will appear here.
375
+ </Body>
376
+ </div>
318
377
  {/if}
319
378
  {#each messages as message (message.id)}
320
379
  {#if message.role === "user"}
@@ -323,13 +382,46 @@
323
382
  </div>
324
383
  {:else if message.role === "assistant"}
325
384
  <div class="message assistant">
326
- {#each message.parts || [] as part, partIndex (partIndex)}
385
+ {#each message.parts ?? [] as part, partIndex}
327
386
  {#if isTextUIPart(part)}
328
387
  <MarkdownViewer value={part.text} />
329
388
  {:else if isReasoningUIPart(part)}
389
+ {@const reasoningId = `${message.id}-reasoning-${partIndex}`}
330
390
  <div class="reasoning-part">
331
- <div class="reasoning-label">Reasoning</div>
332
- <div class="reasoning-content">{part.text}</div>
391
+ <button
392
+ class="reasoning-toggle"
393
+ type="button"
394
+ onclick={() =>
395
+ (expandedTools = {
396
+ ...expandedTools,
397
+ [reasoningId]: !expandedTools[reasoningId],
398
+ })}
399
+ >
400
+ <span
401
+ class="reasoning-icon"
402
+ class:shimmer={part.state === "streaming"}
403
+ >
404
+ <Icon
405
+ name="brain"
406
+ size="M"
407
+ color="var(--spectrum-global-color-gray-600)"
408
+ />
409
+ </span>
410
+ <span
411
+ class="reasoning-label"
412
+ class:shimmer={part.state === "streaming"}
413
+ >
414
+ {part.state === "streaming" ? "Thinking" : "Thought for"}
415
+ {#if reasoningTimers[reasoningId]}
416
+ <span class="reasoning-timer"
417
+ >{reasoningTimers[reasoningId].toFixed(1)}s</span
418
+ >
419
+ {/if}
420
+ </span>
421
+ </button>
422
+ {#if expandedTools[reasoningId]}
423
+ <div class="reasoning-content">{part.text}</div>
424
+ {/if}
333
425
  </div>
334
426
  {:else if isToolUIPart(part)}
335
427
  {@const toolId = `${message.id}-${getToolName(part)}-${partIndex}`}
@@ -341,6 +433,7 @@
341
433
  <div class="tool-part" class:tool-running={isRunning}>
342
434
  <button
343
435
  class="tool-header"
436
+ class:tool-header-expanded={expandedTools[toolId]}
344
437
  type="button"
345
438
  onclick={() => toggleTool(toolId)}
346
439
  >
@@ -348,29 +441,40 @@
348
441
  class="tool-chevron"
349
442
  class:expanded={expandedTools[toolId]}
350
443
  >
351
- <Icon name="caret-right" size="XS" />
352
- </span>
353
- <span class="tool-icon">
354
- <Icon name="wrench" size="S" />
355
- </span>
356
- <span class="tool-name">{getToolName(part)}</span>
357
- <span class="tool-status">
358
- {#if isRunning}
359
- <ProgressCircle size="S" />
360
- {:else if isSuccess}
444
+ <span class="tool-chevron-icon tool-chevron-icon-default">
361
445
  <Icon
362
- name="check"
363
- size="S"
364
- color="var(--spectrum-global-color-green-600)"
446
+ name="globe-simple"
447
+ size="M"
448
+ weight="regular"
449
+ color="var(--spectrum-global-color-gray-600)"
365
450
  />
366
- {:else if isError}
451
+ </span>
452
+ <span class="tool-chevron-icon tool-chevron-icon-expanded">
367
453
  <Icon
368
- name="x"
369
- size="S"
370
- color="var(--spectrum-global-color-red-600)"
454
+ name="minus"
455
+ size="M"
456
+ weight="regular"
457
+ color="var(--spectrum-global-color-gray-600)"
371
458
  />
372
- {/if}
459
+ </span>
373
460
  </span>
461
+ <span class="tool-call-label">Tool call</span>
462
+ <div class="tool-name-wrapper">
463
+ <span class="tool-name">{getToolName(part)}</span>
464
+ </div>
465
+ {#if isRunning || isError}
466
+ <span class="tool-status">
467
+ {#if isRunning}
468
+ <ProgressCircle size="S" />
469
+ {:else if isError}
470
+ <Icon
471
+ name="x"
472
+ size="S"
473
+ color="var(--spectrum-global-color-red-600)"
474
+ />
475
+ {/if}
476
+ </span>
477
+ {/if}
374
478
  </button>
375
479
  {#if expandedTools[toolId]}
376
480
  <div class="tool-details">
@@ -425,11 +529,6 @@
425
529
  </div>
426
530
  {/if}
427
531
  {/each}
428
- {#if isBusy}
429
- <div class="message system">
430
- <BBAI size="48px" animate />
431
- </div>
432
- {/if}
433
532
  </div>
434
533
 
435
534
  <div class="input-wrapper">
@@ -446,11 +545,11 @@
446
545
 
447
546
  <style>
448
547
  .chat-area {
449
- flex: 1 1 auto;
548
+ flex: 1 1 0;
450
549
  display: flex;
451
550
  flex-direction: column;
452
551
  overflow-y: auto;
453
- height: 0;
552
+ min-height: 0;
454
553
  }
455
554
  .chatbox {
456
555
  display: flex;
@@ -461,6 +560,20 @@
461
560
  padding: 48px 0 24px 0;
462
561
  }
463
562
 
563
+ .empty-state {
564
+ display: flex;
565
+ flex-direction: column;
566
+ align-items: center;
567
+ justify-content: center;
568
+ gap: 8px;
569
+ flex: 1 1 auto;
570
+ min-height: 0;
571
+ width: 100%;
572
+ }
573
+
574
+ .empty-state-icon {
575
+ --size: 24px;
576
+ }
464
577
  .starter-section {
465
578
  display: flex;
466
579
  flex-direction: column;
@@ -499,6 +612,7 @@
499
612
  .message {
500
613
  display: flex;
501
614
  flex-direction: column;
615
+ gap: 16px;
502
616
  max-width: 80%;
503
617
  padding: var(--spacing-l);
504
618
  border-radius: 20px;
@@ -507,14 +621,22 @@
507
621
  }
508
622
 
509
623
  .message.user {
624
+ border-radius: 8px;
510
625
  align-self: flex-end;
511
- background-color: var(--grey-3);
626
+ background-color: #215f9e33;
627
+ font-size: 14px;
628
+ color: var(--spectrum-global-color-gray-800);
512
629
  }
513
630
 
514
631
  .message.assistant {
515
632
  align-self: flex-start;
516
- background-color: var(--grey-1);
517
- border: 1px solid var(--grey-3);
633
+ background-color: transparent;
634
+ border: none;
635
+ padding: 0;
636
+ font-size: 14px;
637
+ color: var(--spectrum-global-color-gray-800);
638
+ line-height: 1.4;
639
+ max-width: 100%;
518
640
  }
519
641
 
520
642
  .message.system {
@@ -529,6 +651,8 @@
529
651
  width: 100%;
530
652
  display: flex;
531
653
  flex-direction: column;
654
+ flex-shrink: 0;
655
+ line-height: 1.4;
532
656
  }
533
657
 
534
658
  .input {
@@ -538,21 +662,39 @@
538
662
  resize: none;
539
663
  padding: 20px;
540
664
  font-size: 16px;
541
- background-color: var(--grey-3);
665
+ background-color: var(--spectrum-global-color-gray-200);
542
666
  color: var(--grey-9);
543
- border-radius: 16px;
544
- border: none;
667
+ border-radius: 10px;
668
+ border: 1px solid var(--spectrum-global-color-gray-300) !important;
545
669
  outline: none;
546
670
  min-height: 100px;
547
671
  }
548
672
 
673
+ .input:focus {
674
+ border: 1px solid #215f9e33 !important;
675
+ }
676
+
549
677
  .input::placeholder {
550
678
  color: var(--spectrum-global-color-gray-600);
551
679
  }
552
680
 
553
681
  /* Style the markdown tool sections in assistant messages */
554
682
  :global(.assistant strong) {
555
- color: var(--spectrum-global-color-static-seafoam-700);
683
+ color: var(--spectrum-global-color-gray-900);
684
+ font-weight: 500;
685
+ }
686
+
687
+ :global(.assistant .markdown-viewer p) {
688
+ margin-top: 8px;
689
+ margin-bottom: 8px;
690
+ }
691
+
692
+ :global(.assistant .markdown-viewer p:first-child) {
693
+ margin-top: 0;
694
+ }
695
+
696
+ :global(.assistant .markdown-viewer p:last-child) {
697
+ margin-bottom: 0;
556
698
  }
557
699
 
558
700
  :global(.assistant h3) {
@@ -566,37 +708,37 @@
566
708
  border-radius: 4px;
567
709
  }
568
710
 
711
+ :global(.assistant ul) {
712
+ padding-inline-start: 20px;
713
+ }
714
+
569
715
  /* Tool parts styling */
570
- .tool-part {
571
- margin: var(--spacing-m) 0;
572
- padding: var(--spacing-m);
573
- background-color: var(--grey-2);
574
- border: 1px solid var(--grey-3);
575
- border-radius: 8px;
576
- transition: border-color 0.2s ease;
716
+ .tool-part + .tool-part {
717
+ margin-top: 2px;
577
718
  }
578
719
 
579
- .tool-part.tool-running {
580
- border-color: var(--spectrum-global-color-static-seafoam-600);
720
+ .tool-part {
721
+ position: relative;
722
+ margin-top: var(--spacing-l);
723
+ margin-bottom: 0;
581
724
  }
582
725
 
583
726
  .tool-header {
584
727
  display: flex;
585
728
  align-items: center;
586
- gap: var(--spacing-s);
587
- width: 100%;
729
+ gap: 8px;
588
730
  padding: 0;
589
731
  margin: 0;
590
732
  background: none;
591
733
  border: none;
734
+ border-radius: 4px;
592
735
  cursor: pointer;
593
- font-weight: 600;
594
736
  font-size: 14px;
595
737
  text-align: left;
596
738
  }
597
739
 
598
740
  .tool-header:hover {
599
- opacity: 0.8;
741
+ background-color: var(--spectrum-global-color-gray-100);
600
742
  }
601
743
 
602
744
  .tool-chevron {
@@ -607,20 +749,59 @@
607
749
  color: var(--spectrum-global-color-gray-600);
608
750
  }
609
751
 
752
+ .tool-chevron :global(i) {
753
+ --size: 16px !important;
754
+ }
755
+
756
+ .tool-chevron-icon-expanded :global(i) {
757
+ --size: 16px !important;
758
+ }
759
+
760
+ .tool-chevron-icon {
761
+ display: flex;
762
+ align-items: center;
763
+ justify-content: center;
764
+ }
765
+
766
+ .tool-chevron-icon-expanded {
767
+ display: none;
768
+ }
769
+
770
+ .tool-header-expanded .tool-chevron-icon-default {
771
+ display: none !important;
772
+ }
773
+
774
+ .tool-header-expanded .tool-chevron-icon-expanded {
775
+ display: flex !important;
776
+ }
777
+
610
778
  .tool-chevron.expanded {
611
779
  transform: rotate(90deg);
612
780
  }
613
781
 
614
- .tool-icon {
782
+ .tool-header-expanded .tool-chevron {
783
+ transform: none;
784
+ }
785
+
786
+ .tool-call-label {
787
+ font-size: 14px;
788
+ color: var(--spectrum-global-color-gray-900);
789
+ }
790
+
791
+ .tool-name-wrapper {
615
792
  display: flex;
616
793
  align-items: center;
617
- justify-content: center;
618
- color: var(--spectrum-global-color-static-seafoam-700);
794
+ gap: var(--spacing-s);
795
+ padding: 3px 6px;
796
+ background-color: var(--spectrum-global-color-gray-200);
797
+ border-radius: 4px;
619
798
  }
620
799
 
621
800
  .tool-name {
622
- color: var(--spectrum-global-color-gray-900);
623
801
  font-family: var(--font-mono), monospace;
802
+ font-size: 13px;
803
+ color: var(--spectrum-global-color-gray-800);
804
+ font-weight: 400;
624
805
  }
625
806
 
626
807
  .tool-status {
@@ -631,10 +812,24 @@
631
812
  }
632
813
 
633
814
  .tool-details {
815
+ position: absolute;
816
+ top: 100%;
817
+ left: 0;
634
818
  margin-top: var(--spacing-m);
819
+ width: 100%;
820
+ max-width: 100%;
821
+ box-sizing: border-box;
635
822
  display: flex;
636
823
  flex-direction: column;
637
824
  gap: var(--spacing-s);
825
+ background: var(--background);
826
+ border: 1px solid var(--spectrum-global-color-gray-200);
827
+ border-radius: 6px;
828
+ padding: var(--spacing-m);
829
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
830
+ z-index: 1;
831
+ overflow-x: hidden;
832
+ min-width: 0;
638
833
  }
639
834
 
640
835
  .tool-section {
@@ -658,12 +853,14 @@
658
853
  padding: var(--spacing-s);
659
854
  font-size: 12px;
660
855
  font-family: var(--font-mono), monospace;
661
- overflow-x: auto;
662
856
  white-space: pre-wrap;
663
857
  word-break: break-word;
858
+ overflow-wrap: break-word;
664
859
  margin: 0;
665
860
  max-height: 200px;
666
861
  overflow-y: auto;
862
+ overflow-x: hidden;
863
+ min-width: 0;
667
864
  }
668
865
 
669
866
  .tool-error .tool-section-label {
@@ -677,26 +874,61 @@
677
874
 
678
875
  /* Reasoning parts styling */
679
876
  .reasoning-part {
680
- margin: var(--spacing-m) 0;
681
- padding: var(--spacing-m);
682
- background-color: var(--grey-1);
683
- border-left: 3px solid var(--spectrum-global-color-static-seafoam-700);
877
+ display: flex;
878
+ flex-direction: column;
879
+ gap: 8px;
880
+ }
881
+
882
+ .reasoning-toggle {
883
+ display: flex;
884
+ align-items: center;
885
+ gap: 6px;
886
+ padding: 0;
887
+ margin: 0;
888
+ background: none;
889
+ border: none;
890
+ cursor: pointer;
684
891
  border-radius: 4px;
685
892
  }
686
893
 
894
+ .reasoning-icon {
895
+ display: flex;
896
+ align-items: center;
897
+ justify-content: center;
898
+ flex-shrink: 0;
899
+ }
900
+
687
901
  .reasoning-label {
902
+ font-size: 13px;
903
+ color: var(--spectrum-global-color-gray-600);
904
+ }
905
+
906
+ .reasoning-timer {
688
907
  font-size: 12px;
689
- font-weight: 600;
690
- color: var(--spectrum-global-color-static-seafoam-700);
691
- margin-bottom: 4px;
692
- text-transform: uppercase;
693
- letter-spacing: 0.5px;
908
+ color: var(--spectrum-global-color-gray-600);
909
+ font-weight: 400;
910
+ }
911
+
912
+ .reasoning-label.shimmer,
913
+ .reasoning-icon.shimmer {
914
+ animation: shimmer 2s ease-in-out infinite;
694
915
  }
695
916
 
696
917
  .reasoning-content {
697
918
  font-size: 13px;
698
- color: var(--spectrum-global-color-gray-800);
919
+ color: var(--spectrum-global-color-gray-600);
699
920
  font-style: italic;
921
+ line-height: 1.4;
922
+ }
923
+
924
+ @keyframes shimmer {
925
+ 0%,
926
+ 100% {
927
+ opacity: 0.6;
928
+ }
929
+ 50% {
930
+ opacity: 1;
931
+ }
700
932
  }
701
933
 
702
934
  .sources {