@budibase/frontend-core 3.28.3 → 3.30.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 +2 -2
- package/src/api/agents.ts +16 -0
- package/src/api/chatApps.ts +19 -0
- package/src/components/Chatbox/ChatConversationPanel.svelte +442 -0
- package/src/components/Chatbox/ChatNavigationPanel.svelte +203 -0
- package/src/components/Chatbox/index.svelte +74 -13
- package/src/components/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.30.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": "^3.2.4"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "58456cc695d2c71a354bcf5c064943b247ad4969"
|
|
28
28
|
}
|
package/src/api/agents.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
FetchAgentsResponse,
|
|
8
8
|
SyncAgentDiscordCommandsRequest,
|
|
9
9
|
SyncAgentDiscordCommandsResponse,
|
|
10
|
+
ToggleAgentDiscordRequest,
|
|
11
|
+
ToggleAgentDiscordResponse,
|
|
10
12
|
ToolMetadata,
|
|
11
13
|
UpdateAgentRequest,
|
|
12
14
|
UpdateAgentResponse,
|
|
@@ -34,6 +36,10 @@ export interface AgentEndpoints {
|
|
|
34
36
|
agentId: string,
|
|
35
37
|
body?: SyncAgentDiscordCommandsRequest
|
|
36
38
|
) => Promise<SyncAgentDiscordCommandsResponse>
|
|
39
|
+
toggleAgentDiscordDeployment: (
|
|
40
|
+
agentId: string,
|
|
41
|
+
enabled: boolean
|
|
42
|
+
) => Promise<ToggleAgentDiscordResponse>
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
|
|
@@ -108,4 +114,14 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
|
|
|
108
114
|
body,
|
|
109
115
|
})
|
|
110
116
|
},
|
|
117
|
+
|
|
118
|
+
toggleAgentDiscordDeployment: async (agentId: string, enabled: boolean) => {
|
|
119
|
+
return await API.post<
|
|
120
|
+
ToggleAgentDiscordRequest,
|
|
121
|
+
ToggleAgentDiscordResponse
|
|
122
|
+
>({
|
|
123
|
+
url: `/api/agent/${agentId}/discord/toggle`,
|
|
124
|
+
body: { enabled },
|
|
125
|
+
})
|
|
126
|
+
},
|
|
111
127
|
})
|
package/src/api/chatApps.ts
CHANGED
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
ChatConversation,
|
|
4
4
|
ChatConversationRequest,
|
|
5
5
|
CreateChatConversationRequest,
|
|
6
|
+
FetchChatAppAgentsResponse,
|
|
6
7
|
ChatApp,
|
|
7
8
|
ChatAppAgent,
|
|
8
9
|
FetchAgentHistoryResponse,
|
|
10
|
+
FetchPublishedChatAppsResponse,
|
|
9
11
|
UpdateChatAppRequest,
|
|
10
12
|
AgentMessageMetadata,
|
|
11
13
|
} from "@budibase/types"
|
|
@@ -27,6 +29,7 @@ export interface ChatAppEndpoints {
|
|
|
27
29
|
chatAppId: string,
|
|
28
30
|
chatConversationId: string
|
|
29
31
|
) => Promise<ChatConversation>
|
|
32
|
+
fetchChatAppAgents: (chatAppId: string) => Promise<FetchChatAppAgentsResponse>
|
|
30
33
|
fetchChatHistory: (chatAppId: string) => Promise<FetchAgentHistoryResponse>
|
|
31
34
|
fetchChatApp: (workspaceId?: string) => Promise<ChatApp | null>
|
|
32
35
|
setChatAppAgent: (chatAppId: string, agentId: string) => Promise<ChatAppAgent>
|
|
@@ -35,6 +38,9 @@ export interface ChatAppEndpoints {
|
|
|
35
38
|
workspaceId?: string
|
|
36
39
|
) => Promise<ChatConversation>
|
|
37
40
|
updateChatApp: (chatApp: UpdateChatAppRequest) => Promise<ChatApp>
|
|
41
|
+
getPublishedChatApps: () => Promise<
|
|
42
|
+
FetchPublishedChatAppsResponse["chatApps"]
|
|
43
|
+
>
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
const throwOnErrorChunk = () =>
|
|
@@ -112,6 +118,12 @@ export const buildChatAppEndpoints = (
|
|
|
112
118
|
})
|
|
113
119
|
},
|
|
114
120
|
|
|
121
|
+
fetchChatAppAgents: async (chatAppId: string) => {
|
|
122
|
+
return await API.get({
|
|
123
|
+
url: `/api/chatapps/${chatAppId}/agents`,
|
|
124
|
+
})
|
|
125
|
+
},
|
|
126
|
+
|
|
115
127
|
fetchChatHistory: async (chatAppId: string) => {
|
|
116
128
|
return await API.get({
|
|
117
129
|
url: `/api/chatapps/${chatAppId}/conversations`,
|
|
@@ -189,4 +201,11 @@ export const buildChatAppEndpoints = (
|
|
|
189
201
|
body: chatApp as any,
|
|
190
202
|
})
|
|
191
203
|
},
|
|
204
|
+
|
|
205
|
+
getPublishedChatApps: async () => {
|
|
206
|
+
const response = await API.get<FetchPublishedChatAppsResponse>({
|
|
207
|
+
url: "/api/client/chatapps",
|
|
208
|
+
})
|
|
209
|
+
return response.chatApps
|
|
210
|
+
},
|
|
192
211
|
})
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createEventDispatcher, onMount } from "svelte"
|
|
3
|
+
import { Body, Button, Icon, ProgressCircle } from "@budibase/bbui"
|
|
4
|
+
import type { ChatConversation, DraftChatConversation } from "@budibase/types"
|
|
5
|
+
import Chatbox from "./index.svelte"
|
|
6
|
+
|
|
7
|
+
type ChatConversationLike = ChatConversation | DraftChatConversation
|
|
8
|
+
|
|
9
|
+
type EnabledAgentListItem = {
|
|
10
|
+
agentId: string
|
|
11
|
+
name?: string
|
|
12
|
+
icon?: string
|
|
13
|
+
iconColor?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export let selectedAgentId: string | null = null
|
|
17
|
+
export let selectedAgentName: string = ""
|
|
18
|
+
export let enabledAgentList: EnabledAgentListItem[] = []
|
|
19
|
+
export let conversationStarters: { prompt: string }[] = []
|
|
20
|
+
export let isAgentKnown: boolean = true
|
|
21
|
+
export let isAgentLive: boolean = true
|
|
22
|
+
|
|
23
|
+
export let chat: ChatConversationLike
|
|
24
|
+
export let loading: boolean = false
|
|
25
|
+
export let deletingChat: boolean = false
|
|
26
|
+
export let workspaceId: string
|
|
27
|
+
export let initialPrompt: string = ""
|
|
28
|
+
export let userName: string = ""
|
|
29
|
+
|
|
30
|
+
const dispatch = createEventDispatcher<{
|
|
31
|
+
chatSaved: { chatId?: string; chat: ChatConversationLike }
|
|
32
|
+
deleteChat: undefined
|
|
33
|
+
agentSelected: { agentId: string }
|
|
34
|
+
startChat: { agentId: string; prompt: string }
|
|
35
|
+
}>()
|
|
36
|
+
|
|
37
|
+
const hasChatId = (value: ChatConversationLike) =>
|
|
38
|
+
value && "_id" in value && Boolean(value._id)
|
|
39
|
+
|
|
40
|
+
const buildGreeting = (name: string) => {
|
|
41
|
+
const currentDate = new Date()
|
|
42
|
+
const suffix = name ? `, ${name}` : ""
|
|
43
|
+
|
|
44
|
+
if (currentDate.getDay() === 1) {
|
|
45
|
+
return `Happy Monday${suffix}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const isMorning = currentDate.getHours() < 12
|
|
49
|
+
return `${isMorning ? "Good Morning" : "Good Afternoon"}${suffix}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let readOnlyReason: "disabled" | "deleted" | "offline" | undefined
|
|
53
|
+
|
|
54
|
+
let draftPrompt = ""
|
|
55
|
+
let draftPromptInput: HTMLInputElement | null = null
|
|
56
|
+
|
|
57
|
+
$: greetingText = buildGreeting(userName)
|
|
58
|
+
|
|
59
|
+
$: visibleAgentList = enabledAgentList.slice(0, 3)
|
|
60
|
+
$: hasEnabledAgents = enabledAgentList.length > 0
|
|
61
|
+
|
|
62
|
+
const getAgentStatus = (
|
|
63
|
+
agentId: string | null,
|
|
64
|
+
agents: EnabledAgentListItem[],
|
|
65
|
+
agentKnown: boolean,
|
|
66
|
+
agentLive: boolean
|
|
67
|
+
): {
|
|
68
|
+
isAgentEnabled: boolean
|
|
69
|
+
readOnlyReason: "disabled" | "deleted" | "offline" | undefined
|
|
70
|
+
} => {
|
|
71
|
+
if (!agentId) {
|
|
72
|
+
return { isAgentEnabled: false, readOnlyReason: undefined }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!agentKnown) {
|
|
76
|
+
return { isAgentEnabled: false, readOnlyReason: "deleted" }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!agentLive) {
|
|
80
|
+
return { isAgentEnabled: false, readOnlyReason: "offline" }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isAgentEnabled = agents.some(agent => agent.agentId === agentId)
|
|
84
|
+
return {
|
|
85
|
+
isAgentEnabled,
|
|
86
|
+
readOnlyReason: isAgentEnabled ? undefined : "disabled",
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
$: ({ readOnlyReason } = getAgentStatus(
|
|
91
|
+
selectedAgentId,
|
|
92
|
+
enabledAgentList,
|
|
93
|
+
isAgentKnown,
|
|
94
|
+
isAgentLive
|
|
95
|
+
))
|
|
96
|
+
|
|
97
|
+
const deleteChat = () => {
|
|
98
|
+
dispatch("deleteChat")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const selectAgent = (agentId: string) => {
|
|
102
|
+
dispatch("agentSelected", { agentId })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const startChat = () => {
|
|
106
|
+
const prompt = draftPrompt.trim()
|
|
107
|
+
if (!prompt) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const agentId = enabledAgentList[0]?.agentId
|
|
112
|
+
if (!agentId) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
dispatch("startChat", { agentId, prompt })
|
|
117
|
+
draftPrompt = ""
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const handlePromptKeyDown = (event: KeyboardEvent) => {
|
|
121
|
+
if (event.key !== "Enter") {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
event.preventDefault()
|
|
126
|
+
startChat()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
onMount(() => {
|
|
130
|
+
draftPromptInput?.focus()
|
|
131
|
+
})
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<div class="chat-wrapper">
|
|
135
|
+
{#if selectedAgentId}
|
|
136
|
+
<div class="chat-header">
|
|
137
|
+
<div class="chat-header-agent">
|
|
138
|
+
<Body size="S">
|
|
139
|
+
{selectedAgentName || "Unknown agent"}
|
|
140
|
+
</Body>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
{#if hasChatId(chat)}
|
|
144
|
+
<Button
|
|
145
|
+
quiet
|
|
146
|
+
warning
|
|
147
|
+
disabled={deletingChat || loading}
|
|
148
|
+
on:click={deleteChat}
|
|
149
|
+
>
|
|
150
|
+
<span class="delete-button-content">
|
|
151
|
+
{#if deletingChat}
|
|
152
|
+
<ProgressCircle size="S" />
|
|
153
|
+
Deleting...
|
|
154
|
+
{:else}
|
|
155
|
+
<Icon name="trash" size="S" />
|
|
156
|
+
Delete chat
|
|
157
|
+
{/if}
|
|
158
|
+
</span>
|
|
159
|
+
</Button>
|
|
160
|
+
{/if}
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<Chatbox
|
|
164
|
+
bind:chat
|
|
165
|
+
{workspaceId}
|
|
166
|
+
{conversationStarters}
|
|
167
|
+
{initialPrompt}
|
|
168
|
+
readOnly={Boolean(readOnlyReason)}
|
|
169
|
+
{readOnlyReason}
|
|
170
|
+
onchatsaved={event => dispatch("chatSaved", event.detail)}
|
|
171
|
+
/>
|
|
172
|
+
{:else}
|
|
173
|
+
<div class="chat-empty">
|
|
174
|
+
<div class="chat-empty-greeting">
|
|
175
|
+
<Body size="XL" weight="600" serif>
|
|
176
|
+
{greetingText}
|
|
177
|
+
</Body>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="chat-empty-input" role="presentation">
|
|
180
|
+
<input
|
|
181
|
+
class="chat-empty-input-field"
|
|
182
|
+
type="text"
|
|
183
|
+
placeholder="How can I help you today?"
|
|
184
|
+
bind:this={draftPromptInput}
|
|
185
|
+
bind:value={draftPrompt}
|
|
186
|
+
on:keydown={handlePromptKeyDown}
|
|
187
|
+
disabled={!hasEnabledAgents}
|
|
188
|
+
/>
|
|
189
|
+
<button
|
|
190
|
+
class="chat-empty-input-action"
|
|
191
|
+
type="button"
|
|
192
|
+
on:click={startChat}
|
|
193
|
+
disabled={!hasEnabledAgents}
|
|
194
|
+
aria-label="Start chat"
|
|
195
|
+
>
|
|
196
|
+
<Icon name="arrow-up" size="S" />
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="chat-empty-grid">
|
|
200
|
+
{#if visibleAgentList.length}
|
|
201
|
+
{#each visibleAgentList as agent (agent.agentId)}
|
|
202
|
+
<button
|
|
203
|
+
class="chat-empty-card"
|
|
204
|
+
class:chat-empty-card-single={visibleAgentList.length === 1}
|
|
205
|
+
on:click={() => selectAgent(agent.agentId)}
|
|
206
|
+
style={`--agent-icon-color:${
|
|
207
|
+
agent.iconColor ||
|
|
208
|
+
"var(--spectrum-semantic-cta-color-background-default)"
|
|
209
|
+
};`}
|
|
210
|
+
>
|
|
211
|
+
<div class="chat-empty-card-head">
|
|
212
|
+
<div class="chat-empty-card-icon">
|
|
213
|
+
<Icon
|
|
214
|
+
name={agent.icon || "SideKick"}
|
|
215
|
+
size="S"
|
|
216
|
+
color="var(--agent-icon-color)"
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
<Body size="S" weight="500">
|
|
220
|
+
{agent.name || "Unknown agent"}
|
|
221
|
+
</Body>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="chat-empty-card-subtitle">
|
|
224
|
+
<Body size="XS" color="var(--spectrum-global-color-gray-600)">
|
|
225
|
+
Start a chat with this agent.
|
|
226
|
+
</Body>
|
|
227
|
+
</div>
|
|
228
|
+
</button>
|
|
229
|
+
{/each}
|
|
230
|
+
{:else}
|
|
231
|
+
<Body size="S" color="var(--spectrum-global-color-gray-500)">
|
|
232
|
+
No enabled agents
|
|
233
|
+
</Body>
|
|
234
|
+
{/if}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
{/if}
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<style>
|
|
241
|
+
.chat-wrapper {
|
|
242
|
+
flex: 1 1 auto;
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
padding: 0 32px 32px 32px;
|
|
246
|
+
box-sizing: border-box;
|
|
247
|
+
min-width: 0;
|
|
248
|
+
min-height: 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.chat-header {
|
|
252
|
+
width: 100%;
|
|
253
|
+
padding: var(--spacing-l) 0 var(--spacing-l);
|
|
254
|
+
display: flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
justify-content: space-between;
|
|
257
|
+
gap: var(--spacing-m);
|
|
258
|
+
border-bottom: var(--border-dark);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.chat-header-agent {
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.chat-header-agent :global(p) {
|
|
267
|
+
font-size: 14px;
|
|
268
|
+
line-height: 17px;
|
|
269
|
+
letter-spacing: 0;
|
|
270
|
+
font-weight: 400;
|
|
271
|
+
color: var(--spectrum-alias-text-color);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.delete-button-content {
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
gap: var(--spacing-xs);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.chat-empty {
|
|
281
|
+
flex: 1 1 auto;
|
|
282
|
+
display: flex;
|
|
283
|
+
flex-direction: column;
|
|
284
|
+
justify-content: center;
|
|
285
|
+
align-items: center;
|
|
286
|
+
gap: 32px;
|
|
287
|
+
padding: var(--spacing-xxl);
|
|
288
|
+
text-align: center;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.chat-empty-greeting :global(p) {
|
|
292
|
+
color: var(--spectrum-alias-text-color);
|
|
293
|
+
font-size: 28px;
|
|
294
|
+
line-height: 34px;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.chat-empty-input {
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
gap: var(--spacing-m);
|
|
301
|
+
width: 600px;
|
|
302
|
+
padding: 10px;
|
|
303
|
+
padding-left: 20px;
|
|
304
|
+
border-radius: 999px;
|
|
305
|
+
background: var(--spectrum-alias-background-color-secondary);
|
|
306
|
+
color: var(--spectrum-alias-text-color);
|
|
307
|
+
border: 1px solid var(--spectrum-alias-border-color);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.chat-empty-input-field {
|
|
311
|
+
flex: 1;
|
|
312
|
+
font-size: 16px;
|
|
313
|
+
color: var(--spectrum-alias-text-color);
|
|
314
|
+
background: transparent;
|
|
315
|
+
border: none;
|
|
316
|
+
outline: none;
|
|
317
|
+
font: inherit;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.chat-empty-input-field::placeholder {
|
|
321
|
+
color: var(--spectrum-alias-text-color-disabled);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.chat-empty-input-action {
|
|
325
|
+
width: 32px;
|
|
326
|
+
height: 32px;
|
|
327
|
+
border-radius: 999px;
|
|
328
|
+
background: var(--spectrum-semantic-cta-color-background-default);
|
|
329
|
+
color: var(--spectrum-global-color-gray-50);
|
|
330
|
+
display: inline-flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
justify-content: center;
|
|
333
|
+
border: none;
|
|
334
|
+
cursor: pointer;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.chat-empty-input-action:disabled,
|
|
338
|
+
.chat-empty-input-field:disabled {
|
|
339
|
+
opacity: 0.5;
|
|
340
|
+
cursor: not-allowed;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.chat-empty-input-action:hover {
|
|
344
|
+
background: var(--spectrum-semantic-cta-color-background-hover);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.chat-empty-grid {
|
|
348
|
+
display: flex;
|
|
349
|
+
flex-direction: row;
|
|
350
|
+
gap: 16px;
|
|
351
|
+
width: min(720px, 100%);
|
|
352
|
+
align-items: center;
|
|
353
|
+
justify-content: center;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.chat-empty-card {
|
|
357
|
+
border: 1px solid var(--spectrum-alias-border-color);
|
|
358
|
+
width: 240px;
|
|
359
|
+
border-radius: 16px;
|
|
360
|
+
padding: 0;
|
|
361
|
+
background: var(--spectrum-alias-background-color-primary);
|
|
362
|
+
color: var(--spectrum-alias-text-color);
|
|
363
|
+
font: inherit;
|
|
364
|
+
cursor: pointer;
|
|
365
|
+
text-align: left;
|
|
366
|
+
overflow: hidden;
|
|
367
|
+
transform: translateY(var(--card-offset, 0px))
|
|
368
|
+
rotate(var(--card-rotation, 0deg));
|
|
369
|
+
transition:
|
|
370
|
+
border-color 150ms ease,
|
|
371
|
+
transform 150ms ease;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.chat-empty-card:hover {
|
|
375
|
+
border-color: var(--spectrum-alias-border-color-hover);
|
|
376
|
+
transform: translateY(calc(var(--card-offset, 0px) - 3px))
|
|
377
|
+
rotate(var(--card-rotation, 0deg));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.chat-empty-card:first-child {
|
|
381
|
+
--card-rotation: -6deg;
|
|
382
|
+
--card-offset: 12px;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.chat-empty-card:last-child {
|
|
386
|
+
--card-rotation: 6deg;
|
|
387
|
+
--card-offset: 12px;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.chat-empty-card.chat-empty-card-single {
|
|
391
|
+
--card-rotation: 0deg;
|
|
392
|
+
--card-offset: 0px;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.chat-empty-card-head {
|
|
396
|
+
display: flex;
|
|
397
|
+
align-items: center;
|
|
398
|
+
gap: var(--spacing-s);
|
|
399
|
+
padding: var(--spacing-m);
|
|
400
|
+
background-color: var(--spectrum-alias-background-color-secondary);
|
|
401
|
+
color: var(--spectrum-alias-text-color);
|
|
402
|
+
border-bottom: 1px solid var(--spectrum-alias-border-color);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.chat-empty-card-icon {
|
|
406
|
+
width: 28px;
|
|
407
|
+
height: 28px;
|
|
408
|
+
border-radius: 8px;
|
|
409
|
+
display: inline-flex;
|
|
410
|
+
align-items: center;
|
|
411
|
+
justify-content: center;
|
|
412
|
+
background: transparent;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.chat-empty-card-subtitle {
|
|
416
|
+
padding: var(--spacing-m);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
@media (max-width: 1000px) {
|
|
420
|
+
.chat-wrapper {
|
|
421
|
+
padding: 0 var(--spacing-l) var(--spacing-l);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.chat-empty-input {
|
|
425
|
+
width: min(600px, 100%);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.chat-empty-grid {
|
|
429
|
+
flex-direction: column;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.chat-empty-card {
|
|
433
|
+
width: min(360px, 100%);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.chat-empty-card:first-child,
|
|
437
|
+
.chat-empty-card:last-child {
|
|
438
|
+
--card-rotation: 0deg;
|
|
439
|
+
--card-offset: 0px;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
</style>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Body, Icon } from "@budibase/bbui"
|
|
3
|
+
import { createEventDispatcher } from "svelte"
|
|
4
|
+
|
|
5
|
+
type EnabledAgentListItem = {
|
|
6
|
+
agentId: string
|
|
7
|
+
name?: string
|
|
8
|
+
isDefault?: boolean
|
|
9
|
+
icon?: string
|
|
10
|
+
iconColor?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type ConversationListItem = {
|
|
14
|
+
_id?: string
|
|
15
|
+
title?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export let enabledAgentList: EnabledAgentListItem[] = []
|
|
19
|
+
export let conversationHistory: ConversationListItem[] = []
|
|
20
|
+
export let selectedConversationId: string | undefined
|
|
21
|
+
|
|
22
|
+
$: defaultAgent =
|
|
23
|
+
enabledAgentList.find(agent => agent.isDefault) || enabledAgentList[0]
|
|
24
|
+
|
|
25
|
+
const dispatch = createEventDispatcher<{
|
|
26
|
+
agentSelected: { agentId: string }
|
|
27
|
+
conversationSelected: { conversationId: string }
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const selectAgent = (agentId: string) => {
|
|
31
|
+
dispatch("agentSelected", { agentId })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const selectConversation = (conversationId: string) => {
|
|
35
|
+
dispatch("conversationSelected", { conversationId })
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<div class="chat-nav-shell">
|
|
40
|
+
<div class="chat-nav-content">
|
|
41
|
+
{#if defaultAgent?.agentId}
|
|
42
|
+
<div class="list-section">
|
|
43
|
+
<button
|
|
44
|
+
class="new-chat"
|
|
45
|
+
on:click={() => selectAgent(defaultAgent.agentId)}
|
|
46
|
+
>
|
|
47
|
+
<span class="new-chat-icon">
|
|
48
|
+
<Icon name="plus" size="S" />
|
|
49
|
+
</span>
|
|
50
|
+
<span class="new-chat-label">New chat</span>
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
{/if}
|
|
54
|
+
|
|
55
|
+
<div class="list-section">
|
|
56
|
+
<div class="list-title">Agents</div>
|
|
57
|
+
{#if enabledAgentList.length}
|
|
58
|
+
{#each enabledAgentList as agent (agent.agentId)}
|
|
59
|
+
<button
|
|
60
|
+
class="list-item list-item-button"
|
|
61
|
+
on:click={() => selectAgent(agent.agentId)}
|
|
62
|
+
>
|
|
63
|
+
<span class="list-item-icon">
|
|
64
|
+
<Icon name={agent.icon || "robot"} size="S" />
|
|
65
|
+
</span>
|
|
66
|
+
{agent.name}
|
|
67
|
+
</button>
|
|
68
|
+
{/each}
|
|
69
|
+
{:else}
|
|
70
|
+
<Body size="XS" color="var(--spectrum-global-color-gray-500)">
|
|
71
|
+
No agents
|
|
72
|
+
</Body>
|
|
73
|
+
{/if}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div class="list-section">
|
|
77
|
+
<div class="list-title">Recent Chats</div>
|
|
78
|
+
{#if conversationHistory.length}
|
|
79
|
+
{#each conversationHistory as conversation}
|
|
80
|
+
{#if conversation._id}
|
|
81
|
+
<button
|
|
82
|
+
class="list-item list-item-button"
|
|
83
|
+
class:selected={selectedConversationId === conversation._id}
|
|
84
|
+
on:click={() => selectConversation(conversation._id!)}
|
|
85
|
+
>
|
|
86
|
+
{conversation.title || "Untitled Chat"}
|
|
87
|
+
</button>
|
|
88
|
+
{/if}
|
|
89
|
+
{/each}
|
|
90
|
+
{:else}
|
|
91
|
+
<Body size="XS" color="var(--spectrum-global-color-gray-500)">
|
|
92
|
+
No recent chats
|
|
93
|
+
</Body>
|
|
94
|
+
{/if}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<style>
|
|
100
|
+
.chat-nav-shell {
|
|
101
|
+
display: flex;
|
|
102
|
+
width: 260px;
|
|
103
|
+
min-width: 260px;
|
|
104
|
+
border-right: var(--border-light);
|
|
105
|
+
background: transparent;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.chat-nav-content {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
gap: var(--spacing-xl);
|
|
112
|
+
width: 100%;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.list-section {
|
|
116
|
+
padding: var(--spacing-m);
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-direction: column;
|
|
119
|
+
gap: var(--spacing-xxs);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.list-section + .list-section {
|
|
123
|
+
padding-top: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.new-chat {
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
gap: var(--spacing-s);
|
|
130
|
+
background: transparent;
|
|
131
|
+
border: none;
|
|
132
|
+
padding: var(--spacing-xs) 0;
|
|
133
|
+
font: inherit;
|
|
134
|
+
color: var(--spectrum-global-color-gray-700);
|
|
135
|
+
text-align: left;
|
|
136
|
+
width: 100%;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.new-chat-icon {
|
|
141
|
+
display: inline-flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
width: 28px;
|
|
145
|
+
height: 28px;
|
|
146
|
+
border-radius: 50%;
|
|
147
|
+
background: var(--spectrum-semantic-cta-color-background-default);
|
|
148
|
+
color: var(--spectrum-global-color-gray-50);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.new-chat-label {
|
|
152
|
+
font-size: 14px;
|
|
153
|
+
color: var(--spectrum-global-color-gray-800);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.new-chat:hover .new-chat-icon {
|
|
157
|
+
background: var(--spectrum-semantic-cta-color-background-hover);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.list-item {
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
gap: var(--spacing-s);
|
|
164
|
+
background: transparent;
|
|
165
|
+
border: none;
|
|
166
|
+
padding: var(--spacing-xs) 0;
|
|
167
|
+
font: inherit;
|
|
168
|
+
color: var(--spectrum-global-color-gray-700);
|
|
169
|
+
text-align: left;
|
|
170
|
+
width: 100%;
|
|
171
|
+
white-space: nowrap;
|
|
172
|
+
overflow: hidden;
|
|
173
|
+
text-overflow: ellipsis;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.list-item-icon {
|
|
177
|
+
display: inline-flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
justify-content: center;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.list-item-button {
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.list-item-button:hover {
|
|
187
|
+
color: var(--spectrum-global-color-gray-900);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.list-item.selected {
|
|
191
|
+
color: var(--spectrum-global-color-gray-900);
|
|
192
|
+
font-weight: 600;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.list-title {
|
|
196
|
+
font-size: 14px;
|
|
197
|
+
line-height: 17px;
|
|
198
|
+
letter-spacing: 0;
|
|
199
|
+
color: var(--spectrum-global-color-gray-600);
|
|
200
|
+
font-weight: 400;
|
|
201
|
+
margin-bottom: var(--spacing-xs);
|
|
202
|
+
}
|
|
203
|
+
</style>
|
|
@@ -221,6 +221,7 @@
|
|
|
221
221
|
let isBusy = $derived(
|
|
222
222
|
chatInstance.status === "streaming" || chatInstance.status === "submitted"
|
|
223
223
|
)
|
|
224
|
+
let canStart = $derived(inputValue.trim().length > 0)
|
|
224
225
|
let hasMessages = $derived(Boolean(messages?.length))
|
|
225
226
|
let showConversationStarters = $derived(
|
|
226
227
|
!isBusy &&
|
|
@@ -372,6 +373,14 @@
|
|
|
372
373
|
chatInstance.sendMessage({ text })
|
|
373
374
|
}
|
|
374
375
|
|
|
376
|
+
const handlePromptAction = async () => {
|
|
377
|
+
if (isBusy) {
|
|
378
|
+
await chatInstance.stop()
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
await sendMessage()
|
|
382
|
+
}
|
|
383
|
+
|
|
375
384
|
const toggleTool = (toolId: string) => {
|
|
376
385
|
expandedTools = { ...expandedTools, [toolId]: !expandedTools[toolId] }
|
|
377
386
|
}
|
|
@@ -632,14 +641,30 @@
|
|
|
632
641
|
</div>
|
|
633
642
|
{:else}
|
|
634
643
|
<div class="input-wrapper">
|
|
635
|
-
<
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
644
|
+
<div class="input-container">
|
|
645
|
+
<textarea
|
|
646
|
+
bind:value={inputValue}
|
|
647
|
+
bind:this={textareaElement}
|
|
648
|
+
class="input spectrum-Textfield-input"
|
|
649
|
+
onkeydown={handleKeyDown}
|
|
650
|
+
placeholder="Ask..."
|
|
651
|
+
disabled={isBusy}
|
|
652
|
+
></textarea>
|
|
653
|
+
<button
|
|
654
|
+
type="button"
|
|
655
|
+
class="prompt-action"
|
|
656
|
+
class:running={isBusy}
|
|
657
|
+
onclick={handlePromptAction}
|
|
658
|
+
aria-label={isBusy ? "Pause response" : "Start response"}
|
|
659
|
+
disabled={!isBusy && !canStart}
|
|
660
|
+
>
|
|
661
|
+
{#if isBusy}
|
|
662
|
+
<Icon name="stop" size="M" weight="fill" color="#ffffff" />
|
|
663
|
+
{:else}
|
|
664
|
+
<Icon name="arrow-up" size="M" weight="bold" color="#111111" />
|
|
665
|
+
{/if}
|
|
666
|
+
</button>
|
|
667
|
+
</div>
|
|
643
668
|
</div>
|
|
644
669
|
{/if}
|
|
645
670
|
</div>
|
|
@@ -733,7 +758,7 @@
|
|
|
733
758
|
.message.user {
|
|
734
759
|
border-radius: 8px;
|
|
735
760
|
align-self: flex-end;
|
|
736
|
-
background-color:
|
|
761
|
+
background-color: var(--spectrum-alias-background-color-secondary);
|
|
737
762
|
font-size: 14px;
|
|
738
763
|
color: var(--spectrum-global-color-gray-800);
|
|
739
764
|
}
|
|
@@ -767,29 +792,65 @@
|
|
|
767
792
|
text-align: center;
|
|
768
793
|
}
|
|
769
794
|
|
|
795
|
+
.input-container {
|
|
796
|
+
position: relative;
|
|
797
|
+
width: 100%;
|
|
798
|
+
}
|
|
799
|
+
|
|
770
800
|
.input {
|
|
771
801
|
width: 100%;
|
|
772
|
-
height:
|
|
802
|
+
height: 80px;
|
|
773
803
|
top: 0;
|
|
774
804
|
resize: none;
|
|
775
805
|
padding: 20px;
|
|
776
806
|
font-size: 16px;
|
|
777
807
|
background-color: var(--spectrum-global-color-gray-200);
|
|
778
|
-
color: var(--
|
|
808
|
+
color: var(--spectrum-alias-text-color);
|
|
779
809
|
border-radius: 10px;
|
|
780
810
|
border: 1px solid var(--spectrum-global-color-gray-300) !important;
|
|
781
811
|
outline: none;
|
|
782
|
-
min-height:
|
|
812
|
+
min-height: 80px;
|
|
783
813
|
}
|
|
784
814
|
|
|
785
815
|
.input:focus {
|
|
786
|
-
border: 1px solid
|
|
816
|
+
border: 1px solid var(--spectrum-alias-border-color-mouse-focus) !important;
|
|
787
817
|
}
|
|
788
818
|
|
|
789
819
|
.input::placeholder {
|
|
790
820
|
color: var(--spectrum-global-color-gray-600);
|
|
791
821
|
}
|
|
792
822
|
|
|
823
|
+
.prompt-action {
|
|
824
|
+
position: absolute;
|
|
825
|
+
right: 10px;
|
|
826
|
+
bottom: 10px;
|
|
827
|
+
width: 24px;
|
|
828
|
+
height: 24px;
|
|
829
|
+
min-width: 24px;
|
|
830
|
+
padding: 0;
|
|
831
|
+
border: none;
|
|
832
|
+
border-radius: 999px;
|
|
833
|
+
background: #f2f2f2;
|
|
834
|
+
cursor: pointer;
|
|
835
|
+
display: inline-flex;
|
|
836
|
+
align-items: center;
|
|
837
|
+
justify-content: center;
|
|
838
|
+
transition: opacity 0.15s ease;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.prompt-action:hover:not(:disabled) {
|
|
842
|
+
opacity: 0.9;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.prompt-action.running {
|
|
846
|
+
background: rgba(255, 255, 255, 0.14);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.prompt-action:disabled {
|
|
850
|
+
opacity: 0.45;
|
|
851
|
+
cursor: not-allowed;
|
|
852
|
+
}
|
|
853
|
+
|
|
793
854
|
/* Style the markdown tool sections in assistant messages */
|
|
794
855
|
:global(.assistant strong) {
|
|
795
856
|
color: var(--spectrum-global-color-gray-900);
|
package/src/components/index.ts
CHANGED
|
@@ -11,3 +11,5 @@ export { default as ChangePasswordModal } from "./ChangePasswordModal.svelte"
|
|
|
11
11
|
export { default as ProfileModal } from "./ProfileModal.svelte"
|
|
12
12
|
export { default as PasswordRepeatInput } from "./PasswordRepeatInput.svelte"
|
|
13
13
|
export { default as Chatbox } from "./Chatbox/index.svelte"
|
|
14
|
+
export { default as ChatNavigationPanel } from "./Chatbox/ChatNavigationPanel.svelte"
|
|
15
|
+
export { default as ChatConversationPanel } from "./Chatbox/ChatConversationPanel.svelte"
|