@quidgest/chatbot 0.5.0 → 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 +2317 -1810
  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 +28 -7
  25. package/src/assets/styles/styles.scss +212 -220
  26. package/src/components/ChatBot/ChatBot.vue +346 -370
  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 -73
  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
@@ -0,0 +1,132 @@
1
+ // Composables
2
+ import { useSSE } from '../useSSE'
3
+
4
+ // Utils
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
6
+ import axios from 'axios'
7
+
8
+ // Types
9
+ import type { SSEvents } from '../useSSE'
10
+
11
+ // Mocks
12
+ vi.mock('axios')
13
+ const mockedAxios = vi.mocked(axios)
14
+
15
+ // Utility to create a mock ReadableStream
16
+ function createMockStream(chunks: string[]) {
17
+ return new ReadableStream({
18
+ start(controller) {
19
+ for (const chunk of chunks) {
20
+ controller.enqueue(new TextEncoder().encode(chunk))
21
+ }
22
+ controller.close()
23
+ }
24
+ })
25
+ }
26
+
27
+ describe('useSSE', () => {
28
+ let handlers: SSEvents = {}
29
+
30
+ beforeEach(() => {
31
+ vi.clearAllMocks()
32
+ handlers = {
33
+ onMessage: vi.fn(),
34
+ onError: vi.fn(),
35
+ onFieldMetadata: vi.fn(),
36
+ onDone: vi.fn()
37
+ }
38
+ })
39
+
40
+ it('should handle a message event', async () => {
41
+ const stream = createMockStream(['event: message\ndata: {"value":"hello"}\n\n'])
42
+ mockedAxios.mockResolvedValue({ data: stream })
43
+
44
+ await useSSE({ url: '/sse' }, handlers)
45
+
46
+ expect(handlers.onMessage).toHaveBeenCalledWith('hello')
47
+ expect(handlers.onDone).toHaveBeenCalled()
48
+ })
49
+
50
+ it('should handle an error event', async () => {
51
+ const stream = createMockStream([
52
+ 'event: error\ndata: {"value":"Something went wrong"}\n\n'
53
+ ])
54
+ mockedAxios.mockResolvedValue({ data: stream })
55
+
56
+ await useSSE({ url: '/sse' }, handlers)
57
+
58
+ expect(handlers.onError).toHaveBeenCalled()
59
+
60
+ // We need to cast to access the mock calls
61
+ const onErrorHandler = handlers.onError as ReturnType<typeof vi.fn>
62
+ const errorArg = onErrorHandler.mock.calls[0][0]
63
+
64
+ expect(errorArg).toBeInstanceOf(Error)
65
+ expect(errorArg.message).toBe('Something went wrong')
66
+ })
67
+
68
+ it('should handle field metadata event', async () => {
69
+ const metadata = { foo: 'bar' }
70
+ const stream = createMockStream([
71
+ `event: field_metadata\ndata: ${JSON.stringify(metadata)}\n\n`
72
+ ])
73
+ mockedAxios.mockResolvedValue({ data: stream })
74
+ await useSSE({ url: '/sse' }, handlers)
75
+
76
+ expect(handlers.onFieldMetadata).toHaveBeenCalledWith(metadata)
77
+ expect(handlers.onDone).toHaveBeenCalled()
78
+ })
79
+
80
+ it('should handle a done event', async () => {
81
+ const stream = createMockStream(['event: done\ndata: {}\n\n'])
82
+ mockedAxios.mockResolvedValue({ data: stream })
83
+
84
+ await useSSE({ url: '/sse' }, handlers)
85
+
86
+ expect(handlers.onDone).toHaveBeenCalled()
87
+ })
88
+
89
+ it('should call onError on invalid JSON', async () => {
90
+ const stream = createMockStream(['event: message\ndata: {invalid json}\n\n'])
91
+ mockedAxios.mockResolvedValue({ data: stream })
92
+
93
+ // Suppress console.error for this test
94
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
95
+
96
+ await useSSE({ url: '/sse' }, handlers)
97
+
98
+ expect(handlers.onError).toHaveBeenCalled()
99
+ const onErrorHandler = handlers.onError as ReturnType<typeof vi.fn>
100
+ const errorArg = onErrorHandler.mock.calls[0][0]
101
+
102
+ expect(errorArg).toBeInstanceOf(Error)
103
+ consoleErrorSpy.mockRestore()
104
+ })
105
+
106
+ it('should default to message event if name is missing', async () => {
107
+ const stream = createMockStream(['data: {"value":"default event"}\n\n'])
108
+ mockedAxios.mockResolvedValue({ data: stream })
109
+
110
+ await useSSE({ url: '/sse' }, handlers)
111
+
112
+ expect(handlers.onMessage).toHaveBeenCalledWith('default event')
113
+ expect(handlers.onDone).toHaveBeenCalled()
114
+ })
115
+
116
+ it('should throw an error for non-stream response', async () => {
117
+ mockedAxios.mockResolvedValue({ data: null })
118
+ const fn = useSSE({ url: '/sse' }, handlers)
119
+ await expect(fn).rejects.toThrow('Invalid stream response')
120
+ })
121
+
122
+ it('should warn on unknown event type', async () => {
123
+ const stream = createMockStream(['event: unknown_event\ndata: {"value":"something"}\n\n'])
124
+ mockedAxios.mockResolvedValue({ data: stream })
125
+
126
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
127
+ await useSSE({ url: '/sse' }, handlers)
128
+
129
+ expect(consoleWarnSpy).toHaveBeenCalledWith('Unknown event type: unknown_event')
130
+ consoleWarnSpy.mockRestore()
131
+ })
132
+ })
@@ -8,149 +8,147 @@ import { ChatBotMessageContent } from '@/components/ChatBot/types'
8
8
  import { useSSE } from './useSSE'
