@quidgest/chatbot 0.4.0 → 0.5.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 (73) hide show
  1. package/dist/components/ChatBot/ChatBot.vue.d.ts +53 -0
  2. package/dist/components/ChatBot/types.d.ts +48 -0
  3. package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +17 -0
  4. package/dist/components/ChatBotInput/index.d.ts +5 -0
  5. package/dist/components/ChatBotInput/types.d.ts +28 -0
  6. package/dist/components/ChatBotMessage/ChatBotMessage.vue.d.ts +41 -0
  7. package/dist/components/ChatBotMessage/ChatBotMessageButtons.vue.d.ts +21 -0
  8. package/dist/components/ChatBotMessage/index.d.ts +6 -0
  9. package/dist/components/ChatBotMessage/types.d.ts +46 -0
  10. package/dist/components/ChatToolBar/ChatToolBar.vue.d.ts +39 -0
  11. package/dist/components/ChatToolBar/index.d.ts +5 -0
  12. package/dist/components/ChatToolBar/types.d.ts +16 -0
  13. package/dist/components/FieldPreview/FieldPreview.vue.d.ts +17 -0
  14. package/dist/components/FieldPreview/index.d.ts +5 -0
  15. package/dist/components/FieldPreview/types.d.ts +7 -0
  16. package/dist/components/MarkdownRender/MarkdownRender.vue.d.ts +13 -0
  17. package/dist/components/MarkdownRender/index.d.ts +5 -0
  18. package/dist/components/MarkdownRender/types.d.ts +7 -0
  19. package/dist/composables/useChatApi.d.ts +23 -0
  20. package/dist/composables/useChatMessages.d.ts +11 -0
  21. package/dist/composables/useSSE.d.ts +10 -0
  22. package/dist/composables/useTexts.d.ts +26 -0
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +27 -47
  25. package/dist/index.mjs +2651 -8663
  26. package/dist/style.css +1 -1
  27. package/dist/utils/helper.d.ts +1 -0
  28. package/package.json +5 -5
  29. package/src/assets/chatbot_profile.svg +1 -0
  30. package/src/assets/styles/styles.scss +10 -42
  31. package/src/components/ChatBot/ChatBot.vue +375 -0
  32. package/src/components/ChatBot/types.ts +55 -0
  33. package/src/components/ChatBotInput/ChatBotInput.vue +195 -0
  34. package/src/components/ChatBotInput/index.ts +5 -0
  35. package/src/components/ChatBotInput/types.ts +33 -0
  36. package/src/components/ChatBotMessage/ChatBotMessage.vue +139 -0
  37. package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +169 -0
  38. package/src/components/ChatBotMessage/index.ts +8 -0
  39. package/src/components/ChatBotMessage/types.ts +70 -0
  40. package/src/components/ChatToolBar/ChatToolBar.vue +82 -0
  41. package/src/components/ChatToolBar/index.ts +5 -0
  42. package/src/components/ChatToolBar/types.ts +18 -0
  43. package/src/components/FieldPreview/FieldPreview.vue +78 -0
  44. package/src/components/FieldPreview/field-preview.scss +34 -0
  45. package/src/components/FieldPreview/index.ts +5 -0
  46. package/src/components/FieldPreview/types.ts +7 -0
  47. package/src/components/MarkdownRender/MarkdownRender.vue +25 -0
  48. package/src/components/MarkdownRender/index.ts +5 -0
  49. package/src/components/MarkdownRender/markdown-render.scss +24 -0
  50. package/src/components/MarkdownRender/types.ts +7 -0
  51. package/src/components/PulseDots/PulseDots.vue +24 -0
  52. package/src/components/PulseDots/pulse-dots.scss +37 -0
  53. package/src/composables/useChatApi.ts +156 -0
  54. package/src/composables/useChatMessages.ts +58 -0
  55. package/src/composables/useSSE.ts +90 -0
  56. package/src/composables/useTexts.ts +32 -0
  57. package/src/index.ts +1 -1
  58. package/src/utils/helper.ts +12 -0
  59. package/dist/components/CBMessage.vue.d.ts +0 -95
  60. package/dist/components/ChatBot.vue.d.ts +0 -65
  61. package/dist/components/index.d.ts +0 -4
  62. package/dist/types/chatbot.type.d.ts +0 -14
  63. package/dist/types/message.type.d.ts +0 -34
  64. package/dist/types/texts.type.d.ts +0 -3
  65. package/src/assets/chatbot.png +0 -0
  66. package/src/components/CBMessage.vue +0 -276
  67. package/src/components/ChatBot.vue +0 -496
  68. package/src/components/PulseDots.vue +0 -15
  69. package/src/components/index.ts +0 -4
  70. package/src/types/chatbot.type.ts +0 -15
  71. package/src/types/message.type.ts +0 -55
  72. package/src/types/texts.type.ts +0 -3
  73. /package/dist/components/{PulseDots.vue.d.ts → PulseDots/PulseDots.vue.d.ts} +0 -0
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .q-chatbot{width:100%;padding:1rem;display:flex;flex-direction:column;height:100%}.q-chatbot input{line-height:1.5rem}.q-chatbot .q-input-group .i-text__field{border-radius:0;flex:1}.q-chatbot__text p{margin:0}.q-chatbot__content{background-color:#fff;border:1px solid #eaebec;height:100%;width:100%;display:flex;gap:.75rem;flex-direction:column;overflow:hidden}.q-chatbot__footer-container{padding:.8rem}.q-chatbot__input-wrapper{display:flex;flex-direction:column;position:relative}.q-chatbot__image-preview{display:inline-flex;align-items:center;position:relative;margin-top:.5rem;width:fit-content}.q-chatbot__image-preview img{width:60px;height:60px;object-fit:cover;border-radius:4px;margin-right:.25rem;border:1px solid #eaebec;overflow:hidden}.q-chatbot__image-preview img:hover+.q-chatbot__remove-image,.q-chatbot__image-preview img:focus+.q-chatbot__remove-image{opacity:1;pointer-events:auto}.q-chatbot__image-preview img:focus{outline:solid rgb(var(--q-theme-info-rgb)/50%)}.q-chatbot .q-button.q-chatbot__remove-image{position:absolute;top:-5px;right:-5px;background-color:#00000080;color:#fff;border-radius:50%;padding:5px;font-size:12px;border:none;opacity:0;pointer-events:none;transition:opacity .2s ease-in-out}.q-chatbot .q-button.q-chatbot__remove-image:hover,.q-chatbot .q-button.q-chatbot__remove-image:focus{opacity:1;pointer-events:auto}.q-chatbot__send-container{padding-bottom:.25rem;display:flex;justify-content:space-between;width:100%}.q-chatbot__send-container .q-chatbot__send,.q-chatbot__send-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__send-container .spacer{flex-grow:1}.q-chatbot__footer{position:sticky;padding:0 .5rem;border:1px solid #eaebec;border-radius:.25rem;bottom:0;width:100%;display:flex;flex-direction:column;gap:.25rem}.q-chatbot__footer-disabled{background-color:rgb(var(--q-theme-neutral-light-rgb)/.25);cursor:not-allowed}.q-chatbot__footer.drag-over{border:2px dashed rgb(var(--q-theme-primary-rgb)/.25);background-color:#018bd20d}.q-chatbot__footer .q-chatbot__input{min-height:50px;max-height:100px;border-bottom:1px solid #eaebec;overflow-y:auto}.q-chatbot__footer .q-text-area{max-height:100%;overflow-y:auto}.q-chatbot__footer .q-text-area .q-field__control{border:none}.q-chatbot__upload-container{display:flex;justify-content:flex-start;padding:.25rem 0}.q-chatbot__upload-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__messages-container{display:flex;flex-direction:column;flex-grow:1;padding:0 1rem 2rem;gap:1.5rem;overflow-y:auto}.q-chatbot__messages-wrapper{display:flex;max-width:100%;gap:.2rem}.q-chatbot__tools{display:flex;flex-direction:row;justify-content:end;max-width:100%}.q-chatbot__message-wrapper{display:flex;flex-direction:column;gap:.2rem}.q-chatbot__message-container{display:flex;flex-direction:column;gap:.25rem}.q-chatbot__messages-wrapper_right{justify-content:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-container{align-items:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-wrapper{display:flex;align-items:flex-end}.q-chatbot__profile.q-icon__img{border-radius:50%;height:2rem;width:2rem}.q-chatbot__message{display:flex;align-items:center;padding:.3rem .5rem;background-color:#eaebec;width:fit-content;white-space:normal;min-height:2rem;word-wrap:break-word;word-break:break-word;border-radius:0 .5rem .5rem}.q-chatbot__messages-wrapper_right .q-chatbot__message{background-color:#018bd233;border-radius:.5rem 0 .5rem .5rem}.q-chatbot__sender{white-space:nowrap;color:#7c858d;font-size:.7rem}.q-chatbot__retry-button{align-items:center;display:flex}.q-chatbot__dialog-title{margin:.5rem 0}#comment-dialog .q-dialog__header,.hidden-input{display:none}.pulsing-dots{display:flex;align-items:center;justify-content:center;gap:.1rem}.dot{font-size:20px;line-height:1;animation:pulse 1s infinite;color:var(--q-theme-primary)}@keyframes pulse{0%,to{transform:scale(.8);opacity:.6}50%{transform:scale(1);opacity:1}}
1
+ .markdown-renderer pre,.markdown-renderer code{white-space:pre-wrap;word-break:break-word;overflow-wrap:break-word;overflow-x:auto}.markdown-renderer pre{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.markdown-renderer code{padding:.2rem .4rem;border-radius:4px;font-size:.875rem}.q-field-preview{position:relative;display:flex;flex-direction:column;margin:1rem .25rem}.q-field-preview__toolbar{z-index:1;display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:.1rem .2rem}.q-field-preview__content{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.q-field-preview__footer{display:flex;flex-direction:row;margin-top:.25rem}.q-field-preview:first-child{margin:0 1rem .25rem .25rem}.pulsing-dots{display:flex;align-items:center;justify-content:center;flex-direction:row;gap:.25rem}.generating-text{font-size:.9rem;color:var(--q-theme-primary)}.dots-container{display:flex;align-items:center;gap:.1rem}.dot{font-size:16px;line-height:1;animation:pulse 1s infinite;color:var(--q-theme-primary)}@keyframes pulse{0%,to{transform:scale(.8);opacity:.6}50%{transform:scale(1);opacity:1}}.q-chatbot{width:100%;display:flex;flex-direction:column;height:100%}.q-chatbot input{line-height:1.5rem}.q-chatbot .q-input-group .i-text__field{border-radius:0;flex:1}.q-chatbot__text p{margin:0}.q-chatbot__content{background-color:#fff;border:1px solid #eaebec;height:100%;width:100%;display:flex;gap:.75rem;flex-direction:column;overflow:hidden}.q-chatbot__footer-container{padding:.8rem 0 0}.q-chatbot__input-wrapper{display:flex;flex-direction:column;position:relative}.q-chatbot__image-preview{display:inline-flex;align-items:center;position:relative;margin-top:.5rem;width:fit-content}.q-chatbot__image-preview img{width:60px;height:60px;object-fit:cover;border-radius:4px;margin-right:.25rem;border:1px solid #eaebec;overflow:hidden}.q-chatbot__image-preview img:hover+.q-chatbot__remove-image,.q-chatbot__image-preview img:focus+.q-chatbot__remove-image{opacity:1;pointer-events:auto}.q-chatbot__image-preview img:focus{outline:solid rgb(var(--q-theme-info-rgb)/50%)}.q-chatbot .q-button.q-chatbot__remove-image{position:absolute;top:-5px;right:-5px;background-color:#00000080;color:#fff;border-radius:50%;padding:5px;font-size:12px;border:none;opacity:0;pointer-events:none;transition:opacity .2s ease-in-out}.q-chatbot .q-button.q-chatbot__remove-image:hover,.q-chatbot .q-button.q-chatbot__remove-image:focus{opacity:1;pointer-events:auto}.q-chatbot__send-container{padding-bottom:.25rem;display:flex;justify-content:space-between;width:100%}.q-chatbot__send-container .q-chatbot__send,.q-chatbot__send-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__send-container .spacer{flex-grow:1}.q-chatbot__footer{position:sticky;padding:0 .5rem;border:1px solid #eaebec;border-radius:.25rem;bottom:0;width:100%;display:flex;flex-direction:column;gap:.25rem}.q-chatbot__footer-disabled{background-color:rgb(var(--q-theme-neutral-light-rgb)/.25);cursor:not-allowed}.q-chatbot__footer.drag-over{border:2px dashed rgb(var(--q-theme-primary-rgb)/.25);background-color:#018bd20d}.q-chatbot__footer .q-chatbot__input{min-height:50px;max-height:100px;border-bottom:1px solid #eaebec;overflow-y:auto}.q-chatbot__footer .q-text-area{max-height:100%;overflow-y:auto}.q-chatbot__footer .q-text-area .q-field__control{border:none}.q-chatbot__upload-container{display:flex;justify-content:flex-start;padding:.25rem 0}.q-chatbot__upload-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__messages-container{display:flex;flex-direction:column;flex-grow:1;padding:0 1rem 2rem;gap:1.5rem;overflow-y:auto}.q-chatbot__messages-wrapper{display:flex;max-width:100%;gap:.2rem}.q-chatbot__tools{display:flex;flex-direction:row;justify-content:space-between;max-width:100%;padding:.25rem .5rem}.q-chatbot__message-wrapper{display:flex;flex-direction:column;gap:.2rem}.q-chatbot__message-container{display:flex;flex-direction:column;gap:.25rem}.q-chatbot__messages-wrapper_right{justify-content:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-container{align-items:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-wrapper{display:flex;align-items:flex-end}.q-chatbot__profile.q-icon__img{border-radius:50%;height:2rem;width:2rem}.q-chatbot__message{padding:.3rem .5rem;background-color:#eaebec;width:fit-content;min-height:2rem;white-space:normal;border-radius:0 .5rem .5rem}.q-chatbot__messages-wrapper_right .q-chatbot__message{background-color:#018bd233;border-radius:.5rem 0 .5rem .5rem;white-space:normal}.q-chatbot__sender{white-space:nowrap;color:#7c858d;font-size:.7rem}.q-chatbot__retry-button{align-items:center;display:flex}.q-chatbot__dialog-title{margin:.5rem 0}.hidden-input{display:none}
@@ -0,0 +1 @@
1
+ export declare function parseFieldValue(type: string, text: string): string | number | boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quidgest/chatbot",
3
3
  "private": false,
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
7
  "main": "dist/index.cjs",
@@ -32,9 +32,9 @@
32
32
  "lint:fix": "eslint . --fix && prettier --write --list-different src"
33
33
  },
34
34
  "dependencies": {
35
- "axios": "^1.7.0",
36
- "uuid": "^11.1.0",
37
- "vue-markdown-render": "^2.2.1"
35
+ "axios": "^1.9.0",
36
+ "markdown-it": "^14.1.0",
37
+ "uuid": "^11.1.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/markdown-it": "^14.1.2",
@@ -51,7 +51,7 @@
51
51
  "vue-tsc": "^1.8.27"
52
52
  },
53
53
  "peerDependencies": {
54
- "@quidgest/ui": "^0.16.0",
54
+ "@quidgest/ui": "0.16.11",
55
55
  "vue": "^3.5.13"
56
56
  }
57
57
  }
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 126.25 110.36"><defs><radialGradient id="radial-gradient" cx="45.24" cy="38.05" fx="45.24" fy="38.05" r="59.09" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#31319e"/><stop offset=".19" stop-color="#2e2e97"/><stop offset=".45" stop-color="#292985"/><stop offset=".75" stop-color="#202067"/><stop offset="1" stop-color="#17174a"/></radialGradient><linearGradient id="linear-gradient" x1="82.65" y1="50.41" x2="82.65" y2="77.5" gradientUnits="userSpaceOnUse"><stop offset=".02" stop-color="#66ebfe"/><stop offset=".96" stop-color="#00bfdb"/></linearGradient><linearGradient id="linear-gradient-2" x1="44.6" y1="50.01" x2="44.6" y2="77.1" xlink:href="#linear-gradient"/></defs><g isolation="isolate"><g id="Layer_1"><path d="M126.2,61.4c-.2-3.69-2.61-17.39-5.92-22.09-2.42-3.44-5.89-4.03-7.96-4.38-.13-.02-.27-.05-.41-.07-.16-.28-.32-.58-.45-.81-.63-1.14-1.03-1.87-1.44-2.44-6.96-9.88-14.91-16.51-24.85-20.69-.25-2.29-.84-5.24-3.37-7.44-2.93-2.55-7.58-2.9-11.68-3.21-.93-.07-1.82-.14-2.4-.22l-.33-.05h-8.61l-.36.05c-.65.1-1.61.17-2.63.24-3.75.27-8.01.57-10.98,2.85-2.94,2.26-3.58,5.55-3.79,7.84-9.71,3.94-17.86,10.71-24.85,20.62-.4.57-.81,1.3-1.43,2.44-.13.23-.29.53-.45.81-.14.02-.28.05-.4.07-2.07.35-5.54.94-7.96,4.39-2.25,3.19-3.84,9.89-4.58,13.59-.6,2.97-1.9,10.3-1.08,14.26.79,3.78,3.66,5.6,5.37,6.68.12.08.26.16.39.25,1.65,10.03,6.98,18.56,15.52,24.77,9.23,6.72,20.2,9.43,27.78,10.52,4.49.65,9.15.97,13.85.97,9.67,0,23.77-1.39,36-8.02,11.58-6.27,18.81-16,21.04-28.24,4.52-2.76,6.36-5.67,5.98-12.71Z" fill="#17174a"/><path d="M67.15,4.69c2.98.42,9.31.31,11.62,2.31,1.99,1.73,1.65,4.82,2.06,7.22,10.77,3.89,18.73,10.59,25.39,20.04.64.91,2.13,3.95,2.58,4.33,1.71,1.46,5.54.35,7.67,3.37,2.29,3.25,4.83,15.41,5.06,19.59.31,5.82-.76,6.9-5.58,9.44-3.28,26.16-29.7,34.5-52.68,34.5-4.6,0-9.06-.33-13.17-.93-18.13-2.61-37.11-12.11-39.51-33.08-1.7-1.92-4.96-2.54-5.55-5.39-.95-4.56,2.21-20.32,4.89-24.14,2.13-3.02,5.96-1.91,7.67-3.37.46-.39,1.94-3.42,2.58-4.33,6.54-9.28,14.55-16.22,25.21-19.87.85-.87-.3-5.45,2.43-7.55,2.52-1.94,8.35-1.69,11.42-2.16h7.91" fill="#fff"/><path d="M121.86,61.51c-.22-4.2-2.78-16.43-5.07-19.7-1.77-2.51-4.71-2.18-6.64-2.83.1,1.18.16,2.37.16,3.57,0,26.85-25.96,48.61-57.98,48.61-15.83,0-30.17-5.32-40.63-13.94,4.91,17.1,22.06,25.14,38.51,27.52,4.12.59,8.6.93,13.21.93,23.06,0,49.57-8.38,52.85-34.68,4.83-2.56,5.91-3.63,5.6-9.49Z" fill="#d0d7e2" mix-blend-mode="multiply" opacity=".4"/><path d="M62.91,97.68c5.82,0,11.67-.53,16.72-1.56,13.1-2.67,22.09-8.06,26.72-16.03,4.43-7.63,4.75-17.33.96-28.84-4.28-13-13.22-22.77-24.7-27.89-.13,4.51-3.84,8.16-8.38,8.16h-20.89c-4.61,0-8.39-3.78-8.39-8.39v-.14c-5.36,2.25-10.29,5.49-14.5,9.61-8.23,8.06-13.16,19.02-13.9,30.84-.59,9.44,2.67,17.53,9.43,23.42,6.27,5.46,15.68,9.02,27.2,10.3,3.13.35,6.43.52,9.73.52" fill="url(#radial-gradient)"/><rect x="74.34" y="50.41" width="16.62" height="27.09" rx="4.06" ry="4.06" fill="url(#linear-gradient)"/><rect x="36.29" y="50.01" width="16.62" height="27.09" rx="4.06" ry="4.06" fill="url(#linear-gradient-2)"/></g></g></svg>
@@ -1,7 +1,10 @@
1
+ @import '../../components/MarkdownRender/markdown-render.scss';
2
+ @import '../../components/FieldPreview/field-preview.scss';
3
+ @import '../../components/PulseDots/pulse-dots.scss';
4
+
1
5
  .q-chatbot {
2
6
  width: 100%;
3
7
  height: 100%;
4
- padding: 1rem;
5
8
  display: flex;
6
9
  flex-direction: column;
7
10
  height: 100%;
@@ -34,7 +37,7 @@
34
37
  }
35
38
 
36
39
  &__footer-container {
37
- padding: 0.8rem;
40
+ padding: .8rem 0 0 0;
38
41
  }
39
42
 
40
43
  &__input-wrapper {
@@ -61,7 +64,6 @@
61
64
 
62
65
  &:hover,
63
66
  &:focus {
64
-
65
67
  & + .q-chatbot__remove-image {
66
68
  opacity: 1;
67
69
  pointer-events: auto;
@@ -184,8 +186,9 @@
184
186
  &__tools {
185
187
  display: flex;
186
188
  flex-direction: row;
187
- justify-content: end;
189
+ justify-content: space-between;
188
190
  max-width: 100%;
191
+ padding: 0.25rem 0.5rem;
189
192
  }
190
193
 
191
194
  &__message-wrapper {
@@ -220,21 +223,18 @@
220
223
  }
221
224
 
222
225
  &__message {
223
- display: flex;
224
- align-items: center;
225
226
  padding: 0.3rem 0.5rem;
226
227
  background-color: #eaebec;
227
228
  width: fit-content;
228
- white-space: normal;
229
229
  min-height: 2rem;
230
- word-wrap: break-word;
231
- word-break: break-word;
230
+ white-space: normal;
232
231
  border-radius: 0 0.5rem 0.5rem 0.5rem;
233
232
  }
234
233
 
235
234
  &__messages-wrapper_right .q-chatbot__message {
236
235
  background-color: rgba(#018bd2, 20%);
237
236
  border-radius: 0.5rem 0 0.5rem 0.5rem;
237
+ white-space: normal;
238
238
  }
239
239
 
240
240
  &__sender {
@@ -248,43 +248,11 @@
248
248
  display: flex;
249
249
  }
250
250
 
251
- &__dialog-title{
251
+ &__dialog-title {
252
252
  margin: 0.5rem 0;
253
253
  }
254
254
  }
255
255
 
256
- #comment-dialog {
257
- .q-dialog__header {
258
- display: none;
259
- }
260
- }
261
-
262
256
  .hidden-input {
263
257
  display: none;
264
258
  }
265
-
266
- .pulsing-dots {
267
- display: flex;
268
- align-items: center;
269
- justify-content: center;
270
- gap: 0.1rem;
271
- }
272
-
273
- .dot {
274
- font-size: 20px;
275
- line-height: 1;
276
- animation: pulse 1s infinite;
277
- color: var(--q-theme-primary);
278
- }
279
-
280
- @keyframes pulse {
281
- 0%,
282
- 100% {
283
- transform: scale(0.8);
284
- opacity: 0.6;
285
- }
286
- 50% {
287
- transform: scale(1);
288
- opacity: 1;
289
- }
290
- }
@@ -0,0 +1,375 @@
1
+ <template>
2
+ <div class="q-chatbot">
3
+ <div class="q-chatbot__content">
4
+ <chat-tool-bar
5
+ :disabled="isDisabled"
6
+ :available-agents="props.availableAgents"
7
+ :selected-agent-key="currentAgentId"
8
+ @clear="clearChat"
9
+ @change-chat="changeChat" />
10
+
11
+ <div
12
+ ref="messagesContainer"
13
+ class="q-chatbot__messages-container"
14
+ @scroll="handleScroll">
15
+ <div
16
+ v-for="message in messages"
17
+ :key="message.id"
18
+ :class="getMessageClasses(message.sender)">
19
+ <chat-bot-message
20
+ v-bind="message"
21
+ :date-format="props.dateFormat"
22
+ :user-image="props.userImage"
23
+ :chatbot-image="props.chatbotImage"
24
+ :loading="isLoading && !message.message"
25
+ :imagePreviewUrl="message.imagePreviewUrl"
26
+ :apiEndpoint="props.apiEndpoint"
27
+ :sessionID="message.sessionID"
28
+ :fields="message.fields"
29
+ @apply-fields="applyFields" />
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="q-chatbot__footer-container">
35
+ <chat-bot-input
36
+ :disabled="isDisabled"
37
+ :loading="isLoading"
38
+ @send-message="sendMessage" />
39
+ </div>
40
+ </div>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ // Components
45
+ import { ChatBotMessage } from '@/components/ChatBotMessage'
46
+ import { ChatToolBar } from '@/components/ChatToolBar'
47
+ import { ChatBotInput } from '@/components/ChatBotInput'
48
+
49
+ // Utils
50
+ import {
51
+ onMounted,
52
+ nextTick,
53
+ ref,
54
+ watch,
55
+ computed,
56
+ onBeforeMount
57
+ } from 'vue'
58
+ import { v4 as uuidv4 } from 'uuid'
59
+
60
+ // Types
61
+ import type {
62
+ ChatBotProps,
63
+ ChatBotMessageContent,
64
+ ChatBotMessageSender,
65
+ FieldData,
66
+ AppliedFieldData
67
+ } from './types'
68
+ import type { ChatBotImage } from '../ChatBotInput'
69
+
70
+ // Assets
71
+ import ChatBotIcon from '@/assets/chatbot_profile.svg'
72
+ import UserIcon from '@/assets/user_avatar.png'
73
+
74
+ // Composables
75
+ import { useTexts } from '@/composables/useTexts'
76
+ import { useChatApi } from '@/composables/useChatApi'
77
+ import { useChatMessages } from '@/composables/useChatMessages'
78
+
79
+ const emits = defineEmits<{
80
+ (e: 'apply-fields', fields: AppliedFieldData[]): void
81
+ }>()
82
+
83
+ const props = withDefaults(defineProps<ChatBotProps>(), {
84
+ apiEndpoint: 'http://localhost:3000',
85
+ userImage: UserIcon,
86
+ chatbotImage: ChatBotIcon,
87
+ userPrompt: '',
88
+ availableAgents: () => [],
89
+ agentData: () => {
90
+ return {
91
+ id: '',
92
+ formId: '',
93
+ jobId: ''
94
+ }
95
+ }
96
+ })
97
+
98
+ const isChatDisabled = ref(true)
99
+ const messagesContainer = ref<HTMLElement | null>(null)
100
+ const autoScrollEnabled = ref(true)
101
+
102
+ const currentAgentId = ref(props.agentData.id)
103
+ const currentFormId = ref(props.agentData.formId)
104
+
105
+ // Composables
106
+ const texts = useTexts()
107
+ const {
108
+ isLoading,
109
+ clearChatData,
110
+ getChatData,
111
+ getFieldSuggestionData,
112
+ sendPrompt
113
+ } = useChatApi(props.apiEndpoint)
114
+ const { messages, addChatMessage, clearMessages, getLastMessage } =
115
+ useChatMessages()
116
+
117
+ const isDisabled = computed(() => {
118
+ return isChatDisabled.value || isLoading.value
119
+ })
120
+
121
+ onBeforeMount(() => {
122
+ messagesContainer.value?.removeEventListener('scroll', handleScroll)
123
+ })
124
+
125
+ onMounted(() => {
126
+ initChat()
127
+ })
128
+
129
+ function setDisabledState(state: boolean) {
130
+ isChatDisabled.value = state
131
+ }
132
+
133
+ async function initChat() {
134
+ try {
135
+ await loadChatData()
136
+ } catch (error) {
137
+ addChatMessage(texts.loginError)
138
+ console.error('Error logging in: ' + error)
139
+ }
140
+ }
141
+
142
+ async function loadChatData() {
143
+ setDisabledState(true)
144
+ const { data, error } = await getChatData(
145
+ props.username,
146
+ props.projectPath,
147
+ currentAgentId.value,
148
+ currentFormId.value
149
+ )
150
+
151
+ if (error || !data || !data.success) {
152
+ setDisabledState(true)
153
+ addChatMessage(texts.botIsSick)
154
+ console.log('Error loading chat data: ' + error)
155
+ return
156
+ }
157
+
158
+ setDisabledState(false)
159
+ sendInitialMessage()
160
+
161
+ data.history.forEach(async (message: ChatBotMessageContent) => {
162
+ const imgUrl = message.imageUrl
163
+ ? props.controllerEndpoint + message.imageUrl
164
+ : undefined
165
+
166
+ addChatMessage(
167
+ message.content,
168
+ message.type === 'ai' ? 'bot' : 'user',
169
+ imgUrl,
170
+ message.sessionID
171
+ )
172
+ })
173
+ }
174
+
175
+ function sendInitialMessage() {
176
+ const message = texts.initialMessage
177
+ addChatMessage(message, 'bot', null, undefined, true)
178
+ }
179
+
180
+ function resetChat() {
181
+ // Only reset UI state here, message state is handled by composable
182
+ clearMessages()
183
+ setDisabledState(false)
184
+ autoScrollEnabled.value = true
185
+ }
186
+
187
+ function scrollToBottom() {
188
+ nextTick(() => {
189
+ if (messagesContainer.value) {
190
+ messagesContainer.value.scrollTop =
191
+ messagesContainer.value.scrollHeight
192
+ }
193
+ })
194
+ }
195
+
196
+ function handleScroll() {
197
+ if (messagesContainer.value) {
198
+ const threshold = 20 // px threshold from the bottom
199
+ const { scrollTop, clientHeight, scrollHeight } =
200
+ messagesContainer.value
201
+ autoScrollEnabled.value =
202
+ scrollTop + clientHeight >= scrollHeight - threshold
203
+ }
204
+ }
205
+
206
+ async function sendMessage(prompt: string, image?: ChatBotImage) {
207
+ if (messagesContainer.value) {
208
+ messagesContainer.value.scrollTo({
209
+ top: messagesContainer.value.scrollHeight,
210
+ behavior: 'smooth'
211
+ })
212
+ }
213
+
214
+ addChatMessage(prompt, 'user', image?.previewUrl)
215
+ nextTick(() => {
216
+ if (autoScrollEnabled.value) scrollToBottom()
217
+ })
218
+ setChatPrompt(prompt, image?.file)
219
+ }
220
+
221
+ async function getAgentJob(jobId: string) {
222
+ if (messagesContainer.value) {
223
+ messagesContainer.value.scrollTo({
224
+ top: messagesContainer.value.scrollHeight,
225
+ behavior: 'smooth'
226
+ })
227
+ }
228
+
229
+ // Add an empty bot message to trigger bouncing dots animation
230
+ addChatMessage('', 'bot')
231
+ nextTick(() => {
232
+ if (autoScrollEnabled.value) scrollToBottom()
233
+ })
234
+
235
+ const msg = getLastMessage()
236
+ if (!msg) return
237
+
238
+ await getFieldSuggestionData(
239
+ jobId,
240
+ (chunk: string) => {
241
+ msg.message += chunk
242
+ if (!msg.fields) msg.fields = []
243
+
244
+ const lastField = msg.fields[msg.fields.length - 1]
245
+ lastField.text = msg.message
246
+
247
+ if (autoScrollEnabled.value) scrollToBottom()
248
+ },
249
+ (metadata: Record<string, unknown>) => {
250
+ msg.message = ''
251
+ const data = metadata as FieldData
252
+ msg.fields?.push({
253
+ type: data.type,
254
+ name: data.name,
255
+ text: data.text
256
+ })
257
+ }
258
+ )
259
+ }
260
+
261
+ async function setChatPrompt(prompt: string, image?: File) {
262
+ // Add an empty bot message marked as streaming to trigger bouncing dots animation
263
+ addChatMessage('', 'bot')
264
+ const msg = getLastMessage()
265
+ if (!msg) return
266
+
267
+ const currentSessionID = msg?.sessionID || uuidv4()
268
+
269
+ const formData = new FormData()
270
+ if (image) {
271
+ formData.append('image', image)
272
+ }
273
+
274
+ formData.append('message', prompt)
275
+ formData.append('project', props.projectPath)
276
+ formData.append('user', props.username)
277
+ formData.append('sessionID', currentSessionID)
278
+ formData.append('agentID', currentAgentId.value)
279
+ formData.append('formId', currentFormId.value)
280
+
281
+ await sendPrompt(
282
+ formData,
283
+ (chunk) => {
284
+ if (msg) msg.message += chunk
285
+
286
+ if (autoScrollEnabled.value) scrollToBottom()
287
+ },
288
+ (error) => {
289
+ setDisabledState(true)
290
+ addChatMessage(texts.botIsSick)
291
+ console.error('Error sending message: ' + error)
292
+ }
293
+ )
294
+ }
295
+
296
+ async function clearChat() {
297
+ const { data, error } = await clearChatData(
298
+ props.username,
299
+ props.projectPath,
300
+ currentAgentId.value,
301
+ currentFormId.value
302
+ )
303
+ if (error || !data || !data.success) {
304
+ setDisabledState(true)
305
+ addChatMessage(texts.loginError)
306
+ console.log('Error clearing chat: ' + error)
307
+ return
308
+ }
309
+
310
+ resetChat()
311
+ sendInitialMessage()
312
+ }
313
+
314
+ function getMessageClasses(sender: ChatBotMessageSender) {
315
+ const classes: string[] = ['q-chatbot__messages-wrapper']
316
+ if (sender === 'user') classes.push('q-chatbot__messages-wrapper_right')
317
+ return classes
318
+ }
319
+
320
+ function changeChat(chat: { formId: string; key: string }) {
321
+ ;(currentAgentId.value = chat.key), (currentFormId.value = chat.formId)
322
+ resetChat()
323
+ loadChatData()
324
+ }
325
+
326
+ function applyFields(fields: AppliedFieldData[]) {
327
+ if (!fields || fields.length === 0) return
328
+
329
+ emits('apply-fields', fields)
330
+ }
331
+
332
+ watch(
333
+ () => props.availableAgents,
334
+ (newValue, oldValue) => {
335
+ if (newValue.length === 0 && oldValue.length > 0) {
336
+ currentAgentId.value = ''
337
+ currentFormId.value = ''
338
+ resetChat()
339
+ initChat()
340
+ }
341
+ }
342
+ )
343
+
344
+ watch(
345
+ () => props.agentData,
346
+ async (newValue, oldValue) => {
347
+ if (isDisabled.value) return;
348
+
349
+ if (props.agentData?.id !== oldValue?.id) {
350
+ currentAgentId.value = newValue?.id || ''
351
+ currentFormId.value = newValue?.formId || ''
352
+ resetChat()
353
+ await initChat()
354
+ }
355
+
356
+ if (
357
+ newValue?.id !== '' &&
358
+ newValue &&
359
+ newValue.jobId !== oldValue.jobId
360
+ )
361
+ await getAgentJob(newValue.jobId)
362
+ }
363
+ )
364
+
365
+ watch(
366
+ () => [props.agentData.id, props.agentData.formId],
367
+ ([newId, newFormId]) => {
368
+ currentAgentId.value = newId
369
+ currentFormId.value = newFormId
370
+ },
371
+ { immediate: true }
372
+ )
373
+
374
+ defineOptions({ name: 'ChatBot' })
375
+ </script>
@@ -0,0 +1,55 @@
1
+ export type ChatBotProps = {
2
+ apiEndpoint?: string
3
+ controllerEndpoint?: string
4
+ username: string
5
+ projectPath: string
6
+ userImage?: string
7
+ chatbotImage?: string
8
+ dateFormat: string
9
+ agentData?: AgentData
10
+ availableAgents?: AvailableAgents[]
11
+ }
12
+
13
+ export type AvailableAgents = {
14
+ key: string
15
+ value: string
16
+ formId: string
17
+ }
18
+
19
+ export type AgentData = {
20
+ id: string
21
+ jobId: string
22
+ formId: string
23
+ }
24
+
25
+ export type FieldData = {
26
+ name: string
27
+ type: string
28
+ text: string
29
+ applied?: boolean
30
+ }
31
+
32
+ export type AppliedFieldData = {
33
+ name: string
34
+ text: unknown
35
+ }
36
+
37
+ export type ChatMessage = {
38
+ id: number
39
+ message: string
40
+ date: Date
41
+ sender: ChatBotMessageSender
42
+ sessionID: string
43
+ imagePreviewUrl?: string
44
+ isWelcomeMessage?: boolean
45
+ fields?: FieldData[]
46
+ }
47
+
48
+ export type ChatBotMessageContent = {
49
+ content: string
50
+ type: string
51
+ sessionID: string
52
+ imageUrl?: string
53
+ }
54
+
55
+ export type ChatBotMessageSender = 'bot' | 'user'