@quidgest/chatbot 0.5.1 → 0.5.3-dev.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.
Files changed (66) hide show
  1. package/README.md +1 -2
  2. package/dist/components/ChatBot/ChatBot.vue.d.ts +2 -0
  3. package/dist/components/ChatBot/types.d.ts +2 -1
  4. package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +2 -2
  5. package/dist/components/ChatBotInput/__tests__/ChatBotInput.spec.d.ts +1 -0
  6. package/dist/components/ChatBotMessage/ChatBotMessage.vue.d.ts +3 -1
  7. package/dist/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.d.ts +1 -0
  8. package/dist/components/ChatToolBar/__tests__/ChatToolBar.spec.d.ts +1 -0
  9. package/dist/components/FieldPreview/FieldPreview.vue.d.ts +2 -0
  10. package/dist/components/FieldPreview/__tests__/FieldPreview.spec.d.ts +1 -0
  11. package/dist/components/MarkdownRender/__tests__/MarkdownRender.spec.d.ts +1 -0
  12. package/dist/components/PulseDots/__tests__/PulseDots.spec.d.ts +1 -0
  13. package/dist/composables/__tests__/useChatMessages.spec.d.ts +1 -0
  14. package/dist/composables/__tests__/useSSE.spec.d.ts +1 -0
  15. package/dist/composables/useChatApi.d.ts +1 -1
  16. package/dist/composables/useChatMessages.d.ts +2 -1
  17. package/dist/composables/useSSE.d.ts +1 -2
  18. package/dist/composables/useTexts.d.ts +5 -0
  19. package/dist/index.js +25 -25
  20. package/dist/index.mjs +2319 -1812
  21. package/dist/style.css +1 -1
  22. package/dist/test/setup.d.ts +1 -0
  23. package/dist/utils/__tests__/parseFieldValue.spec.d.ts +1 -0
  24. package/package.json +27 -6
  25. package/src/assets/styles/styles.scss +212 -220
  26. package/src/components/ChatBot/ChatBot.vue +346 -368
  27. package/src/components/ChatBot/types.ts +34 -33
  28. package/src/components/ChatBotInput/ChatBotInput.vue +181 -190
  29. package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +292 -0
  30. package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +25 -0
  31. package/src/components/ChatBotInput/types.ts +24 -24
  32. package/src/components/ChatBotMessage/ChatBotMessage.vue +133 -134
  33. package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +179 -164
  34. package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +199 -0
  35. package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap +25 -0
  36. package/src/components/ChatBotMessage/types.ts +52 -52
  37. package/src/components/ChatToolBar/ChatToolBar.vue +69 -64
  38. package/src/components/ChatToolBar/__tests__/ChatToolBar.spec.ts +138 -0
  39. package/src/components/ChatToolBar/__tests__/__snapshots__/ChatToolBar.spec.ts.snap +11 -0
  40. package/src/components/ChatToolBar/types.ts +12 -12
  41. package/src/components/FieldPreview/FieldPreview.vue +83 -63
  42. package/src/components/FieldPreview/__tests__/FieldPreview.spec.ts +72 -0
  43. package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +19 -0
  44. package/src/components/FieldPreview/field-preview.scss +28 -24
  45. package/src/components/FieldPreview/types.ts +5 -5
  46. package/src/components/MarkdownRender/MarkdownRender.vue +16 -15
  47. package/src/components/MarkdownRender/__tests__/MarkdownRender.spec.ts +68 -0
  48. package/src/components/MarkdownRender/__tests__/__snapshots__/MarkdownRender.spec.ts.snap +8 -0
  49. package/src/components/MarkdownRender/markdown-render.scss +19 -20
  50. package/src/components/MarkdownRender/types.ts +3 -3
  51. package/src/components/PulseDots/PulseDots.vue +17 -17
  52. package/src/components/PulseDots/__tests__/PulseDots.spec.ts +35 -0
  53. package/src/components/PulseDots/__tests__/__snapshots__/PulseDots.spec.ts.snap +7 -0
  54. package/src/components/PulseDots/__tests__/__snapshots__/pulse-dots.spec.ts.snap +7 -0
  55. package/src/components/PulseDots/pulse-dots.scss +24 -23
  56. package/src/composables/__tests__/useChatMessages.spec.ts +64 -0
  57. package/src/composables/__tests__/useSSE.spec.ts +132 -0
  58. package/src/composables/useChatApi.ts +132 -134
  59. package/src/composables/useChatMessages.ts +50 -48
  60. package/src/composables/useSSE.ts +75 -76
  61. package/src/composables/useTexts.ts +33 -30
  62. package/src/test/setup.ts +41 -0
  63. package/src/utils/__tests__/parseFieldValue.spec.ts +27 -0
  64. package/src/utils/parseFieldValue.ts +12 -0
  65. package/src/utils/helper.ts +0 -12
  66. /package/dist/utils/{helper.d.ts → parseFieldValue.d.ts} +0 -0