9
9
 
10
10
  type RequestResponse<T> = {
11
- data: T | null
12
- error: Error | null
11
+ data: T | null
12
+ error: Error | null
13
13
  }
14
14
 
15
15
  export function useChatApi(apiEndpoint: string) {
16
- const isLoading = ref(false)
17
- const lastError = ref<Error | null>(null)
16
+ const isLoading = ref(false)
17
+ const lastError = ref<Error | null>(null)
18
18
 
19
- async function baseRequest<T>(
20
- config: AxiosRequestConfig
21
- ): Promise<RequestResponse<T>> {
22
- isLoading.value = true
23
- lastError.value = null
19
+ async function baseRequest<T>(config: AxiosRequestConfig): Promise<RequestResponse<T>> {
20
+ isLoading.value = true
21
+ lastError.value = null
24
22
 
25
- try {
26
- const response: AxiosResponse<T> = await axios({
27
- ...config,
28
- baseURL: apiEndpoint
29
- })
30
- return { data: response.data, error: null }
31
- } catch (error: any) {
32
- lastError.value = error
33
- console.error('Error in API request:', error)
34
- return { data: null, error }
35
- } finally {
36
- isLoading.value = false
37
- }
38
- }
23
+ try {
24
+ const response: AxiosResponse<T> = await axios({
25
+ ...config,
26
+ baseURL: apiEndpoint
27
+ })
28
+ return { data: response.data, error: null }
29
+ } catch (error: unknown) {
30
+ if (error instanceof Error) {
31
+ lastError.value = error
32
+ console.error('Error in API request:', error)
33
+ return { data: null, error }
34
+ }
35
+ } finally {
36
+ isLoading.value = false
37
+ }
39
38
 
40
- async function getChatData(
41
- username: string,
42
- project: string,
43
- agentID: string,
44
- formId: string
45
- ) {
46
- return await baseRequest<{
47
- success: boolean
48
- history: ChatBotMessageContent[]
49
- }>({
50
- method: 'POST',
51
- url: `/prompt/load`,
52
- data: {
53
- username,
54
- project,
55
- agentID,
56
- formId
57
- }
58
- })
59
- }
39
+ // This should never be reached, but TypeScript needs it
40
+ return { data: null, error: new Error('Unknown error occurred in baseRequest') }
41
+ }
60
42
 
61
- async function clearChatData(
62
- username: string,
63
- project: string,
64
- agentID: string,
65
- formId: string
66
- ) {
67
- return await baseRequest<{ success: boolean }>({
68
- method: 'POST',
69
- url: `/prompt/clear`,
70
- data: {
71
- username,
72
- project,
73
- agentID,
74
- formId
75
- }
76
- })
77
- }
43
+ async function getChatData(username: string, project: string, agentID: string, formId: string) {
44
+ return await baseRequest<{
45
+ success: boolean
46
+ history: ChatBotMessageContent[]
47
+ }>({
48
+ method: 'POST',
49
+ url: `/prompt/load`,
50
+ data: {
51
+ username,
52
+ project,
53
+ agentID,
54
+ formId
55
+ }
56
+ })
57
+ }
78
58
 
79
- async function sendPrompt(
80
- formData: FormData,
81
- onChunk: (chunk: string) => void,
82
- onError?: (error: Error) => void
83
- ) {
84
- isLoading.value = true
85
- return await useSSE(
86
- {
87
- method: 'POST',
88
- url: `${apiEndpoint}/prompt/submit`,
89
- data: formData
90
- },
91
- {
92
- onMessage: (data) => {
93
- onChunk(data)
94
- },
95
- onError: (error) => {
96
- lastError.value = error
97
- onError?.(error)
98
- console.error('Error in sendPrompt:', error)
99
- },
100
- onDone: () => (isLoading.value = false)
101
- }
102
- )
103
- }
59
+ async function sendPrompt(
60
+ formData: FormData,
61
+ onChunk: (chunk: string) => void,
62
+ onError?: (error: Error) => void
63
+ ) {
64
+ isLoading.value = true
65
+ try {
66
+ return await useSSE(
67
+ {
68
+ method: 'POST',
69
+ url: `${apiEndpoint}/prompt/submit`,
70
+ data: formData
71
+ },
72
+ {
73
+ onMessage: (data) => {
74
+ onChunk(data)
75
+ },
76
+ onDone: () => (isLoading.value = false)
77
+ }
78
+ )
79
+ } catch (error) {
80
+ isLoading.value = false
81
+ onError?.(error as Error)
82
+ console.error('Error in sendPrompt:', error)
83
+ throw error
84
+ }
85
+ }
104
86
 
105
- async function getFieldSuggestionData(
106
- jobId: string,
107
- onChunk: (chunk: string) => void,
108
- onMetaData: (metadata: Record<string, unknown>) => void
109
- ) {
110
- isLoading.value = true
111
- return await useSSE(
112
- {
113
- method: 'POST',
114
- url: `${apiEndpoint}/get-job-result`,
115
- data: {
116
- jobId
117
- }
118
- },
119
- {
120
- onMessage: (data) => onChunk(data),
121
- onFieldMetadata: (metadata) => onMetaData(metadata),
122
- onError: (error) => {
123
- lastError.value = error
124
- console.error('Error in getFieldSuggestionData:', error)
125
- },
126
- onDone: () => (isLoading.value = false)
127
- }
128
- )
129
- }
87
+ async function getJobResultData(
88
+ jobId: string,
89
+ onChunk: (chunk: string) => void,
90
+ onMetaData: (metadata: Record<string, unknown>) => void,
91
+ onRequestError?: (error: Error) => void
92
+ ) {
93
+ isLoading.value = true
94
+ try {
95
+ return await useSSE(
96
+ {
97
+ method: 'POST',
98
+ url: `${apiEndpoint}/get-job-result`,
99
+ data: {
100
+ jobId
101
+ }
102
+ },
103
+ {
104
+ onMessage: (data) => onChunk(data),
105
+ onFieldMetadata: (metadata) => onMetaData(metadata),
106
+ onDone: () => (isLoading.value = false)
107
+ }
108
+ )
109
+ } catch (error) {
110
+ lastError.value = error as Error
111
+ onRequestError?.(error as Error)
112
+ isLoading.value = false
113
+ }
114
+ }
115
+ async function clearChatData(
116
+ username: string,
117
+ project: string,
118
+ agentID: string,
119
+ formId: string
120
+ ) {
121
+ return await baseRequest<{ success: boolean }>({
122
+ method: 'POST',
123
+ url: `/prompt/clear`,
124
+ data: {
125
+ username,
126
+ project,
127
+ agentID,
128
+ formId
129
+ }
130
+ })
131
+ }
130
132
 
131
- async function handleFeedback(
132
- feedback: number,
133
- comment: string,
134
- sessionID: string
135
- ) {
136
- return await baseRequest<{ success: boolean }>({
137
- method: 'POST',
138
- url: `/prompt/feedback`,
139
- data: {
140
- messageSessionID: sessionID,
141
- feedbackValue: feedback,
142
- feedbackComment: comment
143
- }
144
- })
145
- }
133
+ async function handleFeedback(feedback: number, comment: string, sessionID: string) {
134
+ return await baseRequest<{ success: boolean }>({
135
+ method: 'POST',
136
+ url: `/prompt/feedback`,
137
+ data: {
138
+ messageSessionID: sessionID,
139
+ feedbackValue: feedback,
140
+ feedbackComment: comment
141
+ }
142
+ })
143
+ }
146
144
 
147
- return {
148
- isLoading,
149
- lastError,
150
- getChatData,
151
- clearChatData,
152
- getFieldSuggestionData,
153
- sendPrompt,
154
- handleFeedback
155
- }
145
+ return {
146
+ isLoading,
147
+ lastError,
148
+ getChatData,
149
+ clearChatData,
150
+ getJobResultData,
151
+ sendPrompt,
152
+ handleFeedback
153
+ }
156
154
  }
