@budibase/frontend-core 3.38.1 → 3.38.3

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.38.1",
3
+ "version": "3.38.3",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
@@ -23,5 +23,5 @@
23
23
  "devDependencies": {
24
24
  "vitest": "^3.2.4"
25
25
  },
26
- "gitHead": "5c558700d0c4dbc554a935b71b9ac99ec263a654"
26
+ "gitHead": "6d025a1d0b3c1b0dc4e1de0c88ce7f3ec6a97c0b"
27
27
  }
package/src/api/agents.ts CHANGED
@@ -12,6 +12,8 @@ import {
12
12
  FetchAgentsResponse,
13
13
  ProvisionAgentSlackChannelRequest,
14
14
  ProvisionAgentSlackChannelResponse,
15
+ ProvisionAgentTelegramChannelRequest,
16
+ ProvisionAgentTelegramChannelResponse,
15
17
  ProvisionAgentMSTeamsChannelRequest,
16
18
  ProvisionAgentMSTeamsChannelResponse,
17
19
  SyncAgentDiscordCommandsRequest,
@@ -48,6 +50,10 @@ export interface AgentEndpoints {
48
50
  agentId: string,
49
51
  body?: ProvisionAgentSlackChannelRequest
50
52
  ) => Promise<ProvisionAgentSlackChannelResponse>
53
+ provisionAgentTelegramChannel: (
54
+ agentId: string,
55
+ body?: ProvisionAgentTelegramChannelRequest
56
+ ) => Promise<ProvisionAgentTelegramChannelResponse>
51
57
  toggleAgentDiscordDeployment: (
52
58
  agentId: string,
53
59
  enabled: boolean
@@ -60,6 +66,10 @@ export interface AgentEndpoints {
60
66
  agentId: string,
61
67
  enabled: boolean
62
68
  ) => Promise<ToggleAgentDeploymentResponse>
69
+ toggleAgentTelegramDeployment: (
70
+ agentId: string,
71
+ enabled: boolean
72
+ ) => Promise<ToggleAgentDeploymentResponse>
63
73
  fetchAgentKnowledge: (agentId: string) => Promise<FetchAgentKnowledgeResponse>
64
74
  uploadAgentFile: (
65
75
  agentId: string,
@@ -167,6 +177,16 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
167
177
  })
168
178
  },
169
179
 
180
+ provisionAgentTelegramChannel: async (agentId: string, body) => {
181
+ return await API.post<
182
+ ProvisionAgentTelegramChannelRequest | undefined,
183
+ ProvisionAgentTelegramChannelResponse
184
+ >({
185
+ url: `/api/agent/${agentId}/telegram/provision`,
186
+ body,
187
+ })
188
+ },
189
+
170
190
  toggleAgentDiscordDeployment: async (agentId: string, enabled: boolean) => {
171
191
  return await API.post<
172
192
  ToggleAgentDeploymentRequest,
@@ -197,6 +217,16 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
197
217
  })
198
218
  },
199
219
 