@@ -1,93 +1,92 @@
1
1
  import axios, { AxiosRequestConfig } from 'axios'
2
2
 
3
- type SSEvents = {
4
- onMessage?: (message: string) => void
5
- onError?: (error: Error) => void
6
- onFieldMetadata?: (metadata: Record<string, unknown>) => void
7
- onDone?: () => void
3
+ export type SSEvents = {
4
+ onMessage?: (message: string) => void
5
+ onError?: (error: Error) => void
6
+ onFieldMetadata?: (metadata: Record<string, unknown>) => void
7
+ onDone?: () => void
8
8
  }
9
9
 
10
10
  export async function useSSE(config: AxiosRequestConfig, handlers: SSEvents) {
11
- const controller = new AbortController()
12
- const signal = controller.signal
11
+ const controller = new AbortController()
12
+ const signal = controller.signal
13
13
 
14
- const response = await axios({
15
- ...config,
16
- headers: {
17
- Accept: 'text/event-stream'
18
- },
19
- responseType: 'stream',
20
- signal,
21
- adapter: 'fetch'
22
- })
14
+ const response = await axios({
15
+ ...config,
16
+ headers: {
17
+ Accept: 'text/event-stream'
18
+ },
19
+ responseType: 'stream',
20
+ signal,
21
+ adapter: 'fetch'
22
+ })
23
23
 
24
- const stream = response.data
24
+ const stream = response.data
25
25
 
26
- if (!stream || !(stream instanceof ReadableStream)) {
27
- throw new Error('Invalid stream response')
28
- }
26
+ if (!stream || !(stream instanceof ReadableStream)) {
27
+ throw new Error('Invalid stream response')
28
+ }
29
29
 
30
- const reader = stream.getReader()
31
- const decoder = new TextDecoder()
30
+ const reader = stream.getReader()
31
+ const decoder = new TextDecoder()
32
32
 
33
- while (true) {
34
- const { done, value } = await reader.read()
35
- if (done) {
36
- handlers.onDone?.()
37
- break
38
- }
33
+ while (true) {
34
+ const { done, value } = await reader.read()
35
+ if (done) {
36
+ handlers.onDone?.()
37
+ break
38
+ }
39
39
 
40
- const chunk = decoder.decode(value, { stream: true })
41
- const events = chunk.split(/\n\n+/)
40
+ const chunk = decoder.decode(value, { stream: true })
41
+ const events = chunk.split(/\n\n+/)
42
42
 
43
- for (const eventBlock of events) {
44
- const lines = eventBlock.trim().split('\n')
45
- let name = ''
46
- let data = ''
43
+ for (const eventBlock of events) {
44
+ const lines = eventBlock.trim().split('\n')
45
+ let name = ''
46
+ let data = ''
47
47
 
48
- for (const line of lines) {
49
- if (line.startsWith('event:')) {
50
- name = line.replace('event:', '').trim()
51
- } else if (line.startsWith('data:')) {
52
- data = line.replace('data:', '').trim()
53
- }
54
- }
48
+ for (const line of lines) {
49
+ if (line.startsWith('event:')) {
50
+ name = line.replace('event:', '').trim()
51
+ } else if (line.startsWith('data:')) {
52
+ data = line.replace('data:', '').trim()
53
+ }
54
+ }
55
55
 
56
+ // Backward compatibility for missing event names
57
+ if (!name) name = 'message'
56
58
 
57
- // Backward compatibility for missing event names
58
- if(!name) name = 'message';
59
+ if (!data) continue
59
60
 
60
- if (!data) continue
61
-
62
- try {
63
- const parsedData = JSON.parse(data)
64
- switch (name) {
65
- case 'message':
66
- handlers.onMessage?.(parsedData.value)
67
- break
68
- case 'error':
69
- handlers.onError?.(new Error(parsedData.value))
70
- break
71
- case 'field_metadata':
72
- handlers.onFieldMetadata?.(parsedData)
73
- break
74
- case 'done':
75
- handlers.onDone?.()
76
- break
77
- default:
78
- console.warn(`Unknown event type: ${name}`)
79
- }
80
- } catch (error) {
81
- handlers.onError?.(error as Error)
82
- console.error('Error processing event:', error)
83
- continue
84
- } finally {
85
- // Ensure we always clean up the reader
86
- if (done) {
87
- reader.releaseLock()
88
- controller.abort()
89
- }
90
- }
91
- }
92
- }
61
+ try {
62
+ const parsedData = JSON.parse(data)
63
+ switch (name) {
64
+ case 'message':
65
+ handlers.onMessage?.(parsedData.value)
66
+ break
67
+ case 'error':
68
+ handlers.onError?.(new Error(parsedData.value))
69
+ break
70
+ case 'field_metadata':
71
+ handlers.onFieldMetadata?.(parsedData)
72
+ break
73
+ case 'done':
74
+ handlers.onDone?.()
75
+ break
76
+ default:
77
+ console.warn(`Unknown event type: ${name}`)
78
+ }
79
+ } catch (error) {
80
+ handlers.onError?.(error as Error)
81
+ console.error('Error processing event:', error)
82
+ continue
83
+ } finally {
84
+ // Ensure we always clean up the reader
85
+ if (done) {
86
+ reader.releaseLock()
87
+ controller.abort()
88
+ }
89
+ }
90
+ }
91
+ }
93
92
  }
@@ -1,32 +1,35 @@
1
+ /**
2
+ * TODO: Implement localization
3
+ */
1
4
  export function useTexts() {
2
- return {
3
- copy: 'Copy',
4
- apply: 'Apply',
5
- applyAll: 'Apply all',
6
- chatbotTitle: 'ChatBot',
7
- sendMessage: 'Send message',
8
- clearChat: 'Clear chat',
9
- inputLabel: 'What can I help with?',
10
- imageUpload: 'Upload Image',
11
- imageUploadQButton: 'Upload Image',
12
- goodResponse: 'Good response',
13
- badResponse: 'Bad response',
14
- initialMessage:
15
- "Howdy! I am GenioBot 👋, Quidgest's personal AI assistant! How can I help you?",
16
- initialAgentMessage:
17
- 'Just a temporary message while we are working on the agent mode',
18
- loginError:
19
- 'Uh oh, I could not authenticate with the Quidgest API endpoint 😓',
20
- botIsSick:
21
- '*cough cough* GenioBot is not feeling alright 🥴️🤒, looks like something failed!',
22
- commentDialogTitle: 'Would you like to add a comment?',
23
- commentPlaceholder: 'Type your comment here (optional)...',
24
- copyResponse: 'Copy response',
25
- submitButton: 'Submit',
26
- cancelButton: 'Cancel',
27
- senderImage: 'Sender Image',
28
- imagePreview: 'Image preview',
29
- regenerateResponse: 'Regenerate response',
30
- generatingResponse: 'Generating'
31
- }
5
+ return {
6
+ copy: 'Copy',
7
+ apply: 'Apply',
8
+ applyAll: 'Apply all',
9
+ chatbotTitle: 'ChatBot',
10
+ sendMessage: 'Send message',
11
+ clearChat: 'Clear chat',
12
+ inputLabel: 'What can I help with?',
13
+ imageUpload: 'Upload Image',
14
+ imageUploadQButton: 'Upload Image',
15
+ goodResponse: 'Good response',
16
+ badResponse: 'Bad response',
17
+ initialMessage:
18
+ "Howdy! I am GenioBot 👋, Quidgest's personal AI assistant! How can I help you?",
19
+ initialAgentMessage: 'Just a temporary message while we are working on the agent mode',
20
+ loginError: 'Uh oh, I could not authenticate with the Quidgest API endpoint 😓',
21
+ botIsSick:
22
+ '*cough cough* GenioBot is not feeling alright 🥴️🤒, looks like something failed!',
23
+ commentDialogTitle: 'Would you like to add a comment?',
24
+ commentPlaceholder: 'Type your comment here (optional)...',
25
+ copyResponse: 'Copy response',
26
+ submitButton: 'Submit',
27
+ cancelButton: 'Cancel',
28
+ senderImage: 'Sender Image',
29
+ imagePreview: 'Image preview',
30
+ regenerateResponsePrompt: 'Regenerate a new response for field {0}',
31
+ regenerateResponse: 'Regenerate response',
32
+ generatingResponse: 'Generating',
33
+ suggestionsForField: 'Suggestions for field'
34
+ }
32
35
  }
@@ -0,0 +1,41 @@
1
+ import { vi } from 'vitest'
2
+ import { config } from '@vue/test-utils'
3
+
4
+ import { createFramework } from '@quidgest/ui/framework'
5
+ import { QSelect } from '@quidgest/ui/components'
6
+
7
+ vi.mock('@quidgest/ui/components', async (importOriginal) => {
8
+ const original = await importOriginal<typeof import('@quidgest/ui/components')>()
9
+ return {
10
+ ...original,
11
+ // QIcon needs to be mocked to prevent it from making network requests for icons during tests
12
+ QIcon: {
13
+ name: 'QIcon',
14
+ props: ['icon'],
15
+ template: '<span :data-test="icon"><slot /></span>'
16
+ }
17
+ }
18
+ })
19
+
20
+ // Prevent any real network fetches (icons/assets) during tests
21
+ // safe noop that resolves to a minimal response-like object
22
+ globalThis.fetch = vi.fn().mockResolvedValue({
23
+ ok: true,
24
+ arrayBuffer: async () => new ArrayBuffer(0),
25
+ blob: async () => new Blob(),
26
+ text: async () => ''
27
+ } as unknown)
28
+
29
+ const framework = createFramework()
30
+
31
+ config.global.plugins = [
32
+ {
33
+ install(app) {
34
+ app.use(framework)
35
+ }
36
+ }
37
+ ]
38
+
39
+ config.global.components = {
40
+ QSelect
41
+ }
@@ -0,0 +1,27 @@
1
+ import { parseFieldValue } from '../parseFieldValue'
2
+
3
+ import { describe, it, expect } from 'vitest'
4
+
5
+ describe('parseFieldValue', () => {
6
+ it('parses date strings correctly', () => {
7
+ const date = '2025-09-04'
8
+ const result = parseFieldValue('date', date)
9
+ expect(result).toBe(new Date(date).toLocaleDateString())
10
+ })
11
+
12
+ it('parses number strings correctly', () => {
13
+ const result = parseFieldValue('number', '123.45')
14
+ expect(result).toBe(123.45)
15
+ })
16
+
17
+ it('parses boolean strings correctly', () => {
18
+ expect(parseFieldValue('boolean', 'true')).toBe(true)
19
+ expect(parseFieldValue('boolean', 'false')).toBe(false)
20
+ expect(parseFieldValue('boolean', 'TRUE')).toBe(true)
21
+ expect(parseFieldValue('boolean', 'FALSE')).toBe(false)
22
+ })
23
+
24
+ it('returns text as string for unknown types', () => {
25
+ expect(parseFieldValue('unknown', '123')).toBe('123')
26
+ })
27
+ })
@@ -0,0 +1,12 @@
1
+ export function parseFieldValue(type: string, text: string) {
2
+ switch (type) {
3
+ case 'date':
4
+ return new Date(text).toLocaleDateString()
5
+ case 'number':
6
+ return parseFloat(text)
7
+ case 'boolean':
8
+ return text.toLowerCase() === 'true'
9
+ default:
10
+ return text.toString()
11
+ }
12
+ }
@@ -1,12 +0,0 @@
1
- export function parseFieldValue(type: string, text: string) {
2
- switch (type) {
3
- case 'date':
4
- return new Date(text).toLocaleDateString()
5
- case 'number':
6
- return parseFloat(text)
7
- case 'boolean':
8
- return text.toLowerCase() === 'true'
9
- default:
10
- return text.toString()
11
- }
12
- }