@@ -2,57 +2,59 @@ import { ref } from 'vue'
2
2
  import { v4 as uuidv4 } from 'uuid'
3
3
 
4
4
  import type { Ref } from 'vue'
5
- import type {
6
- ChatMessage,
7
- ChatBotMessageSender
8
- } from '@/components/ChatBot/types'
5
+ import type { ChatMessage, ChatBotMessageSender } from '@/components/ChatBot/types'
9
6
 
10
7
  const messages: Ref<ChatMessage[]> = ref([])
11
8
  const nextMessageId = ref(1)
12
9
 
13
10
  export function useChatMessages() {
14
- function addChatMessage(
15
- message: string,
16
- sender?: ChatBotMessageSender,
17
- imagePreviewUrl?: string | null,
18
- sessionID?: string,
19
- isWelcomeMessage?: boolean
20
- ) {
21
- const newMessage: ChatMessage = {
22
- id: nextMessageId.value++,
23
- message,
24
- date: new Date(),
25
- sender: sender || 'bot',
26
- sessionID: sessionID || uuidv4(),
27
- imagePreviewUrl: imagePreviewUrl || undefined,
28
- isWelcomeMessage,
29
- fields: []
30
- }
31
-
32
- messages.value.push(newMessage)
33
- }
34
-
35
- function getLastMessage() {
36
- return messages.value.find(
37
- (m: ChatMessage) => m.id === nextMessageId.value - 1
38
- )
39
- }
40
-
41
- function clearMessages() {
42
- messages.value = []
43
- nextMessageId.value = 1
44
- }
45
-
46
- function getMessages() {
47
- return messages.value
48
- }
49
-
50
- return {
51
- messages,
52
- nextMessageId,
53
- addChatMessage,
54
- getLastMessage,
55
- clearMessages,
56
- getMessages
57
- }
11
+ function addChatMessage(
12
+ message: string,
13
+ sender?: ChatBotMessageSender,
14
+ imagePreviewUrl?: string | null,
15
+ sessionID?: string,
16
+ isWelcomeMessage?: boolean
17
+ ) {
18
+ const newMessage: ChatMessage = {
19
+ id: nextMessageId.value++,
20
+ message,
21
+ date: new Date(),
22
+ sender: sender || 'bot',
23
+ sessionID: sessionID || uuidv4(),
24
+ imagePreviewUrl: imagePreviewUrl || undefined,
25
+ isWelcomeMessage,
26
+ fields: []
27
+ }
28
+
29
+ messages.value.push(newMessage)
30
+
31
+ return newMessage
32
+ }
33
+
34
+ function getLastMessage() {
35
+ return messages.value.find((m: ChatMessage) => m.id === nextMessageId.value - 1)
36
+ }
37
+
38
+ function clearMessages() {
39
+ messages.value = []
40
+ nextMessageId.value = 1
41
+ }
42
+
43
+ function deleteMessageById(messageId: number) {
44
+ messages.value = messages.value.filter((m) => m.id !== messageId)
45
+ }
46
+
47
+ function getMessages() {
48
+ return messages.value
49
+ }
50
+
51
+ return {
52
+ messages,
53
+ nextMessageId,
54
+ addChatMessage,
55
+ getLastMessage,
56
+ clearMessages,
57
+ getMessages,
58
+ deleteMessageById
59
+ }
58
60
  }