@budibase/frontend-core 3.23.28 → 3.23.30
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 +2 -2
- package/src/api/agents.ts +31 -67
- package/src/components/ChangePasswordModal.svelte +24 -6
- package/src/components/Chatbox/index.svelte +15 -76
- package/src/components/FilterUsers.svelte +3 -1
- package/src/components/PasswordRepeatInput.svelte +8 -4
- package/src/components/ProfileModal.svelte +22 -7
- package/src/components/grid/cells/RelationshipCell.svelte +8 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/translationGroups.ts +40 -0
- package/src/utils/utils.ts +28 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.23.
|
|
3
|
+
"version": "3.23.30",
|
|
4
4
|
"description": "Budibase frontend core libraries used in builder and client",
|
|
5
5
|
"author": "Budibase",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -18,5 +18,5 @@
|
|
|
18
18
|
"shortid": "2.2.15",
|
|
19
19
|
"socket.io-client": "^4.7.5"
|
|
20
20
|
},
|
|
21
|
-
"gitHead": "
|
|
21
|
+
"gitHead": "f14a0f7924ce2526ffddd2e9eae9e94eb702aa80"
|
|
22
22
|
}
|
package/src/api/agents.ts
CHANGED
|
@@ -14,15 +14,14 @@ import {
|
|
|
14
14
|
|
|
15
15
|
import { Header } from "@budibase/shared-core"
|
|
16
16
|
import { BaseAPIClient } from "./types"
|
|
17
|
-
import { UIMessageChunk } from "ai"
|
|
17
|
+
import { readUIMessageStream, UIMessage, UIMessageChunk } from "ai"
|
|
18
|
+
import { createSseToJsonTransformStream } from "../utils/utils"
|
|
18
19
|
|
|
19
20
|
export interface AgentEndpoints {
|
|
20
21
|
agentChatStream: (
|
|
21
22
|
chat: AgentChat,
|
|
22
|
-
workspaceId: string
|
|
23
|
-
|
|
24
|
-
onError?: (error: Error) => void
|
|
25
|
-
) => Promise<void>
|
|
23
|
+
workspaceId: string
|
|
24
|
+
) => Promise<AsyncIterable<UIMessage>>
|
|
26
25
|
|
|
27
26
|
removeChat: (chatId: string) => Promise<void>
|
|
28
27
|
fetchChats: (agentId: string) => Promise<FetchAgentHistoryResponse>
|
|
@@ -40,71 +39,36 @@ export interface AgentEndpoints {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
|
|
43
|
-
agentChatStream: async (chat, workspaceId
|
|
42
|
+
agentChatStream: async (chat, workspaceId) => {
|
|
44
43
|
const body: ChatAgentRequest = chat
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const reader = response.body?.getReader()
|
|
68
|
-
if (!reader) {
|
|
69
|
-
throw new Error("Failed to get response reader")
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const decoder = new TextDecoder()
|
|
73
|
-
let buffer = ""
|
|
74
|
-
|
|
75
|
-
while (true) {
|
|
76
|
-
const { done, value } = await reader.read()
|
|
77
|
-
|
|
78
|
-
if (done) break
|
|
79
|
-
|
|
80
|
-
buffer += decoder.decode(value, { stream: true })
|
|
81
|
-
|
|
82
|
-
// Process complete lines
|
|
83
|
-
const lines = buffer.split("\n")
|
|
84
|
-
buffer = lines.pop() || "" // Keep incomplete line in buffer
|
|
85
|
-
|
|
86
|
-
for (const line of lines) {
|
|
87
|
-
if (line.startsWith("data: ")) {
|
|
88
|
-
try {
|
|
89
|
-
const data = line.slice(6) // Remove 'data: ' prefix
|
|
90
|
-
const trimmedData = data.trim()
|
|
91
|
-
if (trimmedData && trimmedData !== "[DONE]") {
|
|
92
|
-
const chunk: UIMessageChunk = JSON.parse(data)
|
|
93
|
-
onChunk(chunk)
|
|
94
|
-
}
|
|
95
|
-
} catch (parseError) {
|
|
96
|
-
console.error("Failed to parse SSE data:", parseError)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
} catch (error: any) {
|
|
102
|
-
if (onError) {
|
|
103
|
-
onError(error)
|
|
104
|
-
} else {
|
|
105
|
-
throw error
|
|
106
|
-
}
|
|
45
|
+
const response = await fetch("/api/agent/chat/stream", {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
Accept: "application/json",
|
|
50
|
+
[Header.APP_ID]: workspaceId,
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify(body),
|
|
53
|
+
credentials: "same-origin",
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const errorBody = await response.json()
|
|
58
|
+
throw new Error(
|
|
59
|
+
errorBody.message || `HTTP error! status: ${response.status}`
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!response.body) {
|
|
64
|
+
throw new Error("Failed to get response body")
|
|
107
65
|
}
|
|
66
|
+
|
|
67
|
+
const chunkStream = response.body
|
|
68
|
+
.pipeThrough(new TextDecoderStream())
|
|
69
|
+
.pipeThrough(createSseToJsonTransformStream<UIMessageChunk>())
|
|
70
|
+
|
|
71
|
+
return readUIMessageStream({ stream: chunkStream })
|
|
108
72
|
},
|
|
109
73
|
|
|
110
74
|
removeChat: async (chatId: string) => {
|
|
@@ -3,24 +3,36 @@
|
|
|
3
3
|
import PasswordRepeatInput from "./PasswordRepeatInput.svelte"
|
|
4
4
|
import type { APIClient } from "@budibase/frontend-core"
|
|
5
5
|
import { createEventDispatcher } from "svelte"
|
|
6
|
+
import { resolveTranslationGroup } from "@budibase/shared-core"
|
|
6
7
|
|
|
7
8
|
export let API: APIClient
|
|
8
9
|
export let passwordMinLength: string | undefined = undefined
|
|
9
10
|
export let notifySuccess = notifications.success
|
|
10
11
|
export let notifyError = notifications.error
|
|
12
|
+
// Get the default translations for the password modal and derive a type from it.
|
|
13
|
+
// `labels` can override any subset of these defaults while keeping type safety.
|
|
14
|
+
const DEFAULT_LABELS = resolveTranslationGroup("passwordModal")
|
|
15
|
+
type PasswordModalLabels = typeof DEFAULT_LABELS
|
|
16
|
+
|
|
17
|
+
export let labels: Partial<PasswordModalLabels> = {}
|
|
11
18
|
|
|
12
19
|
const dispatch = createEventDispatcher()
|
|
13
20
|
|
|
21
|
+
$: resolvedLabels = {
|
|
22
|
+
...DEFAULT_LABELS,
|
|
23
|
+
...labels,
|
|
24
|
+
} as PasswordModalLabels
|
|
25
|
+
|
|
14
26
|
let password: string = ""
|
|
15
27
|
let error: string = ""
|
|
16
28
|
|
|
17
29
|
const updatePassword = async () => {
|
|
18
30
|
try {
|
|
19
31
|
await API.updateSelf({ password })
|
|
20
|
-
notifySuccess(
|
|
32
|
+
notifySuccess(resolvedLabels.successText)
|
|
21
33
|
dispatch("save")
|
|
22
34
|
} catch (error) {
|
|
23
|
-
notifyError(
|
|
35
|
+
notifyError(resolvedLabels.errorText)
|
|
24
36
|
}
|
|
25
37
|
}
|
|
26
38
|
|
|
@@ -33,11 +45,17 @@
|
|
|
33
45
|
|
|
34
46
|
<svelte:window on:keydown={handleKeydown} />
|
|
35
47
|
<ModalContent
|
|
36
|
-
title=
|
|
37
|
-
confirmText=
|
|
48
|
+
title={resolvedLabels.title}
|
|
49
|
+
confirmText={resolvedLabels.saveText}
|
|
50
|
+
cancelText={resolvedLabels.cancelText}
|
|
38
51
|
onConfirm={updatePassword}
|
|
39
52
|
disabled={!!error || !password}
|
|
40
53
|
>
|
|
41
|
-
<Body size="S">
|
|
42
|
-
<PasswordRepeatInput
|
|
54
|
+
<Body size="S">{resolvedLabels.body}</Body>
|
|
55
|
+
<PasswordRepeatInput
|
|
56
|
+
bind:password
|
|
57
|
+
bind:error
|
|
58
|
+
minLength={passwordMinLength}
|
|
59
|
+
labels={resolvedLabels}
|
|
60
|
+
/>
|
|
43
61
|
</ModalContent>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { MarkdownViewer, notifications } from "@budibase/bbui"
|
|
3
3
|
import type { AgentChat } from "@budibase/types"
|
|
4
4
|
import BBAI from "../../icons/BBAI.svelte"
|
|
5
5
|
import { tick } from "svelte"
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { onMount } from "svelte"
|
|
8
8
|
import { createEventDispatcher } from "svelte"
|
|
9
9
|
import { createAPIClient } from "@budibase/frontend-core"
|
|
10
|
-
import type { UIMessage
|
|
10
|
+
import type { UIMessage } from "ai"
|
|
11
11
|
import { v4 as uuidv4 } from "uuid"
|
|
12
12
|
|
|
13
13
|
export let API = createAPIClient()
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
let observer: MutationObserver
|
|
24
24
|
let textareaElement: HTMLTextAreaElement
|
|
25
25
|
|
|
26
|
-
$: if (chat
|
|
26
|
+
$: if (chat?.messages?.length) {
|
|
27
27
|
scrollToBottom()
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
async function prompt() {
|
|
45
45
|
if (!chat) {
|
|
46
|
-
chat = { title: "", messages: []
|
|
46
|
+
chat = { title: "", messages: [] }
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const userMessage: UIMessage = {
|
|
@@ -52,97 +52,36 @@
|
|
|
52
52
|
parts: [{ type: "text", text: inputValue }],
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const updatedChat = {
|
|
55
|
+
const updatedChat: AgentChat = {
|
|
56
56
|
...chat,
|
|
57
57
|
messages: [...chat.messages, userMessage],
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Update local display immediately with user message
|
|
61
60
|
chat = updatedChat
|
|
62
|
-
|
|
63
|
-
// Ensure we scroll to the new message
|
|
64
61
|
await scrollToBottom()
|
|
65
62
|
|
|
66
63
|
inputValue = ""
|
|
67
64
|
loading = true
|
|
68
65
|
|
|
69
|
-
let streamingText = ""
|
|
70
|
-
let assistantIndex = -1
|
|
71
|
-
let streamCompleted = false
|
|
72
|
-
|
|
73
66
|
try {
|
|
74
|
-
await API.agentChatStream(
|
|
75
|
-
updatedChat,
|
|
76
|
-
workspaceId,
|
|
77
|
-
(chunk: UIMessageChunk) => {
|
|
78
|
-
if (chunk.type === "text-start") {
|
|
79
|
-
const assistantMessage: UIMessage = {
|
|
80
|
-
id: Helpers.uuid(),
|
|
81
|
-
role: "assistant",
|
|
82
|
-
parts: [{ type: "text", text: "", state: "streaming" }],
|
|
83
|
-
}
|
|
84
|
-
chat = {
|
|
85
|
-
...chat,
|
|
86
|
-
messages: [...updatedChat.messages, assistantMessage],
|
|
87
|
-
}
|
|
88
|
-
assistantIndex = chat.messages.length - 1
|
|
89
|
-
scrollToBottom()
|
|
90
|
-
} else if (chunk.type === "text-delta") {
|
|
91
|
-
streamingText += chunk.delta || ""
|
|
92
|
-
if (assistantIndex >= 0) {
|
|
93
|
-
const messages = [...chat.messages]
|
|
94
|
-
const assistant = { ...messages[assistantIndex] }
|
|
95
|
-
const parts = [...assistant.parts]
|
|
96
|
-
const textPart = parts.find(p => p.type === "text")
|
|
97
|
-
if (textPart) {
|
|
98
|
-
textPart.text = streamingText
|
|
99
|
-
}
|
|
100
|
-
assistant.parts = parts
|
|
101
|
-
messages[assistantIndex] = assistant
|
|
102
|
-
chat = { ...chat, messages }
|
|
103
|
-
}
|
|
104
|
-
scrollToBottom()
|
|
105
|
-
} else if (chunk.type === "text-end") {
|
|
106
|
-
loading = false
|
|
107
|
-
streamCompleted = true
|
|
108
|
-
if (assistantIndex >= 0) {
|
|
109
|
-
const messages = [...chat.messages]
|
|
110
|
-
const assistant = { ...messages[assistantIndex] }
|
|
111
|
-
const parts = [...assistant.parts]
|
|
112
|
-
const textPart = parts.find(p => p.type === "text")
|
|
113
|
-
if (textPart) {
|
|
114
|
-
textPart.state = "done"
|
|
115
|
-
}
|
|
116
|
-
assistant.parts = parts
|
|
117
|
-
messages[assistantIndex] = assistant
|
|
118
|
-
chat = { ...chat, messages }
|
|
119
|
-
}
|
|
120
|
-
scrollToBottom()
|
|
121
|
-
} else if (chunk.type === "error") {
|
|
122
|
-
notifications.error(chunk.errorText || "An error occurred")
|
|
123
|
-
loading = false
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
error => {
|
|
127
|
-
console.error("Streaming error:", error)
|
|
128
|
-
notifications.error(error.message)
|
|
129
|
-
loading = false
|
|
130
|
-
}
|
|
131
|
-
)
|
|
67
|
+
const messageStream = await API.agentChatStream(updatedChat, workspaceId)
|
|
132
68
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
69
|
+
for await (const message of messageStream) {
|
|
70
|
+
chat = {
|
|
71
|
+
...updatedChat,
|
|
72
|
+
messages: [...updatedChat.messages, message],
|
|
73
|
+
}
|
|
74
|
+
scrollToBottom()
|
|
138
75
|
}
|
|
76
|
+
|
|
77
|
+
loading = false
|
|
78
|
+
dispatch("chatSaved", { chatId: chat._id || "" })
|
|
139
79
|
} catch (err: any) {
|
|
140
80
|
console.error(err)
|
|
141
81
|
notifications.error(err.message)
|
|
142
82
|
loading = false
|
|
143
83
|
}
|
|
144
84
|
|
|
145
|
-
// Return focus to textarea after the response
|
|
146
85
|
await tick()
|
|
147
86
|
if (textareaElement) {
|
|
148
87
|
textareaElement.focus()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { Select, Multiselect } from "@budibase/bbui"
|
|
3
|
-
import { fetchData } from "@budibase/frontend-core"
|
|
3
|
+
import { fetchData, loadTranslationsByGroup } from "@budibase/frontend-core"
|
|
4
4
|
import { createAPIClient } from "../api"
|
|
5
5
|
|
|
6
6
|
export let API = createAPIClient()
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
$: options = $fetch.rows
|
|
23
23
|
|
|
24
24
|
$: component = multiselect ? Multiselect : Select
|
|
25
|
+
const pickerLabels = loadTranslationsByGroup("picker")
|
|
25
26
|
</script>
|
|
26
27
|
|
|
27
28
|
<div class="user-control">
|
|
@@ -34,5 +35,6 @@
|
|
|
34
35
|
getOptionLabel={option => option.email}
|
|
35
36
|
getOptionValue={option => option._id}
|
|
36
37
|
{disabled}
|
|
38
|
+
searchPlaceholder={pickerLabels.searchPlaceholder}
|
|
37
39
|
/>
|
|
38
40
|
</div>
|
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
export let password: string
|
|
7
7
|
export let error: string
|
|
8
8
|
export let minLength = "12"
|
|
9
|
+
export let labels: any = {}
|
|
9
10
|
|
|
10
11
|
const validatePassword = (value: string | undefined) => {
|
|
11
12
|
if (!value || value.length < parseInt(minLength)) {
|
|
12
|
-
return
|
|
13
|
+
return (
|
|
14
|
+
labels?.minLengthText?.replace("{minLength}", minLength) ||
|
|
15
|
+
`Please enter at least ${minLength} characters. We recommend using machine generated or random passwords.`
|
|
16
|
+
)
|
|
13
17
|
}
|
|
14
18
|
return null
|
|
15
19
|
}
|
|
@@ -38,17 +42,17 @@
|
|
|
38
42
|
|
|
39
43
|
<FancyForm bind:this={passwordForm}>
|
|
40
44
|
<FancyInput
|
|
41
|
-
label="Password"
|
|
45
|
+
label={labels?.passwordLabel ?? "Password"}
|
|
42
46
|
type="password"
|
|
43
47
|
error={firstPasswordError}
|
|
44
48
|
bind:value={$firstPassword}
|
|
45
49
|
/>
|
|
46
50
|
<FancyInput
|
|
47
|
-
label="Repeat password"
|
|
51
|
+
label={labels?.repeatLabel ?? "Repeat password"}
|
|
48
52
|
type="password"
|
|
49
53
|
error={$repeatTouched &&
|
|
50
54
|
$firstPassword !== $repeatPassword &&
|
|
51
|
-
"Passwords must match"}
|
|
55
|
+
(labels?.mismatchText ?? "Passwords must match")}
|
|
52
56
|
bind:value={$repeatPassword}
|
|
53
57
|
/>
|
|
54
58
|
</FancyForm>
|
|
@@ -4,14 +4,24 @@
|
|
|
4
4
|
import type { User, ContextUser } from "@budibase/types"
|
|
5
5
|
import type { APIClient } from "@budibase/frontend-core"
|
|
6
6
|
import { createEventDispatcher } from "svelte"
|
|
7
|
+
import { resolveTranslationGroup } from "@budibase/shared-core"
|
|
7
8
|
|
|
8
9
|
export let user: User | ContextUser | undefined = undefined
|
|
9
10
|
export let API: APIClient
|
|
10
11
|
export let notifySuccess = notifications.success
|
|
11
12
|
export let notifyError = notifications.error
|
|
13
|
+
const DEFAULT_LABELS = resolveTranslationGroup("profileModal")
|
|
14
|
+
type ProfileModalLabels = typeof DEFAULT_LABELS
|
|
15
|
+
|
|
16
|
+
export let labels: Partial<ProfileModalLabels> = {}
|
|
12
17
|
|
|
13
18
|
const dispatch = createEventDispatcher()
|
|
14
19
|
|
|
20
|
+
$: resolvedLabels = {
|
|
21
|
+
...DEFAULT_LABELS,
|
|
22
|
+
...labels,
|
|
23
|
+
} as ProfileModalLabels
|
|
24
|
+
|
|
15
25
|
const values = writable({
|
|
16
26
|
firstName: user?.firstName,
|
|
17
27
|
lastName: user?.lastName,
|
|
@@ -20,20 +30,25 @@
|
|
|
20
30
|
const updateInfo = async () => {
|
|
21
31
|
try {
|
|
22
32
|
await API.updateSelf($values)
|
|
23
|
-
notifySuccess(
|
|
33
|
+
notifySuccess(resolvedLabels.successText)
|
|
24
34
|
dispatch("save")
|
|
25
35
|
} catch (error) {
|
|
26
36
|
console.error(error)
|
|
27
|
-
notifyError(
|
|
37
|
+
notifyError(resolvedLabels.errorText)
|
|
28
38
|
}
|
|
29
39
|
}
|
|
30
40
|
</script>
|
|
31
41
|
|
|
32
|
-
<ModalContent
|
|
42
|
+
<ModalContent
|
|
43
|
+
title={resolvedLabels.title}
|
|
44
|
+
confirmText={resolvedLabels.saveText}
|
|
45
|
+
cancelText={resolvedLabels.cancelText}
|
|
46
|
+
onConfirm={updateInfo}
|
|
47
|
+
>
|
|
33
48
|
<Body size="S">
|
|
34
|
-
|
|
49
|
+
{resolvedLabels.body}
|
|
35
50
|
</Body>
|
|
36
|
-
<Input disabled value={user?.email || ""} label=
|
|
37
|
-
<Input bind:value={$values.firstName} label=
|
|
38
|
-
<Input bind:value={$values.lastName} label=
|
|
51
|
+
<Input disabled value={user?.email || ""} label={resolvedLabels.emailLabel} />
|
|
52
|
+
<Input bind:value={$values.firstName} label={resolvedLabels.firstNameLabel} />
|
|
53
|
+
<Input bind:value={$values.lastName} label={resolvedLabels.lastNameLabel} />
|
|
39
54
|
</ModalContent>
|
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
import { debounce } from "../../../utils/utils"
|
|
5
5
|
import GridPopover from "../overlays/GridPopover.svelte"
|
|
6
6
|
import { OptionColours } from "../../../constants"
|
|
7
|
+
import { loadTranslationsByGroup } from "../../../utils/translationGroups"
|
|
7
8
|
|
|
8
9
|
const { API, cache } = getContext("grid")
|
|
9
10
|
|
|
11
|
+
const pickerLabels = loadTranslationsByGroup("picker")
|
|
12
|
+
|
|
10
13
|
export let value = []
|
|
11
14
|
export let api
|
|
12
15
|
export let readonly
|
|
@@ -40,6 +43,10 @@
|
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
$: relationshipSearchPlaceholder = primaryDisplay
|
|
47
|
+
? pickerLabels.searchByFieldPlaceholder.replace("{field}", primaryDisplay)
|
|
48
|
+
: pickerLabels.searchPlaceholder
|
|
49
|
+
|
|
43
50
|
$: relationFields = fieldValue?.reduce((acc, f) => {
|
|
44
51
|
const fields = {}
|
|
45
52
|
for (const [column] of Object.entries(schema?.columns || {}).filter(
|
|
@@ -314,7 +321,7 @@
|
|
|
314
321
|
quiet
|
|
315
322
|
type="text"
|
|
316
323
|
bind:value={searchString}
|
|
317
|
-
placeholder={
|
|
324
|
+
placeholder={relationshipSearchPlaceholder}
|
|
318
325
|
/>
|
|
319
326
|
</div>
|
|
320
327
|
{#if searching}
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getContext } from "svelte"
|
|
2
|
+
import { get, type Readable } from "svelte/store"
|
|
3
|
+
import {
|
|
4
|
+
resolveTranslationGroup,
|
|
5
|
+
resolveWorkspaceTranslations,
|
|
6
|
+
type TranslationCategory,
|
|
7
|
+
type TranslationOverrides,
|
|
8
|
+
} from "@budibase/shared-core"
|
|
9
|
+
|
|
10
|
+
type TranslationStoreValue = {
|
|
11
|
+
translationOverrides?: TranslationOverrides | null
|
|
12
|
+
application?: { translationOverrides?: TranslationOverrides | null } | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface LoadTranslationsByGroupOptions {
|
|
16
|
+
appStore?: Readable<TranslationStoreValue> | null
|
|
17
|
+
overrides?: TranslationOverrides | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface SDKContext {
|
|
21
|
+
appStore?: Readable<TranslationStoreValue> | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const loadTranslationsByGroup = (
|
|
25
|
+
category: TranslationCategory,
|
|
26
|
+
options?: LoadTranslationsByGroupOptions
|
|
27
|
+
): Record<string, string> => {
|
|
28
|
+
const sdk = getContext<SDKContext | undefined>("sdk")
|
|
29
|
+
const appStore = options?.appStore ?? sdk?.appStore
|
|
30
|
+
const storeValue = appStore ? get(appStore) : undefined
|
|
31
|
+
|
|
32
|
+
const overrides = options?.overrides
|
|
33
|
+
? resolveWorkspaceTranslations(options.overrides)
|
|
34
|
+
: resolveWorkspaceTranslations(
|
|
35
|
+
storeValue?.translationOverrides ??
|
|
36
|
+
storeValue?.application?.translationOverrides
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return resolveTranslationGroup(category, overrides)
|
|
40
|
+
}
|
package/src/utils/utils.ts
CHANGED
|
@@ -422,3 +422,31 @@ export function parseFilter(filter: UISearchFilter) {
|
|
|
422
422
|
|
|
423
423
|
return update
|
|
424
424
|
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Utility to transform a SSE stream into a JSON stream
|
|
428
|
+
* @param {TransformStream<string, T>} stream
|
|
429
|
+
* @returns {TransformStream<T, T>}
|
|
430
|
+
*/
|
|
431
|
+
|
|
432
|
+
export function createSseToJsonTransformStream<T>(): TransformStream<
|
|
433
|
+
string,
|
|
434
|
+
T
|
|
435
|
+
> {
|
|
436
|
+
let buffer = ""
|
|
437
|
+
return new TransformStream({
|
|
438
|
+
transform(chunk, controller) {
|
|
439
|
+
buffer += chunk
|
|
440
|
+
const lines = buffer.split("\n")
|
|
441
|
+
buffer = lines.pop() || ""
|
|
442
|
+
for (const line of lines) {
|
|
443
|
+
if (line.startsWith("data: ")) {
|
|
444
|
+
const data = line.slice(6).trim()
|
|
445
|
+
if (data && data !== "[DONE]") {
|
|
446
|
+
controller.enqueue(JSON.parse(data))
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
})
|
|
452
|
+
}
|