@budibase/frontend-core 3.46.0 → 3.47.0

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": "v3.46.0",
3
+ "version": "v3.47.0",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
@@ -24,5 +24,5 @@
24
24
  "devDependencies": {
25
25
  "vitest": "^5.0.0"
26
26
  },
27
- "gitHead": "1444a702ce29cf158b10c31d3f205fc84c463169"
27
+ "gitHead": "adfff86585b1d393941175f58cbe74f2005616b4"
28
28
  }
@@ -30,31 +30,64 @@
30
30
 
31
31
  // Resolved either by this card's action (statusMessage) or elsewhere (poll).
32
32
  let isResolved = $derived(!!statusMessage || resolution !== "pending")
33
+ let expanded = $state(true)
34
+ let showDetails = $derived(!showApproval || expanded)
35
+
36
+ const toggleDetails = () => {
37
+ expanded = !expanded
38
+ }
33
39
  </script>
34
40
 
35
41
  <div class="escalation-card" aria-live="polite">
36
- <div class="escalation-card-header">
37
- <Icon name={isResolved ? "check-circle" : "clock"} size="M" />
38
- <span class="escalation-card-title">{title || "Approval required"}</span>
39
- {#if showApproval && !isResolved}
40
- <span class="escalation-card-badge">Test mode</span>
42
+ {#if showApproval}
43
+ <button
44
+ type="button"
45
+ class="escalation-card-header escalation-card-toggle"
46
+ aria-expanded={expanded}
47
+ aria-label={`${expanded ? "Collapse" : "Expand"} approval details${title ? ` for ${title}` : ""}`}
48
+ onclick={toggleDetails}
49
+ >
50
+ <Icon name={isResolved ? "check-circle" : "clock"} size="M" />
51
+ <span class="escalation-card-title">{title || "Approval required"}</span>
52
+ <span class="escalation-card-header-end">
53
+ {#if !isResolved}
54
+ <span class="escalation-card-badge">Test mode</span>
55
+ {/if}
56
+ <span class="escalation-card-chevron" class:expanded>
57
+ <Icon name="caret-down" size="S" />
58
+ </span>
59
+ </span>
60
+ </button>
61
+ {:else}
62
+ <div class="escalation-card-header">
63
+ <Icon name={isResolved ? "check-circle" : "clock"} size="M" />
64
+ <span class="escalation-card-title">{title || "Approval required"}</span>
65
+ </div>
66
+ {/if}
67
+
68
+ {#if showDetails}
69
+ {#if summary}
70
+ <Body size="S" color="var(--spectrum-global-color-gray-700)">
71
+ {summary}
72
+ </Body>
41
73
  {/if}
42
- </div>
43
74
 
44
- {#if summary}
45
- <Body size="S" color="var(--spectrum-global-color-gray-700)">
46
- {summary}
47
- </Body>
75
+ {#if isResolved}
76
+ <Body size="XS" color="var(--spectrum-global-color-gray-600)">
77
+ {statusMessage || "Response recorded."}
78
+ </Body>
79
+ {:else if showApproval}
80
+ <Body size="XS" color="var(--spectrum-global-color-gray-600)">
81
+ Approve or reject here to simulate a reviewer response.
82
+ </Body>
83
+ {:else}
84
+ <Body size="XS" color="var(--spectrum-global-color-gray-600)">
85
+ Awaiting a human response.
86
+ </Body>
87
+ {/if}
48
88
  {/if}
49
89
 
50
- {#if isResolved}
51
- <Body size="XS" color="var(--spectrum-global-color-gray-600)">
52
- {statusMessage || "Response recorded."}
53
- </Body>
54
- {:else if showApproval}
55
- <Body size="XS" color="var(--spectrum-global-color-gray-600)">
56
- Approve or reject here to simulate a reviewer response.
57
- </Body>
90
+ {#if showApproval && !isResolved}
58
91
  <div class="escalation-card-actions">
59
92
  <Button cta disabled={resolving} on:click={() => onApprove?.()}>
60
93
  Approve
@@ -63,10 +96,6 @@
63
96
  Reject
64
97
  </Button>
65
98
  </div>
66
- {:else}
67
- <Body size="XS" color="var(--spectrum-global-color-gray-600)">
68
- Awaiting a human response.
69
- </Body>
70
99
  {/if}
71
100
  </div>
72
101
 
@@ -89,13 +118,37 @@
89
118
  align-items: center;
90
119
  gap: var(--spacing-s);
91
120
  }
121
+ .escalation-card-toggle {
122
+ width: 100%;
123
+ margin: 0;
124
+ padding: 0;
125
+ border: none;
126
+ background: none;
127
+ cursor: pointer;
128
+ text-align: left;
129
+ color: inherit;
130
+ font: inherit;
131
+ }
132
+ .escalation-card-header-end {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: var(--spacing-xs);
136
+ margin-left: auto;
137
+ }
138
+ .escalation-card-chevron {
139
+ display: flex;
140
+ color: var(--spectrum-global-color-gray-700);
141
+ transition: transform 130ms ease-in-out;
142
+ }
143
+ .escalation-card-chevron.expanded {
144
+ transform: rotate(180deg);
145
+ }
92
146
  .escalation-card-title {
93
147
  font-weight: 600;
94
148
  font-size: 14px;
95
149
  color: var(--spectrum-global-color-gray-900);
96
150
  }
97
151
  .escalation-card-badge {
98
- margin-left: auto;
99
152
  border-radius: 999px;
100
153
  border: 1px solid var(--spectrum-global-color-blue-400);
101
154
  color: var(--spectrum-global-color-blue-700);
@@ -412,6 +412,10 @@
412
412
  }
413
413
  })
414
414
 
415
+ // Tool/reasoning toggles mutate the chat DOM; the observer below would
416
+ // otherwise pin the viewport to the bottom as height changes.
417
+ let skipAutoScroll = false
418
+
415
419
  const scrollToBottom = async () => {
416
420
  await tick()
417
421
  if (chatAreaElement) {
@@ -421,6 +425,9 @@
421
425
 
422
426
  $effect(() => {
423
427
  if (messages?.length) {
428
+ if (skipAutoScroll) {
429
+ return
430
+ }
424
431
  scrollToBottom()
425
432
  }
426
433
  })
@@ -495,8 +502,17 @@
495
502
  await sendMessage()
496
503
  }
497
504
 
498
- const toggleTool = (toolId: string) => {
499
- expandedTools = { ...expandedTools, [toolId]: !expandedTools[toolId] }
505
+ const toggleExpanded = (id: string) => {
506
+ const area = chatAreaElement
507
+ const previousScrollTop = area?.scrollTop ?? 0
508
+ skipAutoScroll = true
509
+ expandedTools = { ...expandedTools, [id]: !expandedTools[id] }
510
+ tick().then(() => {
511
+ if (area) {
512
+ area.scrollTop = previousScrollTop
513
+ }
514
+ skipAutoScroll = false
515
+ })
500
516
  }
501
517
 
502
518
  const formatToolOutput = (output: unknown): string =>
@@ -530,7 +546,12 @@
530
546
  $effect(() => {
531
547
  if (!chatAreaElement) return
532
548
 
533
- const obs = new MutationObserver(scrollToBottom)
549
+ const obs = new MutationObserver(() => {
550
+ if (skipAutoScroll) {
551
+ return
552
+ }
553
+ scrollToBottom()
554
+ })
534
555
  obs.observe(chatAreaElement, {
535
556
  childList: true,
536
557
  subtree: true,
@@ -543,8 +564,8 @@
543
564
  })
544
565
  </script>
545
566
 
546
- <div class="chat-area" bind:this={chatAreaElement}>
547
- <div class="chatbox">
567
+ <div class="chat-area">
568
+ <div class="chatbox" bind:this={chatAreaElement}>
548
569
  {#if !hasMessages && !isRequestPending}
549
570
  <div class="empty-state">
550
571
  <div class="empty-state-icon">
@@ -597,35 +618,13 @@
597
618
  interactive={!!reasoningText}
598
619
  expanded={Boolean(expandedTools[reasoningId])}
599
620
  content={reasoningText}
600
- ontoggle={() =>
601
- (expandedTools = {
602
- ...expandedTools,
603
- [reasoningId]: !expandedTools[reasoningId],
604
- })}
621
+ ontoggle={() => toggleExpanded(reasoningId)}
605
622
  />
606
623
  {/if}
607
624
  {#each message.parts ?? [] as part, partIndex}
608
625
  {#if isTextUIPart(part)}
609
626
  <MarkdownViewer value={part.text} />
610
- {:else if isToolUIPart(part) && isRaisedEscalation(part.output)}
611
- {@const card = escalationCardProps(part)}
612
- <EscalationCard
613
- title={card.title}
614
- summary={card.summary}
615
- resolution={card.resolution}
616
- statusMessage={card.escalationId
617
- ? resolveMessages[card.escalationId]
618
- : undefined}
619
- showApproval={showInlineApproval}
620
- resolving={!!card.escalationId &&
621
- !!resolvingEscalations[card.escalationId]}
622
- onApprove={() =>
623
- card.escalationId && handleResolve(card.escalationId, true)}
624
- onReject={() =>
625
- card.escalationId &&
626
- handleResolve(card.escalationId, false)}
627
- />
628
- {:else if isToolUIPart(part)}
627
+ {:else if isToolUIPart(part) && !isRaisedEscalation(part.output)}
629
628
  {@const rawToolName = getToolName(part)}
630
629
  {@const displayToolName = formatToolName(
631
630
  rawToolName,
@@ -642,7 +641,7 @@
642
641
  class="tool-header"
643
642
  class:tool-header-expanded={expandedTools[toolId]}
644
643
  type="button"
645
- onclick={() => toggleTool(toolId)}
644
+ onclick={() => toggleExpanded(toolId)}
646
645
  >
647
646
  <span
648
647
  class="tool-chevron"
@@ -753,6 +752,27 @@
753
752
  </ul>
754
753
  </div>
755
754
  {/if}
755
+ {#each message.parts ?? [] as part}
756
+ {#if isToolUIPart(part) && isRaisedEscalation(part.output)}
757
+ {@const card = escalationCardProps(part)}
758
+ <EscalationCard
759
+ title={card.title}
760
+ summary={card.summary}
761
+ resolution={card.resolution}
762
+ statusMessage={card.escalationId
763
+ ? resolveMessages[card.escalationId]
764
+ : undefined}
765
+ showApproval={showInlineApproval}
766
+ resolving={!!card.escalationId &&
767
+ !!resolvingEscalations[card.escalationId]}
768
+ onApprove={() =>
769
+ card.escalationId && handleResolve(card.escalationId, true)}
770
+ onReject={() =>
771
+ card.escalationId &&
772
+ handleResolve(card.escalationId, false)}
773
+ />
774
+ {/if}
775
+ {/each}
756
776
  </div>
757
777
  {/if}
758
778
  {/if}
@@ -803,7 +823,7 @@
803
823
  flex: 1 1 0;
804
824
  display: flex;
805
825
  flex-direction: column;
806
- overflow-y: auto;
826
+ overflow: hidden;
807
827
  min-height: 0;
808
828
  font-family: var(--chat-font-sans, var(--font-sans));
809
829
  --font-serif: var(--chat-font-sans, var(--font-sans));
@@ -819,7 +839,9 @@
819
839
  flex-direction: column;
820
840
  gap: 24px;
821
841
  width: 100%;
822
- flex: 1 1 auto;
842
+ flex: 1 1 0;
843
+ min-height: 0;
844
+ overflow-y: auto;
823
845
  padding: 48px 0 24px 0;
824
846
  }
825
847
 
@@ -868,14 +890,13 @@
868
890
  }
869
891
 
870
892
  .input-wrapper {
871
- position: sticky;
872
- bottom: 0;
873
893
  width: 100%;
874
894
  display: flex;
875
895
  flex-direction: column;
876
896
  flex-shrink: 0;
877
897
  line-height: 1.4;
878
898
  gap: 6px;
899
+ padding-top: 8px;
879
900
  }
880
901
 
881
902
  .input-footer {
@@ -983,7 +1004,9 @@
983
1004
  }
984
1005
 
985
1006
  .tool-part {
986
- position: relative;
1007
+ display: flex;
1008
+ flex-direction: column;
1009
+ align-items: stretch;
987
1010
  margin-top: var(--spacing-l);
988
1011
  margin-bottom: 0;
989
1012
  }
@@ -1076,22 +1099,17 @@
1076
1099
  }
1077
1100
 
1078
1101
  .tool-details {
1079
- position: absolute;
1080
- top: 100%;
1081
- left: 0;
1082
- margin-top: var(--spacing-m);
1102
+ margin-top: var(--spacing-s);
1083
1103
  width: 100%;
1084
1104
  max-width: 100%;
1085
1105
  box-sizing: border-box;
1086
1106
  display: flex;
1087
1107
  flex-direction: column;
1088
1108
  gap: var(--spacing-s);
1089
- background: var(--background);
1109
+ background: var(--spectrum-global-color-gray-75);
1090
1110
  border: 1px solid var(--spectrum-global-color-gray-200);
1091
1111
  border-radius: 6px;
1092
1112
  padding: var(--spacing-m);
1093
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
1094
- z-index: 1;
1095
1113
  overflow-x: hidden;
1096
1114
  min-width: 0;
1097
1115
  }
@@ -11,7 +11,6 @@ import BaseDataFetch, { DataFetchParams } from "./DataFetch"
11
11
 
12
12
  interface UserFetchQuery {
13
13
  appId?: string
14
- workspaceId?: string
15
14
  paginated?: boolean
16
15
  string?: {
17
16
  email: string
@@ -36,6 +35,7 @@ export default class UserFetch extends BaseDataFetch<
36
35
  super({
37
36
  ...opts,
38
37
  datasource: {
38
+ ...opts.datasource,
39
39
  type: "user",
40
40
  },
41
41
  })
@@ -56,9 +56,14 @@ export default class UserFetch extends BaseDataFetch<
56
56
  async getData() {
57
57
  const { limit, paginate } = this.options
58
58
  const { cursor, query } = get(this.store)
59
+ const {
60
+ groupIds,
61
+ workspaceId: datasourceWorkspaceId,
62
+ workspaceRoleId,
63
+ } = this.options.datasource
59
64
 
60
65
  // Convert old format to new one - we now allow use of the lucene format
61
- const { appId, paginated, workspaceId, ...rest } = query
66
+ const { appId, paginated, ...rest } = query
62
67
 
63
68
  const finalQuery: SearchFilters = utils.isSupportedUserSearch(rest)
64
69
  ? rest
@@ -69,7 +74,9 @@ export default class UserFetch extends BaseDataFetch<
69
74
  bookmark: cursor ?? undefined,
70
75
  query: finalQuery ?? undefined,
71
76
  appId: appId,
72
- workspaceId,
77
+ workspaceId: datasourceWorkspaceId,
78
+ groupIds,
79
+ workspaceRoleId,
73
80
  paginate: paginated || paginate,
74
81
  limit,
75
82
  }