@budibase/frontend-core 3.26.3 → 3.27.1
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 +8 -3
- package/src/components/Chatbox/index.svelte +80 -61
- package/src/components/grid/cells/RelationshipCell.svelte +2 -1
- package/src/components/grid/stores/cache.ts +2 -1
- package/src/constants.ts +0 -1
- package/src/utils/searchFields.js +16 -10
- package/src/utils/searchFields.test.js +93 -0
- package/vite.config.mjs +23 -0
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.27.1",
|
|
4
4
|
"description": "Budibase frontend core libraries used in builder and client",
|
|
5
5
|
"author": "Budibase",
|
|
6
6
|
"license": "MPL-2.0",
|
|
7
7
|
"svelte": "./src/index.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"check:types": "yarn svelte-check"
|
|
9
|
+
"check:types": "yarn svelte-check",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"test:watch": "vitest"
|
|
10
12
|
},
|
|
11
13
|
"dependencies": {
|
|
12
14
|
"@ai-sdk/svelte": "^4.0.48",
|
|
@@ -19,5 +21,8 @@
|
|
|
19
21
|
"shortid": "2.2.15",
|
|
20
22
|
"socket.io-client": "^4.7.5"
|
|
21
23
|
},
|
|
22
|
-
"
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"vitest": "^3.2.4"
|
|
26
|
+
},
|
|
27
|
+
"gitHead": "e16e4f2acb1dbf82177c69b4cbfd23c3bdf16544"
|
|
23
28
|
}
|
|
@@ -62,6 +62,22 @@
|
|
|
62
62
|
let inputValue = $state("")
|
|
63
63
|
let reasoningTimers = $state<Record<string, number>>({})
|
|
64
64
|
|
|
65
|
+
const getReasoningText = (message: UIMessage<AgentMessageMetadata>) =>
|
|
66
|
+
(message.parts ?? [])
|
|
67
|
+
.filter(isReasoningUIPart)
|
|
68
|
+
.map(p => p.text)
|
|
69
|
+
.join("")
|
|
70
|
+
|
|
71
|
+
const isReasoningStreaming = (message: UIMessage<AgentMessageMetadata>) =>
|
|
72
|
+
(message.parts ?? []).some(
|
|
73
|
+
part => isReasoningUIPart(part) && part.state === "streaming"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const hasToolError = (message: UIMessage<AgentMessageMetadata>) =>
|
|
77
|
+
(message.parts ?? []).some(
|
|
78
|
+
part => isToolUIPart(part) && part.state === "output-error"
|
|
79
|
+
)
|
|
80
|
+
|
|
65
81
|
$effect(() => {
|
|
66
82
|
const interval = setInterval(() => {
|
|
67
83
|
let updated = false
|
|
@@ -71,24 +87,33 @@
|
|
|
71
87
|
if (message.role !== "assistant") continue
|
|
72
88
|
const createdAt = message.metadata?.createdAt
|
|
73
89
|
const completedAt = message.metadata?.completedAt
|
|
90
|
+
const id = `${message.id}-reasoning`
|
|
91
|
+
|
|
92
|
+
if (!createdAt) continue
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
if (completedAt) {
|
|
95
|
+
const finalElapsed = (completedAt - createdAt) / 1000
|
|
96
|
+
if (newTimers[id] !== finalElapsed) {
|
|
97
|
+
newTimers[id] = finalElapsed
|
|
98
|
+
updated = true
|
|
99
|
+
}
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const toolError = hasToolError(message)
|
|
104
|
+
if (toolError) {
|
|
105
|
+
if (newTimers[id] == null) {
|
|
106
|
+
newTimers[id] = (Date.now() - createdAt) / 1000
|
|
107
|
+
updated = true
|
|
108
|
+
}
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isReasoningStreaming(message)) {
|
|
113
|
+
const newElapsed = (Date.now() - createdAt) / 1000
|
|
114
|
+
if (newTimers[id] !== newElapsed) {
|
|
115
|
+
newTimers[id] = newElapsed
|
|
116
|
+
updated = true
|
|
92
117
|
}
|
|
93
118
|
}
|
|
94
119
|
}
|
|
@@ -381,48 +406,48 @@
|
|
|
381
406
|
<MarkdownViewer value={getUserMessageText(message)} />
|
|
382
407
|
</div>
|
|
383
408
|
{:else if message.role === "assistant"}
|
|
409
|
+
{@const reasoningText = getReasoningText(message)}
|
|
410
|
+
{@const reasoningId = `${message.id}-reasoning`}
|
|
411
|
+
{@const toolError = hasToolError(message)}
|
|
412
|
+
{@const reasoningStreaming = isReasoningStreaming(message)}
|
|
413
|
+
{@const isThinking =
|
|
414
|
+
reasoningStreaming && !toolError && !message.metadata?.completedAt}
|
|
384
415
|
<div class="message assistant">
|
|
416
|
+
{#if reasoningText}
|
|
417
|
+
<div class="reasoning-part">
|
|
418
|
+
<button
|
|
419
|
+
class="reasoning-toggle"
|
|
420
|
+
type="button"
|
|
421
|
+
onclick={() =>
|
|
422
|
+
(expandedTools = {
|
|
423
|
+
...expandedTools,
|
|
424
|
+
[reasoningId]: !expandedTools[reasoningId],
|
|
425
|
+
})}
|
|
426
|
+
>
|
|
427
|
+
<span class="reasoning-icon" class:shimmer={isThinking}>
|
|
428
|
+
<Icon
|
|
429
|
+
name="brain"
|
|
430
|
+
size="M"
|
|
431
|
+
color="var(--spectrum-global-color-gray-600)"
|
|
432
|
+
/>
|
|
433
|
+
</span>
|
|
434
|
+
<span class="reasoning-label" class:shimmer={isThinking}>
|
|
435
|
+
{isThinking ? "Thinking" : "Thought for"}
|
|
436
|
+
{#if reasoningTimers[reasoningId]}
|
|
437
|
+
<span class="reasoning-timer"
|
|
438
|
+
>{reasoningTimers[reasoningId].toFixed(1)}s</span
|
|
439
|
+
>
|
|
440
|
+
{/if}
|
|
441
|
+
</span>
|
|
442
|
+
</button>
|
|
443
|
+
{#if expandedTools[reasoningId]}
|
|
444
|
+
<div class="reasoning-content">{reasoningText}</div>
|
|
445
|
+
{/if}
|
|
446
|
+
</div>
|
|
447
|
+
{/if}
|
|
385
448
|
{#each message.parts ?? [] as part, partIndex}
|
|
386
449
|
{#if isTextUIPart(part)}
|
|
387
450
|
<MarkdownViewer value={part.text} />
|
|
388
|
-
{:else if isReasoningUIPart(part)}
|
|
389
|
-
{@const reasoningId = `${message.id}-reasoning-${partIndex}`}
|
|
390
|
-
<div class="reasoning-part">
|
|
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}
|
|
425
|
-
</div>
|
|
426
451
|
{:else if isToolUIPart(part)}
|
|
427
452
|
{@const toolId = `${message.id}-${getToolName(part)}-${partIndex}`}
|
|
428
453
|
{@const isRunning =
|
|
@@ -639,12 +664,6 @@
|
|
|
639
664
|
max-width: 100%;
|
|
640
665
|
}
|
|
641
666
|
|
|
642
|
-
.message.system {
|
|
643
|
-
align-self: flex-start;
|
|
644
|
-
background: none;
|
|
645
|
-
padding-left: 0;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
667
|
.input-wrapper {
|
|
649
668
|
position: sticky;
|
|
650
669
|
bottom: 0;
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// Reset state if this search is invalid
|
|
100
|
-
if (!schema?.tableId || !isOpen) {
|
|
100
|
+
if (!schema?.tableId || !isOpen || !primaryDisplay) {
|
|
101
101
|
lastSearchString = null
|
|
102
102
|
candidateIndex = null
|
|
103
103
|
searchResults = []
|
|
@@ -159,6 +159,7 @@
|
|
|
159
159
|
primaryDisplay = await cache.actions.getPrimaryDisplayForTableId(
|
|
160
160
|
schema.tableId
|
|
161
161
|
)
|
|
162
|
+
searching = false
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
// Show initial list of results
|
|
@@ -38,7 +38,8 @@ export const createActions = (context: StoreContext): CacheActionStore => {
|
|
|
38
38
|
|
|
39
39
|
const getPrimaryDisplayForTableId = async (tableId: string) => {
|
|
40
40
|
const table = await fetchTable(tableId)
|
|
41
|
-
const
|
|
41
|
+
const firstSchemaKey = Object.keys(table?.schema || {})[0]
|
|
42
|
+
const display = table?.primaryDisplay || firstSchemaKey
|
|
42
43
|
return display
|
|
43
44
|
}
|
|
44
45
|
|
package/src/constants.ts
CHANGED
|
@@ -11,7 +11,6 @@ import { BpmCorrelationKey } from "@budibase/shared-core"
|
|
|
11
11
|
import { BBReferenceFieldSubType, FieldType } from "@budibase/types"
|
|
12
12
|
|
|
13
13
|
export const BannedSearchTypes = [
|
|
14
|
-
FieldType.LINK,
|
|
15
14
|
FieldType.ATTACHMENT_SINGLE,
|
|
16
15
|
FieldType.ATTACHMENTS,
|
|
17
16
|
FieldType.FORMULA,
|
|
@@ -8,10 +8,12 @@ export function getTableFields(tables, linkField) {
|
|
|
8
8
|
const linkFields = getFields(tables, Object.values(table.schema), {
|
|
9
9
|
allowLinks: false,
|
|
10
10
|
})
|
|
11
|
-
return linkFields
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
return linkFields
|
|
12
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
13
|
+
.map(field => ({
|
|
14
|
+
...field,
|
|
15
|
+
name: `${linkField.name}.${field.name}`,
|
|
16
|
+
}))
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export function getFields(
|
|
@@ -19,18 +21,22 @@ export function getFields(
|
|
|
19
21
|
fields,
|
|
20
22
|
{ allowLinks } = { allowLinks: true }
|
|
21
23
|
) {
|
|
24
|
+
const result = []
|
|
22
25
|
let filteredFields = fields.filter(
|
|
23
|
-
field =>
|
|
26
|
+
field =>
|
|
27
|
+
!BannedSearchTypes.includes(field.type) &&
|
|
28
|
+
(allowLinks || field.type !== "link")
|
|
24
29
|
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
for (const field of filteredFields) {
|
|
31
|
+
result.push(field)
|
|
32
|
+
|
|
33
|
+
if (allowLinks && field.type === "link") {
|
|
28
34
|
// only allow one depth of SQL relationship filtering
|
|
29
|
-
|
|
35
|
+
result.push(...getTableFields(tables, field))
|
|
30
36
|
}
|
|
31
37
|
}
|
|
32
38
|
const staticFormulaFields = fields.filter(
|
|
33
39
|
field => field.type === "formula" && field.formulaType === "static"
|
|
34
40
|
)
|
|
35
|
-
return
|
|
41
|
+
return result.concat(staticFormulaFields)
|
|
36
42
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { getFields, getTableFields } from "./searchFields.js"
|
|
3
|
+
|
|
4
|
+
const field = (name, type, extra = {}) => ({
|
|
5
|
+
name,
|
|
6
|
+
type,
|
|
7
|
+
...extra,
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const table = (id, schema) => ({
|
|
11
|
+
_id: id,
|
|
12
|
+
name: id,
|
|
13
|
+
sql: true,
|
|
14
|
+
schema,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe("search fields", () => {
|
|
18
|
+
const relatedTable = table("t2", {
|
|
19
|
+
title: field("title", "string"),
|
|
20
|
+
otherLink: field("otherLink", "link", { tableId: "t3" }),
|
|
21
|
+
})
|
|
22
|
+
const baseTable = table("t1", {
|
|
23
|
+
name: field("name", "string"),
|
|
24
|
+
rel: field("rel", "link", { tableId: "t2" }),
|
|
25
|
+
})
|
|
26
|
+
const tables = [baseTable, relatedTable]
|
|
27
|
+
|
|
28
|
+
describe("getTableFields", () => {
|
|
29
|
+
it("excludes nested link fields when allowLinks is false", () => {
|
|
30
|
+
const fields = getTableFields(tables, baseTable.schema.rel)
|
|
31
|
+
expect(fields.map(field => field.name)).toEqual(["rel.title"])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("returns empty for non-sql related tables", () => {
|
|
35
|
+
const nonSqlTable = {
|
|
36
|
+
...relatedTable,
|
|
37
|
+
sql: false,
|
|
38
|
+
}
|
|
39
|
+
const fields = getTableFields([baseTable, nonSqlTable], {
|
|
40
|
+
...baseTable.schema.rel,
|
|
41
|
+
tableId: nonSqlTable._id,
|
|
42
|
+
})
|
|
43
|
+
expect(fields).toEqual([])
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe("getFields", () => {
|
|
48
|
+
it("excludes link fields when allowLinks is false", () => {
|
|
49
|
+
const fields = getFields(tables, Object.values(baseTable.schema), {
|
|
50
|
+
allowLinks: false,
|
|
51
|
+
})
|
|
52
|
+
expect(fields.map(field => field.name)).toEqual(["name"])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it("includes one relationship depth when allowLinks is true", () => {
|
|
56
|
+
const fields = getFields(tables, Object.values(baseTable.schema), {
|
|
57
|
+
allowLinks: true,
|
|
58
|
+
})
|
|
59
|
+
const names = fields.map(field => field.name)
|
|
60
|
+
expect(names).toEqual(
|
|
61
|
+
expect.arrayContaining(["name", "rel", "rel.title"])
|
|
62
|
+
)
|
|
63
|
+
expect(names).not.toEqual(expect.arrayContaining(["rel.otherLink"]))
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it("appends static formula fields even when allowLinks is false", () => {
|
|
67
|
+
const withFormula = table("t4", {
|
|
68
|
+
name: field("name", "string"),
|
|
69
|
+
calc: field("calc", "formula", { formulaType: "static" }),
|
|
70
|
+
rel: field("rel", "link", { tableId: "t2" }),
|
|
71
|
+
})
|
|
72
|
+
const fields = getFields(tables, Object.values(withFormula.schema), {
|
|
73
|
+
allowLinks: false,
|
|
74
|
+
})
|
|
75
|
+
expect(fields.map(field => field.name)).toEqual(
|
|
76
|
+
expect.arrayContaining(["name", "calc"])
|
|
77
|
+
)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("filters out banned field types", () => {
|
|
81
|
+
const withBanned = table("t5", {
|
|
82
|
+
name: field("name", "string"),
|
|
83
|
+
meta: field("meta", "json"),
|
|
84
|
+
files: field("files", "attachment"),
|
|
85
|
+
file: field("file", "attachment_single"),
|
|
86
|
+
})
|
|
87
|
+
const fields = getFields(tables, Object.values(withBanned.schema), {
|
|
88
|
+
allowLinks: false,
|
|
89
|
+
})
|
|
90
|
+
expect(fields.map(field => field.name)).toEqual(["name"])
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
})
|
package/vite.config.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from "vite"
|
|
2
|
+
import path from "path"
|
|
3
|
+
|
|
4
|
+
export default defineConfig(() => {
|
|
5
|
+
return {
|
|
6
|
+
test: {
|
|
7
|
+
globals: true,
|
|
8
|
+
include: ["src/**/*.test.*", "src/**/*.spec.*"],
|
|
9
|
+
},
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: [
|
|
12
|
+
{
|
|
13
|
+
find: "@budibase/types",
|
|
14
|
+
replacement: path.resolve("../types/src"),
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
find: "@budibase/shared-core",
|
|
18
|
+
replacement: path.resolve("../shared-core/src"),
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
})
|