220
+ toggleAgentTelegramDeployment: async (agentId: string, enabled: boolean) => {
221
+ return await API.post<
222
+ ToggleAgentDeploymentRequest,
223
+ ToggleAgentDeploymentResponse
224
+ >({
225
+ url: `/api/agent/${agentId}/telegram/toggle`,
226
+ body: { enabled },
227
+ })
228
+ },
229
+
200
230
  fetchAgentKnowledge: async (agentId: string) => {
201
231
  return await API.get<FetchAgentKnowledgeResponse>({
202
232
  url: `/api/agent/${agentId}/knowledge`,
@@ -0,0 +1,354 @@
1
+ <script lang="ts">
2
+ import {
3
+ Icon,
4
+ Popover,
5
+ PopoverAlignment,
6
+ type PopoverAPI,
7
+ } from "@budibase/bbui"
8
+ import type {
9
+ AgentMessageUsage,
10
+ AgentMessageUsageSegment,
11
+ } from "@budibase/types"
12
+
13
+ type SegmentDetails = {
14
+ name: string
15
+ color: string
16
+ }
17
+ type VisibleSegment = AgentMessageUsageSegment & SegmentDetails
18
+
19
+ interface Props {
20
+ usage?: AgentMessageUsage
21
+ }
22
+
23
+ let { usage }: Props = $props()
24
+
25
+ let open = $state(false)
26
+ let triggerEl = $state<HTMLButtonElement>()
27
+ let popover = $state<PopoverAPI>()
28
+
29
+ const DEFAULT_MAX_TOKENS = 200000
30
+ const SEGMENT_DETAILS: Record<
31
+ AgentMessageUsageSegment["type"],
32
+ SegmentDetails
33
+ > = {
34
+ system: {
35
+ name: "System prompt",
36
+ color: "var(--grey-7)",
37
+ },
38
+ input: {
39
+ name: "Input",
40
+ color: "var(--color-purple-500)",
41
+ },
42
+ cachedInput: {
43
+ name: "Cached input",
44
+ color: "var(--color-blue-400)",
45
+ },
46
+ output: {
47
+ name: "Output",
48
+ color: "var(--color-red-500)",
49
+ },
50
+ reasoning: {
51
+ name: "Reasoning",
52
+ color: "var(--color-orange-500)",
53
+ },
54
+ }
55
+
56
+ const maxTokens = $derived(usage?.maxTokens ?? DEFAULT_MAX_TOKENS)
57
+ const visibleSegments = $derived.by((): VisibleSegment[] =>
58
+ (usage?.segments || [])
59
+ .filter(segment => segment.tokens > 0)
60
+ .map(segment => ({
61
+ ...segment,
62
+ ...SEGMENT_DETAILS[segment.type],
63
+ }))
64
+ )
65
+ const totalTokens = $derived(
66
+ visibleSegments.reduce((sum, s) => sum + s.tokens, 0)
67
+ )
68
+ const percentage = $derived(
69
+ maxTokens > 0
70
+ ? Math.min(100, Math.round((totalTokens / maxTokens) * 100))
71
+ : 0
72
+ )
73
+
74
+ const RING_RADIUS = 7
75
+ const RING_CIRC = 2 * Math.PI * RING_RADIUS
76
+ const dashOffset = $derived(RING_CIRC - (percentage / 100) * RING_CIRC)
77
+
78
+ const compact = new Intl.NumberFormat("en", {
79
+ notation: "compact",
80
+ maximumFractionDigits: 1,
81
+ })
82
+ </script>
83
+
84
+ <button
85
+ bind:this={triggerEl}
86
+ type="button"
87
+ class="trigger"
88
+ class:active={open}
89
+ onclick={() => popover?.show()}
90
+ aria-label="Context usage"
91
+ aria-expanded={open}
92
+ >
93
+ <span class="ring" aria-hidden="true">
94
+ <svg viewBox="0 0 18 18">
95
+ <circle cx="9" cy="9" r={RING_RADIUS} class="ring-track" />
96
+ <circle
97
+ cx="9"
98
+ cy="9"
99
+ r={RING_RADIUS}
100
+ class="ring-progress"
101
+ style="stroke-dasharray: {RING_CIRC}; stroke-dashoffset: {dashOffset};"
102
+ />
103
+ </svg>
104
+ </span>
105
+ <span class="trigger-label">
106
+ <span class="trigger-percent">{percentage}%</span>
107
+ <span class="trigger-suffix">context</span>
108
+ </span>
109
+ </button>
110
+
111
+ <Popover
112
+ bind:this={popover}
113
+ bind:open
114
+ anchor={triggerEl}
115
+ align={PopoverAlignment.Right}
116
+ offset={8}
117
+ minWidth={420}
118
+ maxWidth={420}
119
+ resizable={false}
120
+ >
121
+ <div class="popover">
122
+ <header class="popover-header">
123
+ <span class="popover-title">Context</span>
124
+ <span class="popover-fill">{percentage}% Full</span>
125
+ <div class="popover-right">
126
+ {#if usage}
127
+ <span class="popover-total">
128
+ ~{compact.format(totalTokens)} / {compact.format(maxTokens)} Tokens
129
+ </span>
130
+ {/if}
131
+ <button
132
+ class="popover-close"
133
+ type="button"
134
+ onclick={() => popover?.hide()}
135
+ aria-label="Close context details"
136
+ >
137
+ <Icon
138
+ name="x"
139
+ size="S"
140
+ color="var(--spectrum-global-color-gray-700)"
141
+ />
142
+ </button>
143
+ </div>
144
+ </header>
145
+
146
+ <div class="bar" role="img" aria-label="Context breakdown">
147
+ {#each visibleSegments as segment (segment.name)}
148
+ {@const segPct = (segment.tokens / maxTokens) * 100}
149
+ <span
150
+ class="bar-segment"
151
+ style="width: {segPct}%; background: {segment.color};"
152
+ ></span>
153
+ {/each}
154
+ </div>
155
+
156
+ <ul class="legend">
157
+ {#each visibleSegments as segment (segment.name)}
158
+ <li class="legend-row">
159
+ <span class="legend-swatch" style="background: {segment.color};"
160
+ ></span>
161
+ <span class="legend-name">{segment.name}</span>
162
+ <span class="legend-tokens">{compact.format(segment.tokens)}</span>
163
+ </li>
164
+ {/each}
165
+ </ul>
166
+ </div>
167
+ </Popover>
168
+
169
+ <style>
170
+ .trigger {
171
+ display: inline-flex;
172
+ align-items: center;
173
+ gap: 6px;
174
+ padding: 4px 10px 4px 6px;
175
+ border: 1px solid transparent;
176
+ border-radius: 999px;
177
+ background: transparent;
178
+ color: var(--spectrum-global-color-gray-700);
179
+ font-size: 12px;
180
+ line-height: 1;
181
+ cursor: pointer;
182
+ transition:
183
+ background-color 0.15s ease,
184
+ border-color 0.15s ease,
185
+ color 0.15s ease;
186
+ }
187
+
188
+ .trigger:hover,
189
+ .trigger.active {
190
+ background: var(--spectrum-global-color-gray-100);
191
+ border-color: var(--spectrum-global-color-gray-200);
192
+ color: var(--spectrum-global-color-gray-900);
193
+ }
194
+
195
+ .ring {
196
+ display: inline-flex;
197
+ width: 16px;
198
+ height: 16px;
199
+ }
200
+
201
+ .ring svg {
202
+ width: 100%;
203
+ height: 100%;
204
+ transform: rotate(-90deg);
205
+ }
206
+
207
+ .ring-track {
208
+ fill: none;
209
+ stroke: var(--spectrum-global-color-gray-300);
210
+ stroke-width: 2;
211
+ }
212
+
213
+ .ring-progress {
214
+ fill: none;
215
+ stroke: var(--spectrum-global-color-gray-800);
216
+ stroke-width: 2;
217
+ stroke-linecap: round;
218
+ transition:
219
+ stroke-dashoffset 0.4s cubic-bezier(0.22, 1, 0.36, 1),
220
+ stroke 0.2s ease;
221
+ }
222
+
223
+ .trigger-label {
224
+ display: inline-flex;
225
+ gap: 4px;
226
+ align-items: baseline;
227
+ font-variant-numeric: tabular-nums;
228
+ }
229
+
230
+ .trigger-percent {
231
+ font-weight: 600;
232
+ color: var(--spectrum-global-color-gray-900);
233
+ }
234
+
235
+ .trigger-suffix {
236
+ color: var(--spectrum-global-color-gray-700);
237
+ }
238
+
239
+ .popover {
240
+ padding: 16px 16px 14px;
241
+ display: flex;
242
+ flex-direction: column;
243
+ gap: 12px;
244
+ }
245
+
246
+ .popover-header {
247
+ display: flex;
248
+ align-items: center;
249
+ gap: 10px;
250
+ font-variant-numeric: tabular-nums;
251
+ }
252
+
253
+ .popover-title {
254
+ font-size: 14px;
255
+ font-weight: 600;
256
+ color: var(--spectrum-global-color-gray-900);
257
+ letter-spacing: -0.01em;
258
+ }
259
+
260
+ .popover-fill {
261
+ font-size: 12px;
262
+ color: var(--spectrum-global-color-gray-700);
263
+ }
264
+
265
+ .popover-right {
266
+ margin-left: auto;
267
+ display: inline-flex;
268
+ align-items: center;
269
+ gap: 10px;
270
+ }
271
+
272
+ .popover-total {
273
+ font-size: 12px;
274
+ color: var(--spectrum-global-color-gray-700);
275
+ white-space: nowrap;
276
+ }
277
+
278
+ .popover-close {
279
+ width: 22px;
280
+ height: 22px;
281
+ display: inline-flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ border-radius: 6px;
285
+ border: none;
286
+ background: transparent;
287
+ cursor: pointer;
288
+ color: var(--spectrum-global-color-gray-700);
289
+ transition: background-color 0.15s ease;
290
+ }
291
+
292
+ .popover-close:hover {
293
+ background: var(--spectrum-global-color-gray-200);
294
+ }
295
+
296
+ .bar {
297
+ display: flex;
298
+ width: 100%;
299
+ height: 8px;
300
+ border-radius: 999px;
301
+ background: var(--spectrum-global-color-gray-200);
302
+ overflow: hidden;
303
+ gap: 2px;
304
+ }
305
+
306
+ .bar-segment {
307
+ height: 100%;
308
+ transition: width 0.4s cubic-bezier(0.22, 1, 0.36, 1);
309
+ }
310
+
311
+ .bar-segment:first-child {
312
+ border-top-left-radius: 999px;
313
+ border-bottom-left-radius: 999px;
314
+ }
315
+
316
+ .legend {
317
+ list-style: none;
318
+ margin: 0;
319
+ padding: 0;
320
+ display: flex;
321
+ flex-direction: column;
322
+ gap: 2px;
323
+ }
324
+
325
+ .legend-row {
326
+ display: grid;
327
+ grid-template-columns: 14px 1fr auto;
328
+ align-items: center;
329
+ gap: 10px;
330
+ padding: 6px 2px;
331
+ font-size: 13px;
332
+ }
333
+
334
+ .legend-row + .legend-row {
335
+ border-top: 1px solid var(--spectrum-global-color-gray-100);
336
+ }
337
+
338
+ .legend-swatch {
339
+ width: 10px;
340
+ height: 10px;
341
+ border-radius: 2px;
342
+ display: inline-block;
343
+ }
344
+
345
+ .legend-name {
346
+ color: var(--spectrum-global-color-gray-800);
347
+ }
348
+
349
+ .legend-tokens {
350
+ color: var(--spectrum-global-color-gray-700);
351
+ font-variant-numeric: tabular-nums;
352
+ font-size: 12px;
353
+ }
354
+ </style>
@@ -18,6 +18,7 @@
18
18
  import { Chat } from "@ai-sdk/svelte"
19
19
  import { formatToolName } from "../../utils/aiTools"
20
20
  import ReasoningStatus from "./ReasoningStatus.svelte"
21
+ import ContextUsage from "./ContextUsage.svelte"
21
22
  import {
22
23
  DefaultChatTransport,
23
24
  isTextUIPart,
@@ -232,6 +233,11 @@
232
233
 
233
234
  let messages = $derived(chatInstance.messages)
234
235
  let lastMessage = $derived(messages[messages.length - 1])
236
+
237
+ let lastAssistantUsage = $derived(
238
+ messages.findLast(m => m.role === "assistant" && m.metadata?.usage)
239
+ ?.metadata?.usage
240
+ )
235
241
  let lastAssistantMessage = $derived(
236
242
  messages.findLast(message => message.role === "assistant")
237
243
  )
@@ -713,6 +719,9 @@
713
719
  {/if}
714
720
  </button>
715
721
  </div>
722
+ <div class="input-footer">
723
+ <ContextUsage usage={lastAssistantUsage} />
724
+ </div>
716
725
  </div>
717
726
  {/if}
718
727
  </div>
@@ -838,6 +847,13 @@
838
847
  flex-direction: column;
839
848
  flex-shrink: 0;
840
849
  line-height: 1.4;
850
+ gap: 6px;
851
+ }
852
+
853
+ .input-footer {
854
+ display: flex;
855
+ justify-content: flex-end;
856
+ padding: 0 4px;
841
857
  }
842
858
 
843
859
  .read-only-notice {