@quidgest/chatbot 0.5.9 → 0.6.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/dist/components/ChatBotMessage/ChatBotMessage.vue.d.ts +7 -0
- package/dist/components/ChatBotMessage/ChatBotMessageButtons.vue.d.ts +4 -0
- package/dist/components/ChatBotMessage/types.d.ts +21 -0
- package/dist/components/PulseDots/PulseDots.vue.d.ts +16 -1
- package/dist/composables/useChatApi.d.ts +6 -3
- package/dist/composables/useTexts.d.ts +2 -0
- package/dist/index.js +22 -26
- package/dist/index.mjs +1775 -1589
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/assets/styles/styles.scss +4 -4
- package/src/components/ChatBot/ChatBot.vue +167 -56
- package/src/components/ChatBot/__tests__/ChatBot.spec.ts +20 -0
- package/src/components/ChatBotMessage/ChatBotMessage.vue +41 -3
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +68 -2
- package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +148 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessage.spec.ts.snap +3 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap +2 -0
- package/src/components/ChatBotMessage/types.ts +25 -0
- package/src/components/MarkdownRender/MarkdownRender.vue +71 -0
- package/src/components/MarkdownRender/markdown-render.scss +38 -0
- package/src/components/PulseDots/PulseDots.vue +11 -4
- package/src/composables/useChatApi.ts +23 -8
- package/src/composables/useTexts.ts +3 -1
|
@@ -196,4 +196,152 @@ describe('ChatBotMessageButtons', () => {
|
|
|
196
196
|
// The date should be in the current locale format
|
|
197
197
|
expect(dateElement.text()).toBe(expectedFormat)
|
|
198
198
|
})
|
|
199
|
+
|
|
200
|
+
it('renders approve plan button for execution plan messages', () => {
|
|
201
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
202
|
+
props: {
|
|
203
|
+
...props,
|
|
204
|
+
isExecutionPlan: true,
|
|
205
|
+
isLastMessage: true
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const approveProceedButton = wrapper.find('.q-chatbot__approve-proceed-plan-button')
|
|
210
|
+
|
|
211
|
+
expect(approveProceedButton.exists()).toBe(true)
|
|
212
|
+
expect(approveProceedButton.text()).toBe('Approve & proceed')
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('disables approve plan button when not the last message', () => {
|
|
216
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
217
|
+
props: {
|
|
218
|
+
...props,
|
|
219
|
+
isExecutionPlan: true,
|
|
220
|
+
isLastMessage: false
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
const approveProceedButton = wrapper.find('.q-chatbot__approve-proceed-plan-button')
|
|
225
|
+
expect(approveProceedButton.exists()).toBe(true)
|
|
226
|
+
expect(approveProceedButton.attributes('disabled')).toBeDefined()
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('does not render approve plan button when not an execution plan', () => {
|
|
230
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
231
|
+
props: {
|
|
232
|
+
...props,
|
|
233
|
+
isExecutionPlan: false,
|
|
234
|
+
isLastMessage: true
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const approveProceedButton = wrapper.find('.q-chatbot__approve-proceed-plan-button')
|
|
239
|
+
expect(approveProceedButton.exists()).toBe(false)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('emits approve-proceed-plan event when approve button is clicked', async () => {
|
|
243
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
244
|
+
props: {
|
|
245
|
+
...props,
|
|
246
|
+
isExecutionPlan: true,
|
|
247
|
+
isLastMessage: true
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const approveProceedButton = wrapper.find('.q-chatbot__approve-proceed-plan-button')
|
|
252
|
+
await approveProceedButton.trigger('click')
|
|
253
|
+
|
|
254
|
+
expect(wrapper.emitted()['approve-proceed-plan']).toBeTruthy()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('does not emit approve-proceed-plan if the button has already been clicked', async () => {
|
|
258
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
259
|
+
props: {
|
|
260
|
+
...props,
|
|
261
|
+
isExecutionPlan: true,
|
|
262
|
+
isLastMessage: true
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const approveProceedButton = wrapper.find('.q-chatbot__approve-proceed-plan-button')
|
|
267
|
+
await approveProceedButton.trigger('click')
|
|
268
|
+
await approveProceedButton.trigger('click') // Click again
|
|
269
|
+
|
|
270
|
+
expect(wrapper.emitted()['approve-proceed-plan']).toBeTruthy()
|
|
271
|
+
expect(wrapper.emitted()['approve-proceed-plan'].length).toBe(1) // Should only be emitted once
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('renders cancel execution button when showCancelExecution is true', () => {
|
|
275
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
276
|
+
props: {
|
|
277
|
+
...props,
|
|
278
|
+
showCancelExecution: true,
|
|
279
|
+
isLastMessage: true
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
const cancelExecutionButton = wrapper.find('.q-chatbot__cancel-execution-button')
|
|
284
|
+
|
|
285
|
+
expect(cancelExecutionButton.exists()).toBe(true)
|
|
286
|
+
expect(cancelExecutionButton.text()).toBe('Cancel')
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('disables cancel execution button when not the last message', () => {
|
|
290
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
291
|
+
props: {
|
|
292
|
+
...props,
|
|
293
|
+
showCancelExecution: true,
|
|
294
|
+
isLastMessage: false
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
const cancelExecutionButton = wrapper.find('.q-chatbot__cancel-execution-button')
|
|
299
|
+
expect(cancelExecutionButton.exists()).toBe(true)
|
|
300
|
+
expect(cancelExecutionButton.attributes('disabled')).toBeDefined()
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('does not render cancel execution button when showCancelExecution is false', () => {
|
|
304
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
305
|
+
props: {
|
|
306
|
+
...props,
|
|
307
|
+
showCancelExecution: false,
|
|
308
|
+
isLastMessage: true
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
const cancelExecutionButton = wrapper.find('.q-chatbot__cancel-execution-button')
|
|
313
|
+
expect(cancelExecutionButton.exists()).toBe(false)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('emits cancel-execution event when cancel execution button is clicked', async () => {
|
|
317
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
318
|
+
props: {
|
|
319
|
+
...props,
|
|
320
|
+
showCancelExecution: true,
|
|
321
|
+
isLastMessage: true
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
const cancelExecutionButton = wrapper.find('.q-chatbot__cancel-execution-button')
|
|
326
|
+
await cancelExecutionButton.trigger('click')
|
|
327
|
+
|
|
328
|
+
expect(wrapper.emitted()['cancel-execution']).toBeTruthy()
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('does not emit cancel-execution if the button has already been clicked', async () => {
|
|
332
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
333
|
+
props: {
|
|
334
|
+
...props,
|
|
335
|
+
showCancelExecution: true,
|
|
336
|
+
isLastMessage: true
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const cancelExecutionButton = wrapper.find('.q-chatbot__cancel-execution-button')
|
|
341
|
+
await cancelExecutionButton.trigger('click')
|
|
342
|
+
await cancelExecutionButton.trigger('click') // Click again
|
|
343
|
+
|
|
344
|
+
expect(wrapper.emitted()['cancel-execution']).toBeTruthy()
|
|
345
|
+
expect(wrapper.emitted()['cancel-execution'].length).toBe(1) // Should only be emitted once
|
|
346
|
+
})
|
|
199
347
|
})
|
|
@@ -9,6 +9,7 @@ exports[`ChatBotMessage > renders the component with default props 1`] = `
|
|
|
9
9
|
<p>Hello, this is a test message.</p>
|
|
10
10
|
</div>
|
|
11
11
|
</div>
|
|
12
|
+
<!--v-if-->
|
|
12
13
|
</div>
|
|
13
14
|
<!--teleport start-->
|
|
14
15
|
<transition-stub name="fade" appear="true" persisted="false" css="true">
|
|
@@ -28,6 +29,8 @@ exports[`ChatBotMessage > renders the component with default props 1`] = `
|
|
|
28
29
|
<!--v-if--><span class="q-button__content"><span data-test="copy-content"></span> </span>
|
|
29
30
|
</button>
|
|
30
31
|
<!--v-if-->
|
|
32
|
+
<!--v-if-->
|
|
33
|
+
<!--v-if-->
|
|
31
34
|
</div>
|
|
32
35
|
</div>
|
|
33
36
|
<div class="q-chatbot__sender">11:47</div>
|
package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap
CHANGED
|
@@ -19,6 +19,8 @@ exports[`ChatBotMessageButtons > renders correctly with default props 1`] = `
|
|
|
19
19
|
<!--v-if--><span class="q-button__content"><span data-test="copy-content"></span> </span>
|
|
20
20
|
</button>
|
|
21
21
|
<!--v-if-->
|
|
22
|
+
<!--v-if-->
|
|
23
|
+
<!--v-if-->
|
|
22
24
|
</div>
|
|
23
25
|
</div>
|
|
24
26
|
<div class="q-chatbot__sender">11:47</div>"
|
|
@@ -61,6 +61,26 @@ export type ChatBotMessageProps = {
|
|
|
61
61
|
* Additional fields for the message
|
|
62
62
|
*/
|
|
63
63
|
fields?: FieldData[]
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Flag to indicate if this is the last message
|
|
67
|
+
*/
|
|
68
|
+
isLastMessage?: boolean
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Flag to indicate if message is currently streaming
|
|
72
|
+
*/
|
|
73
|
+
isStreaming?: boolean
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Flag to show cancel execution button
|
|
77
|
+
*/
|
|
78
|
+
showCancelExecution?: boolean
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Flag to disable cancel execution button
|
|
82
|
+
*/
|
|
83
|
+
cancelExecutionDisabled?: boolean
|
|
64
84
|
}
|
|
65
85
|
|
|
66
86
|
export type ChatBotMessageButtonsProps = {
|
|
@@ -68,4 +88,9 @@ export type ChatBotMessageButtonsProps = {
|
|
|
68
88
|
showButtons: boolean
|
|
69
89
|
dateFormat: string
|
|
70
90
|
date?: Date
|
|
91
|
+
isLastMessage?: boolean
|
|
92
|
+
isExecutionPlan?: boolean
|
|
93
|
+
isStreaming?: boolean
|
|
94
|
+
showCancelExecution?: boolean
|
|
95
|
+
cancelExecutionDisabled?: boolean
|
|
71
96
|
}
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
+
v-if="!isExecutionPlan && !isExecutionProgress"
|
|
3
4
|
class="markdown-renderer"
|
|
4
5
|
v-html="renderedContent"></div>
|
|
6
|
+
<div
|
|
7
|
+
v-else-if="isExecutionPlan"
|
|
8
|
+
class="markdown-renderer">
|
|
9
|
+
<div v-html="executionPlanTitle"></div>
|
|
10
|
+
<div
|
|
11
|
+
v-if="executionPlanContent"
|
|
12
|
+
class="markdown-renderer__execution-plan-content"
|
|
13
|
+
v-html="executionPlanContent"></div>
|
|
14
|
+
</div>
|
|
15
|
+
<div
|
|
16
|
+
v-else-if="isExecutionProgress"
|
|
17
|
+
class="markdown-renderer">
|
|
18
|
+
<div v-html="executionProgressTitle"></div>
|
|
19
|
+
<div
|
|
20
|
+
v-if="executionProgressContent"
|
|
21
|
+
class="markdown-renderer__execution-progress-content"
|
|
22
|
+
v-html="executionProgressContent"></div>
|
|
23
|
+
</div>
|
|
5
24
|
</template>
|
|
6
25
|
|
|
7
26
|
<script setup lang="ts">
|
|
@@ -23,4 +42,56 @@
|
|
|
23
42
|
if (props.plugins) props.plugins.forEach((plugin) => md.value.use(plugin))
|
|
24
43
|
|
|
25
44
|
const renderedContent = computed(() => md.value.render(props.source))
|
|
45
|
+
|
|
46
|
+
const isExecutionPlan = computed(() => {
|
|
47
|
+
return props.source.includes('data-type="execution-plan"')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const executionPlanTitle = computed(() => {
|
|
51
|
+
if (!isExecutionPlan.value) return ''
|
|
52
|
+
|
|
53
|
+
const lines = props.source.split(/\n|<br>/)
|
|
54
|
+
const titleLine = lines.find((line) => line.includes('data-type="execution-plan"'))
|
|
55
|
+
|
|
56
|
+
return titleLine ? md.value.render(titleLine) : ''
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const executionPlanContent = computed(() => {
|
|
60
|
+
if (!isExecutionPlan.value) return ''
|
|
61
|
+
|
|
62
|
+
const lines = props.source.split(/\n|<br>/)
|
|
63
|
+
const titleIndex = lines.findIndex((line) => line.includes('data-type="execution-plan"'))
|
|
64
|
+
|
|
65
|
+
if (titleIndex === -1) return ''
|
|
66
|
+
|
|
67
|
+
const contentLines = lines.slice(titleIndex + 1).join('\n')
|
|
68
|
+
return md.value.render(contentLines)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const isExecutionProgress = computed(() => {
|
|
72
|
+
return props.source.includes('data-type="execution-progress"')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const executionProgressTitle = computed(() => {
|
|
76
|
+
if (!isExecutionProgress.value) return ''
|
|
77
|
+
|
|
78
|
+
const lines = props.source.split(/\n|<br>/)
|
|
79
|
+
const titleLine = lines.find((line) => line.includes('data-type="execution-progress"'))
|
|
80
|
+
|
|
81
|
+
return titleLine ? md.value.render(titleLine) : ''
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const executionProgressContent = computed(() => {
|
|
85
|
+
if (!isExecutionProgress.value) return ''
|
|
86
|
+
|
|
87
|
+
const lines = props.source.split(/\n|<br>/)
|
|
88
|
+
const titleIndex = lines.findIndex((line) =>
|
|
89
|
+
line.includes('data-type="execution-progress"')
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if (titleIndex === -1) return ''
|
|
93
|
+
|
|
94
|
+
const contentLines = lines.slice(titleIndex + 1).join('\n')
|
|
95
|
+
return md.value.render(contentLines)
|
|
96
|
+
})
|
|
26
97
|
</script>
|
|
@@ -20,4 +20,42 @@
|
|
|
20
20
|
border-radius: 4px;
|
|
21
21
|
font-size: 0.875rem;
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
&__execution-plan-content {
|
|
25
|
+
background-color: var(--gray-dark);
|
|
26
|
+
color: var(--q-theme-on-neutral-dark);
|
|
27
|
+
padding: 0.75rem 1rem;
|
|
28
|
+
border-radius: 6px;
|
|
29
|
+
margin-top: 0.5rem;
|
|
30
|
+
|
|
31
|
+
ul,
|
|
32
|
+
ol {
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding-left: 1.5rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
p {
|
|
38
|
+
margin: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
hr {
|
|
42
|
+
border-color: var(--q-theme-on-neutral-dark);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&__execution-progress-content {
|
|
47
|
+
padding: 0.75rem 1rem;
|
|
48
|
+
border-radius: 6px;
|
|
49
|
+
margin-top: 0.5rem;
|
|
50
|
+
|
|
51
|
+
ul,
|
|
52
|
+
ol {
|
|
53
|
+
margin: 0;
|
|
54
|
+
padding-left: 1.5rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
p {
|
|
58
|
+
margin: 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
23
61
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="pulsing-dots">
|
|
3
|
-
<span
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
<span
|
|
4
|
+
class="generating-text"
|
|
5
|
+
v-html="displayText"></span>
|
|
6
6
|
<div class="dots-container">
|
|
7
7
|
<span
|
|
8
8
|
v-for="(_, index) in dots"
|
|
@@ -16,9 +16,16 @@
|
|
|
16
16
|
</template>
|
|
17
17
|
|
|
18
18
|
<script setup lang="ts">
|
|
19
|
+
import { computed } from 'vue'
|
|
19
20
|
import { useTexts } from '@/composables/useTexts'
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
+
const props = defineProps<{
|
|
23
|
+
/** Text to display above the pulsing dots; if omitted, a default is used. */
|
|
24
|
+
text?: string
|
|
25
|
+
}>()
|
|
22
26
|
|
|
27
|
+
const dots = [1, 2, 3]
|
|
23
28
|
const texts = useTexts()
|
|
29
|
+
|
|
30
|
+
const displayText = computed(() => props.text || texts.generatingResponse)
|
|
24
31
|
</script>
|
|
@@ -58,9 +58,10 @@ export function useChatApi(apiEndpoint: string) {
|
|
|
58
58
|
|
|
59
59
|
async function sendPrompt(
|
|
60
60
|
formData: FormData,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
onError?: (error: Error) => void
|
|
61
|
+
onMessage: (chunk: string) => void,
|
|
62
|
+
onToolStatus?: (payload: { event: string; data: { id: string; html: string } }) => void,
|
|
63
|
+
onError?: (error: Error) => void,
|
|
64
|
+
onDone?: () => void
|
|
64
65
|
) {
|
|
65
66
|
isLoading.value = true
|
|
66
67
|
try {
|
|
@@ -72,12 +73,15 @@ export function useChatApi(apiEndpoint: string) {
|
|
|
72
73
|
},
|
|
73
74
|
{
|
|
74
75
|
onMessage: (data) => {
|
|
75
|
-
|
|
76
|
+
onMessage(data)
|
|
76
77
|
},
|
|
77
78
|
onToolStatus: (payload) => {
|
|
78
|
-
|
|
79
|
+
onToolStatus?.(payload)
|
|
79
80
|
},
|
|
80
|
-
onDone: () =>
|
|
81
|
+
onDone: () => {
|
|
82
|
+
isLoading.value = false
|
|
83
|
+
onDone?.()
|
|
84
|
+
}
|
|
81
85
|
}
|
|
82
86
|
)
|
|
83
87
|
} catch (error) {
|
|
@@ -88,9 +92,19 @@ export function useChatApi(apiEndpoint: string) {
|
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
|
|
95
|
+
async function cancelExecution(sessionId: string) {
|
|
96
|
+
return await baseRequest<{ success: boolean }>({
|
|
97
|
+
method: 'POST',
|
|
98
|
+
url: `/prompt/cancel-execution`,
|
|
99
|
+
data: {
|
|
100
|
+
sessionId
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
async function getJobResultData(
|
|
92
106
|
jobId: string,
|
|
93
|
-
|
|
107
|
+
onMessage: (chunk: string) => void,
|
|
94
108
|
onMetaData: (metadata: Record<string, unknown>) => void,
|
|
95
109
|
onRequestError?: (error: Error) => void
|
|
96
110
|
) {
|
|
@@ -105,7 +119,7 @@ export function useChatApi(apiEndpoint: string) {
|
|
|
105
119
|
}
|
|
106
120
|
},
|
|
107
121
|
{
|
|
108
|
-
onMessage: (data) =>
|
|
122
|
+
onMessage: (data) => onMessage(data),
|
|
109
123
|
onFieldMetadata: (metadata) => onMetaData(metadata),
|
|
110
124
|
onDone: () => (isLoading.value = false)
|
|
111
125
|
}
|
|
@@ -153,6 +167,7 @@ export function useChatApi(apiEndpoint: string) {
|
|
|
153
167
|
clearChatData,
|
|
154
168
|
getJobResultData,
|
|
155
169
|
sendPrompt,
|
|
170
|
+
cancelExecution,
|
|
156
171
|
handleFeedback
|
|
157
172
|
}
|
|
158
173
|
}
|
|
@@ -31,6 +31,8 @@ export function useTexts() {
|
|
|
31
31
|
regenerateResponse: 'Regenerate response',
|
|
32
32
|
generatingResponse: 'Generating',
|
|
33
33
|
suggestionsForField: 'Suggestions for field:',
|
|
34
|
-
fileUpload: 'Upload File'
|
|
34
|
+
fileUpload: 'Upload File',
|
|
35
|
+
approveProceed: 'Approve & proceed',
|
|
36
|
+
approveProceedPlan: 'Approve & proceed with plan'
|
|
35
37
|
}
|
|
36
38
|
}
|