@meistrari/chat-nuxt 3.9.1 → 3.10.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 (87) hide show
  1. package/README.md +50 -440
  2. package/dist/module.d.mts +1 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +56 -3
  5. package/dist/runtime/assets/css/markstream.css +1 -1
  6. package/dist/runtime/conversations/components/chat-conversation-creator-filter.vue +11 -67
  7. package/dist/runtime/conversations/components/chat-conversation-item.d.vue.ts +2 -1
  8. package/dist/runtime/conversations/components/chat-conversation-item.vue +29 -33
  9. package/dist/runtime/conversations/components/chat-conversation-item.vue.d.ts +2 -1
  10. package/dist/runtime/conversations/components/chat-conversation-list.d.vue.ts +0 -1
  11. package/dist/runtime/conversations/components/chat-conversation-list.vue +117 -158
  12. package/dist/runtime/conversations/components/chat-conversation-list.vue.d.ts +0 -1
  13. package/dist/runtime/conversations/components/chat-conversation-new-group-button.vue +34 -73
  14. package/dist/runtime/conversations/components/chat-conversation-search.vue +1 -21
  15. package/dist/runtime/conversations/components/chat-widget-history-list.d.vue.ts +12 -0
  16. package/dist/runtime/conversations/components/chat-widget-history-list.vue +27 -0
  17. package/dist/runtime/conversations/components/chat-widget-history-list.vue.d.ts +12 -0
  18. package/dist/runtime/conversations/composables/conversation-groups.d.ts +1 -0
  19. package/dist/runtime/conversations/composables/conversation-groups.js +2 -0
  20. package/dist/runtime/conversations/composables/conversation-item.js +4 -4
  21. package/dist/runtime/conversations/composables/conversation-list.js +14 -5
  22. package/dist/runtime/embed/components/chat-configuration-modal.d.vue.ts +3 -3
  23. package/dist/runtime/embed/components/chat-configuration-modal.vue.d.ts +3 -3
  24. package/dist/runtime/embed/components/chat-embed-inner.d.vue.ts +6 -3
  25. package/dist/runtime/embed/components/chat-embed-inner.vue +365 -152
  26. package/dist/runtime/embed/components/chat-embed-inner.vue.d.ts +6 -3
  27. package/dist/runtime/embed/components/chat-embed.d.vue.ts +31 -4
  28. package/dist/runtime/embed/components/chat-embed.vue +10 -4
  29. package/dist/runtime/embed/components/chat-embed.vue.d.ts +31 -4
  30. package/dist/runtime/embed/components/chat-topbar.vue +83 -85
  31. package/dist/runtime/embed/components/meistrari-chat-embed.d.vue.ts +5 -3
  32. package/dist/runtime/embed/components/meistrari-chat-embed.vue +118 -57
  33. package/dist/runtime/embed/components/meistrari-chat-embed.vue.d.ts +5 -3
  34. package/dist/runtime/embed/components/mobile/chat-mobile-drawer.d.vue.ts +0 -1
  35. package/dist/runtime/embed/components/mobile/chat-mobile-drawer.vue +2 -45
  36. package/dist/runtime/embed/components/mobile/chat-mobile-drawer.vue.d.ts +0 -1
  37. package/dist/runtime/embed/components/mobile/chat-mobile-home-suggestions.d.vue.ts +9 -5
  38. package/dist/runtime/embed/components/mobile/chat-mobile-home-suggestions.vue +14 -25
  39. package/dist/runtime/embed/components/mobile/chat-mobile-home-suggestions.vue.d.ts +9 -5
  40. package/dist/runtime/embed/components/mobile/chat-mobile-shell.d.vue.ts +6 -6
  41. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue +2 -3
  42. package/dist/runtime/embed/components/mobile/chat-mobile-shell.vue.d.ts +6 -6
  43. package/dist/runtime/embed/feature-config.d.ts +7 -0
  44. package/dist/runtime/embed/types.d.ts +47 -8
  45. package/dist/runtime/files/icons.d.ts +1 -1
  46. package/dist/runtime/files/icons.js +1 -1
  47. package/dist/runtime/messages/components/chat-mention-dropdown.d.vue.ts +6 -1
  48. package/dist/runtime/messages/components/chat-mention-dropdown.vue +46 -22
  49. package/dist/runtime/messages/components/chat-mention-dropdown.vue.d.ts +6 -1
  50. package/dist/runtime/messages/components/chat-message-bubble.vue +53 -71
  51. package/dist/runtime/messages/components/chat-message-feedback.vue +25 -229
  52. package/dist/runtime/messages/components/chat-message-input.vue +176 -134
  53. package/dist/runtime/messages/components/chat-message-list.d.vue.ts +6 -0
  54. package/dist/runtime/messages/components/chat-message-list.vue +27 -23
  55. package/dist/runtime/messages/components/chat-message-list.vue.d.ts +6 -0
  56. package/dist/runtime/messages/components/chat-model-selector.vue +10 -22
  57. package/dist/runtime/messages/components/reasoning/chat-reasoning-message.vue +2 -3
  58. package/dist/runtime/messages/components/reasoning/chat-reasoning-thought.vue +60 -69
  59. package/dist/runtime/messages/components/reasoning/chat-thinking-indicator.vue +3 -9
  60. package/dist/runtime/messages/components/widgets/chat-bash-widget.vue +3 -12
  61. package/dist/runtime/messages/components/widgets/chat-doc-sdk-widget.vue +3 -12
  62. package/dist/runtime/messages/components/widgets/chat-generic-tool-widget.vue +3 -12
  63. package/dist/runtime/messages/components/widgets/chat-grep-widget.vue +7 -17
  64. package/dist/runtime/messages/components/widgets/chat-read-widget.vue +7 -17
  65. package/dist/runtime/messages/components/widgets/chat-tela-skill-widget.vue +4 -13
  66. package/dist/runtime/messages/components/widgets/chat-thinking-widget.vue +11 -22
  67. package/dist/runtime/messages/components/widgets/chat-web-search-widget.vue +13 -40
  68. package/dist/runtime/messages/components/widgets/chat-workstation-query-widget.vue +52 -65
  69. package/dist/runtime/messages/components/widgets/chat-write-widget.vue +8 -18
  70. package/dist/runtime/messages/composables/mention-autocomplete.js +4 -0
  71. package/dist/runtime/messages/message-reasoning.js +4 -0
  72. package/dist/runtime/messages/message-state.js +1 -0
  73. package/dist/runtime/public/files/generic.svg +2 -5
  74. package/dist/runtime/public/files/image.svg +2 -1
  75. package/dist/runtime/types/embed.d.ts +1 -1
  76. package/dist/runtime/types/schemas/chat/conversations.d.ts +2 -0
  77. package/dist/runtime/types/schemas/chat/conversations.js +3 -0
  78. package/dist/runtime/types/schemas/chat/messages.d.ts +4 -0
  79. package/dist/runtime/types/schemas/chat/messages.js +7 -0
  80. package/dist/types.d.mts +1 -1
  81. package/package.json +5 -4
  82. package/dist/runtime/conversations/components/chat-conversation-new-group-popover-content.d.vue.ts +0 -23
  83. package/dist/runtime/conversations/components/chat-conversation-new-group-popover-content.vue +0 -75
  84. package/dist/runtime/conversations/components/chat-conversation-new-group-popover-content.vue.d.ts +0 -23
  85. package/dist/runtime/messages/components/reasoning/chat-reasoning-step-item.d.vue.ts +0 -37
  86. package/dist/runtime/messages/components/reasoning/chat-reasoning-step-item.vue +0 -125
  87. package/dist/runtime/messages/components/reasoning/chat-reasoning-step-item.vue.d.ts +0 -37
@@ -5,7 +5,7 @@ import { useInputHistory } from "../composables/input-history";
5
5
  import { useMentionAutocomplete } from "../composables/mention-autocomplete";
6
6
  import { getAgentInputReadiness } from "../input-readiness";
7
7
  import { formatFileSize } from "../../files/formatting";
8
- import { getMimeIcon, getMimeLabel } from "../../files/icons";
8
+ import { getFileIcon, getMimeIcon, getMimeLabel } from "../../files/icons";
9
9
  const props = defineProps({
10
10
  disabled: { type: Boolean, required: false },
11
11
  loading: { type: Boolean, required: false },
@@ -28,7 +28,8 @@ const selectedModel = defineModel("selectedModel", { type: [String, null], ...{
28
28
  const { uploadFiles, uploading, uploadError } = useFileUpload();
29
29
  const isCancelEnabled = computed(() => props.showCancelButton === true);
30
30
  const content = ref("");
31
- const editorRef = ref(null);
31
+ const promptTextareaRef = ref(null);
32
+ const editorRef = computed(() => promptTextareaRef.value?.el ?? null);
32
33
  const fileInputRef = ref(null);
33
34
  const pendingFiles = ref([]);
34
35
  const selectedUploadInputName = ref(void 0);
@@ -69,6 +70,7 @@ const fileItems = computed(() => {
69
70
  label: f.name,
70
71
  description: getMimeLabel(f.mimeType),
71
72
  icon: getMimeIcon(f.mimeType),
73
+ iconUrl: getFileIcon(f.mimeType),
72
74
  category: "file"
73
75
  });
74
76
  }
@@ -81,6 +83,7 @@ const fileItems = computed(() => {
81
83
  label: f.name,
82
84
  description: "Contexto",
83
85
  icon: getMimeIcon(f.mimeType),
86
+ iconUrl: getFileIcon(f.mimeType),
84
87
  category: "file"
85
88
  });
86
89
  }
@@ -94,6 +97,7 @@ const fileItems = computed(() => {
94
97
  label: f.name,
95
98
  description: "Contexto do agente",
96
99
  icon: getMimeIcon(),
100
+ iconUrl: getFileIcon(),
97
101
  category: "file",
98
102
  idKind: "path"
99
103
  });
@@ -150,6 +154,16 @@ const mentionAutocomplete = useMentionAutocomplete({
150
154
  const canSend = computed(
151
155
  () => content.value.trim().length > 0 && !isLocked.value && !uploading.value && !props.agentInputSchemaLoading && agentInputReadiness.value.ready
152
156
  );
157
+ const submitStatus = computed(() => {
158
+ if (uploading.value)
159
+ return "uploading";
160
+ if (props.loading && isCancelEnabled.value)
161
+ return "streaming";
162
+ return "ready";
163
+ });
164
+ const promptShellLocked = computed(
165
+ () => isLocked.value && submitStatus.value !== "streaming"
166
+ );
153
167
  watch(agentInputVariables, (variables) => {
154
168
  const variableNames = new Set(variables.map((input) => input.name));
155
169
  agentInputTexts.value = Object.fromEntries(
@@ -407,10 +421,45 @@ function handlePaste(e) {
407
421
  sel.addRange(range);
408
422
  handleInput();
409
423
  }
410
- function focusEditor() {
411
- if (isLocked.value)
424
+ function insertMentionFromMenu(item) {
425
+ const editor = editorRef.value;
426
+ if (!editor || isLocked.value)
412
427
  return;
413
- editorRef.value?.focus();
428
+ const span = document.createElement("span");
429
+ span.className = `mention-tag mention-tag-${item.category}`;
430
+ span.contentEditable = "false";
431
+ span.dataset.mentionId = item.id;
432
+ span.dataset.mentionCategory = item.category;
433
+ span.dataset.mentionLabel = item.label;
434
+ if (item.idKind)
435
+ span.dataset.mentionIdKind = item.idKind;
436
+ if (item.description)
437
+ span.dataset.mentionDescription = item.description;
438
+ if (item.variables)
439
+ span.dataset.mentionVariables = JSON.stringify(item.variables);
440
+ const iconClass = item.category === "file" ? "i-ph-note-blank" : item.category === "canvas" ? "i-ph-wrench" : "i-ph-hexagon";
441
+ const icon = document.createElement("span");
442
+ icon.className = iconClass;
443
+ icon.setAttribute("aria-hidden", "true");
444
+ span.appendChild(icon);
445
+ span.appendChild(document.createTextNode(item.label));
446
+ const lastChild = editor.lastChild;
447
+ const needsSpaceBefore = lastChild?.nodeType === Node.TEXT_NODE ? !/[\s\u00A0]$/.test(lastChild.textContent ?? "") : lastChild !== null;
448
+ if (needsSpaceBefore)
449
+ editor.appendChild(document.createTextNode(" "));
450
+ editor.appendChild(span);
451
+ const space = document.createTextNode("\xA0");
452
+ editor.appendChild(space);
453
+ const range = document.createRange();
454
+ range.setStartAfter(space);
455
+ range.collapse(true);
456
+ const sel = window.getSelection();
457
+ sel?.removeAllRanges();
458
+ sel?.addRange(range);
459
+ handleInput();
460
+ nextTick(() => {
461
+ editorRef.value?.focus();
462
+ });
414
463
  }
415
464
  function addFiles(files, inputName) {
416
465
  const validFiles = files.filter((file) => {
@@ -445,7 +494,7 @@ defineExpose({
445
494
 
446
495
  <template>
447
496
  <div :class="centered ? 'w-full' : hasDisclaimer ? 'px-24px' : 'px-24px pb-24px'" bg-transparent>
448
- <div :class="centered ? 'w-full max-w-700px' : 'max-w-800px'" mx-auto>
497
+ <div :class="centered ? 'w-full' : ''" max-w-700px mx-auto>
449
498
  <Transition name="agent-inputs-panel-motion" mode="out-in" appear>
450
499
  <div
451
500
  v-if="hasAgentInputs"
@@ -554,53 +603,6 @@ defineExpose({
554
603
  </Transition>
555
604
  </div>
556
605
  </Transition>
557
- <div v-if="pendingFiles.length > 0" flex flex-wrap gap-8px mb-12px>
558
- <div
559
- v-for="pendingFile in pendingFiles"
560
- :key="pendingFile.id"
561
- flex items-center gap-8px flex-wrap
562
- px-12px py-8px
563
- rounded-8px
564
- bg-gray-100
565
- b=".5px gray-200"
566
- >
567
- <div i-ph-file text-gray-600 text-16px />
568
- <div flex="~ col" gap-2px>
569
- <span body-12-medium text-black-700 max-w-150px truncate>{{ pendingFile.file.name }}</span>
570
- <span body-11-regular text-gray-500>{{ formatFileSize(pendingFile.file.size) }}</span>
571
- </div>
572
- <select
573
- v-if="fileInputVariables.length > 0"
574
- class="agent-file-variable-select"
575
- :value="pendingFile.inputName ?? ''"
576
- :disabled="isLocked"
577
- @change="updatePendingFileInput(pendingFile.id, $event)"
578
- @click.stop
579
- >
580
- <option value="" disabled>
581
- Selecionar variável
582
- </option>
583
- <option
584
- v-for="input in fileInputVariables"
585
- :key="input.id || input.name"
586
- :value="input.name"
587
- >
588
- {{ input.name }}
589
- </option>
590
- </select>
591
- <TelaIconButton
592
- p-4px
593
- icon="i-ph-x"
594
- rounded-4px
595
- color="secondary"
596
- hover:bg-gray-200
597
- transition
598
- icon-class="text-gray-500 text-12px"
599
- @click="removeFile(pendingFile.id)"
600
- />
601
- </div>
602
- </div>
603
-
604
606
  <div v-if="hasUnmappedAgentFiles" text-orange-600 body-12-regular mb-8px>
605
607
  Selecione uma variável para cada arquivo anexado.
606
608
  </div>
@@ -617,102 +619,142 @@ defineExpose({
617
619
  @change="handleFileSelect"
618
620
  >
619
621
 
620
- <div relative>
622
+ <!-- Deliberately NOT position:relative — TelaChatBody pins
623
+ [data-chat-prompt-input] with absolute left-0 right-0 against
624
+ its nearest positioned ancestor; a positioned wrapper here
625
+ would shrink the input. The dropdown positions itself fixed
626
+ from the caret rect, so it needs no relative ancestor. -->
627
+ <div>
621
628
  <ChatMentionDropdown
622
629
  v-if="mentionAutocomplete.isOpen"
623
630
  :items="mentionAutocomplete.filteredItems"
624
631
  :highlighted-index="mentionAutocomplete.highlightedIndex"
625
632
  :trigger="mentionAutocomplete.activeTrigger"
626
633
  :query="mentionAutocomplete.query"
634
+ :caret-rect="mentionAutocomplete.triggerCaretRect"
627
635
  @select="mentionAutocomplete.selectItem"
628
636
  />
629
- </div>
630
637
 
631
- <div
632
- flex="~ col"
633
- gap-8px
634
- p-16px
635
- rounded-16px
636
- class="input-container"
637
- transition
638
- :class="[
639
- content.length > 0 && !isLocked && 'b-gray-300',
640
- !isLocked && (centered ? 'bg-white' : 'bg-transparent'),
641
- isLocked ? 'input-locked cursor-not-allowed' : 'cursor-text'
642
- ]"
643
- @click="focusEditor"
644
- >
645
- <div
646
- ref="editorRef"
647
- class="editor-content custom-scrollbar"
648
- :class="{ 'is-empty': !content.trim() }"
649
- data-placeholder="Escreva sua mensagem, @ para adicionar arquivos, / para skills"
650
- w-full
651
- min-h-20px
652
- text-16px
653
- font-normal
654
- leading-20px
655
- tracking--0.2px
656
- focus:outline-none
657
- :contenteditable="!isLocked"
658
- @keydown="handleKeydown"
659
- @input="handleInput"
660
- @paste="handlePaste"
661
- @blur="handleBlur"
662
- />
663
- <div flex items-center gap-8px>
664
- <ChatModelSelector
665
- v-model="selectedModel"
638
+ <TelaChatPromptInput :locked="promptShellLocked" :class="centered && 'bg-white'">
639
+ <TelaChatPromptInputAttachments v-if="pendingFiles.length > 0">
640
+ <TelaChatPromptInputAttachment
641
+ v-for="pendingFile in pendingFiles"
642
+ :key="pendingFile.id"
643
+ :name="pendingFile.file.name"
644
+ :icon="getFileIcon(pendingFile.file.type)"
645
+ :meta="formatFileSize(pendingFile.file.size)"
646
+ remove-label="Remover anexo"
647
+ @remove="removeFile(pendingFile.id)"
648
+ >
649
+ <select
650
+ v-if="fileInputVariables.length > 0"
651
+ class="agent-file-variable-select"
652
+ :value="pendingFile.inputName ?? ''"
653
+ :disabled="isLocked"
654
+ @change="updatePendingFileInput(pendingFile.id, $event)"
655
+ @click.stop
656
+ >
657
+ <option value="" disabled>
658
+ Selecionar variável
659
+ </option>
660
+ <option
661
+ v-for="input in fileInputVariables"
662
+ :key="input.id || input.name"
663
+ :value="input.name"
664
+ >
665
+ {{ input.name }}
666
+ </option>
667
+ </select>
668
+ </TelaChatPromptInputAttachment>
669
+ </TelaChatPromptInputAttachments>
670
+
671
+ <TelaChatPromptInputTextarea
672
+ ref="promptTextareaRef"
673
+ placeholder="Escreva sua mensagem..."
674
+ :empty="!content.trim()"
675
+ :flush="pendingFiles.length > 0"
666
676
  :disabled="isLocked"
667
- @click.stop
677
+ @keydown="handleKeydown"
678
+ @input="handleInput"
679
+ @paste="handlePaste"
680
+ @blur="handleBlur"
668
681
  />
669
- <div flex-1 />
670
- <TelaTooltip :content="pendingFiles.length >= MAX_FILES ? 'Limite de arquivos atingido' : 'Anexar arquivo'" side="top">
671
- <TelaIconButton
672
- icon="i-ph-paperclip"
673
- size="md"
674
- color="secondary"
675
- class="rounded-8px! p-6px! transition-colors" :class="[
676
- !isLocked && 'hover:bg-gray-50!'
677
- ]"
678
- icon-class="text-gray-300 text-20px"
679
- :disabled="isLocked || pendingFiles.length >= MAX_FILES"
680
- @click="openFileDialog"
681
- />
682
- </TelaTooltip>
683
- <TelaTooltip :content="loading && isCancelEnabled ? 'Parar gera\xE7\xE3o' : 'Enter para enviar'" side="top">
684
- <button
685
- v-if="loading && isCancelEnabled"
686
- class="stop-button"
687
- @click="emit('cancel')"
688
- >
689
- <span class="stop-button-ring" />
690
- <span i-ph-stop-fill text-14px text-gray-900 />
691
- </button>
692
- <TelaIconButton
693
- v-else
694
- :icon="uploading ? 'i-ph-circle-notch' : 'i-ph-arrow-up'"
695
- size="md"
696
- color="secondary"
697
- class="rounded-10px! p-6px! transition-colors"
698
- :class="[
699
- canSend ? 'bg-black-900! text-white! hover:bg-black-800!' : 'bg-gray-100! hover:bg-gray-200!',
700
- uploading ? 'animate-spin' : ''
701
- ]"
702
- :icon-class="[
703
- 'text-20px',
704
- canSend ? 'text-white' : 'text-gray-400'
705
- ].join(' ')"
706
- :disabled="!canSend"
707
- @click="handleSend"
708
- />
709
- </TelaTooltip>
710
- </div>
682
+
683
+ <TelaChatPromptInputToolbar>
684
+ <TelaChatPromptInputTools>
685
+ <TelaChatPromptInputActionMenu>
686
+ <TelaChatPromptInputActionMenuTrigger :locked="isLocked" label="Abrir menu de ações" />
687
+ <TelaChatPromptInputActionMenuContent>
688
+ <TelaChatPromptInputActionMenuItem
689
+ icon="i-ph-paperclip"
690
+ :disabled="isLocked || pendingFiles.length >= MAX_FILES"
691
+ @click="openFileDialog()"
692
+ >
693
+ Adicionar arquivos ou fotos
694
+ </TelaChatPromptInputActionMenuItem>
695
+ <TelaChatPromptInputActionMenuSub>
696
+ <TelaChatPromptInputActionMenuSubTrigger icon="i-ph-clock-counter-clockwise">
697
+ Arquivos recentes
698
+ </TelaChatPromptInputActionMenuSubTrigger>
699
+ <TelaChatPromptInputActionMenuSubContent>
700
+ <TelaChatPromptInputActionMenuItem v-if="!fileItems.length" class="text-center" disabled>
701
+ Nenhum arquivo recente
702
+ </TelaChatPromptInputActionMenuItem>
703
+ <TelaChatPromptInputActionMenuItem
704
+ v-for="file in fileItems"
705
+ :key="file.id"
706
+ @click="insertMentionFromMenu(file)"
707
+ >
708
+ <img :src="file.iconUrl" class="w-16px h-16px mr-8px shrink-0" alt="">
709
+ <span class="min-w-0 max-w-200px truncate">{{ file.label }}</span>
710
+ </TelaChatPromptInputActionMenuItem>
711
+ </TelaChatPromptInputActionMenuSubContent>
712
+ </TelaChatPromptInputActionMenuSub>
713
+ <TelaMenubarSeparator />
714
+ <TelaChatPromptInputActionMenuSub>
715
+ <TelaChatPromptInputActionMenuSubTrigger icon="i-ph-notepad" icon-size="16px">
716
+ Skills
717
+ </TelaChatPromptInputActionMenuSubTrigger>
718
+ <TelaChatPromptInputActionMenuSubContent>
719
+ <TelaChatPromptInputActionMenuItem v-if="!skillItems.length" class="text-center" disabled>
720
+ Nenhuma skill disponível
721
+ </TelaChatPromptInputActionMenuItem>
722
+ <TelaChatPromptInputActionMenuItem
723
+ v-for="skill in skillItems"
724
+ :key="skill.id"
725
+ @click="insertMentionFromMenu(skill)"
726
+ >
727
+ <span class="min-w-0 max-w-200px truncate">{{ skill.label }}</span>
728
+ </TelaChatPromptInputActionMenuItem>
729
+ </TelaChatPromptInputActionMenuSubContent>
730
+ </TelaChatPromptInputActionMenuSub>
731
+ </TelaChatPromptInputActionMenuContent>
732
+ </TelaChatPromptInputActionMenu>
733
+ </TelaChatPromptInputTools>
734
+
735
+ <TelaChatPromptInputActions>
736
+ <ChatModelSelector
737
+ v-model="selectedModel"
738
+ :disabled="isLocked"
739
+ @click.stop
740
+ />
741
+ <TelaChatPromptSubmitButton
742
+ :status="submitStatus"
743
+ :disabled="!canSend"
744
+ send-label="Enviar mensagem"
745
+ stop-label="Parar geração"
746
+ uploading-label="Enviando anexos"
747
+ @send="handleSend"
748
+ @stop="emit('cancel')"
749
+ />
750
+ </TelaChatPromptInputActions>
751
+ </TelaChatPromptInputToolbar>
752
+ </TelaChatPromptInput>
711
753
  </div>
712
754
  </div>
713
755
  </div>
714
756
  </template>
715
757
 
716
758
  <style scoped>
717
- .input-container{border:.5px solid #e5e7eb;box-shadow:0 3px 10px 0 hsla(0,0%,64%,.08),0 12px 20px 0 hsla(0,0%,64%,.06);position:relative;z-index:2}.input-locked{background:var(--Base-Gray-gray-100,#f3f4f6);border:.5px solid var(--Base-Neutral-neutral-300,#d9d9d9);box-shadow:0 12px 20px 0 hsla(0,0%,64%,.06)}.agent-inputs-panel{box-shadow:0 3px 10px 0 hsla(0,0%,64%,.06);position:relative;transform-origin:bottom center;will-change:max-height,opacity,transform,filter;z-index:1}.agent-inputs-panel-motion-enter-active,.agent-inputs-panel-motion-leave-active{overflow:hidden;transition:max-height .26s cubic-bezier(.2,.8,.2,1),opacity .18s ease,transform .26s cubic-bezier(.2,.8,.2,1),filter .22s ease}.agent-inputs-panel-motion-enter-from,.agent-inputs-panel-motion-leave-to{filter:blur(2px);max-height:0;opacity:0;transform:translateY(46px) scale(.985)}.agent-inputs-panel-motion-enter-to,.agent-inputs-panel-motion-leave-from{filter:blur(0);max-height:560px;opacity:1;transform:translateY(0) scale(1)}.agent-inputs-content{transform-origin:bottom center;will-change:max-height,opacity,transform,filter}.agent-inputs-content-enter-active,.agent-inputs-content-leave-active{overflow:hidden;transition:max-height .22s cubic-bezier(.2,.8,.2,1),opacity .16s ease,transform .22s cubic-bezier(.2,.8,.2,1),filter .22s ease}.agent-inputs-content-enter-from,.agent-inputs-content-leave-to{filter:blur(2px);max-height:0;opacity:0;transform:translateY(14px) scale(.985)}.agent-inputs-content-enter-to,.agent-inputs-content-leave-from{filter:blur(0);max-height:900px;opacity:1;transform:translateY(0) scale(1)}.agent-text-input{border:.5px solid #e5e7eb;border-radius:8px;color:#111827;font-size:13px;line-height:18px;min-height:64px;outline:none;padding:8px 10px;resize:vertical;width:100%}.agent-text-input:focus{border-color:#d1d5db}.agent-text-input:disabled{background-color:#f3f4f6;cursor:not-allowed}.agent-file-variable-select{background:#fff;border:.5px solid #d1d5db;border-radius:6px;color:#374151;font-size:12px;height:28px;max-width:160px;outline:none;padding:0 8px}.editor-content{caret-color:#111827;color:#111827;max-height:200px;outline:none;overflow-y:auto;white-space:pre-wrap;word-break:break-word}.editor-content.is-empty:before{color:#bdc4cc;content:attr(data-placeholder);float:left;height:0;pointer-events:none}.icon-button:disabled{cursor:not-allowed;opacity:.5}.custom-scrollbar{scrollbar-color:#d1d5db transparent;scrollbar-width:thin}.custom-scrollbar::-webkit-scrollbar{width:6px}.custom-scrollbar::-webkit-scrollbar-track{background:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{background-color:#d1d5db;border-radius:3px}@media (max-width:480px){.custom-scrollbar{scrollbar-width:none}.custom-scrollbar::-webkit-scrollbar{display:none}}.stop-button{align-items:center;background:transparent;border:none;cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;position:relative;transition:opacity .15s ease;width:32px}.stop-button:hover{opacity:.8}.stop-button-ring{border:2px solid #e5e7eb;border-radius:50%;inset:0;position:absolute}.stop-button-ring:before{animation:spin 1s linear infinite;border:2px solid transparent;border-radius:50%;border-top-color:#111827;content:"";inset:-2px;position:absolute}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}
759
+ .agent-inputs-panel{box-shadow:0 3px 10px 0 hsla(0,0%,64%,.06);position:relative;transform-origin:bottom center;will-change:max-height,opacity,transform,filter;z-index:1}.agent-inputs-panel-motion-enter-active,.agent-inputs-panel-motion-leave-active{overflow:hidden;transition:max-height .26s cubic-bezier(.2,.8,.2,1),opacity .18s ease,transform .26s cubic-bezier(.2,.8,.2,1),filter .22s ease}.agent-inputs-panel-motion-enter-from,.agent-inputs-panel-motion-leave-to{filter:blur(2px);max-height:0;opacity:0;transform:translateY(46px) scale(.985)}.agent-inputs-panel-motion-enter-to,.agent-inputs-panel-motion-leave-from{filter:blur(0);max-height:560px;opacity:1;transform:translateY(0) scale(1)}.agent-inputs-content{transform-origin:bottom center;will-change:max-height,opacity,transform,filter}.agent-inputs-content-enter-active,.agent-inputs-content-leave-active{overflow:hidden;transition:max-height .22s cubic-bezier(.2,.8,.2,1),opacity .16s ease,transform .22s cubic-bezier(.2,.8,.2,1),filter .22s ease}.agent-inputs-content-enter-from,.agent-inputs-content-leave-to{filter:blur(2px);max-height:0;opacity:0;transform:translateY(14px) scale(.985)}.agent-inputs-content-enter-to,.agent-inputs-content-leave-from{filter:blur(0);max-height:900px;opacity:1;transform:translateY(0) scale(1)}.agent-text-input{border:.5px solid #e5e7eb;border-radius:8px;color:#111827;font-size:13px;line-height:18px;min-height:64px;outline:none;padding:8px 10px;resize:vertical;width:100%}.agent-text-input:focus{border-color:#d1d5db}.agent-text-input:disabled{background-color:#f3f4f6;cursor:not-allowed}.agent-file-variable-select{background:#fff;border:.5px solid #d1d5db;border-radius:6px;color:#374151;font-size:12px;height:28px;max-width:160px;outline:none;padding:0 8px}
718
760
  </style>
@@ -2,6 +2,12 @@ import type { Message } from '../../types/schemas/chat/conversations.js';
2
2
  type __VLS_Props = {
3
3
  messages: readonly Message[];
4
4
  loading?: boolean;
5
+ /**
6
+ * Where the list renders: `page`/`widget` sit inside a `TelaChatBody` that
7
+ * owns the conversation padding; `mobile` renders standalone and keeps its
8
+ * own spacing. Defaults to `page`.
9
+ */
10
+ variant?: 'page' | 'widget' | 'mobile';
5
11
  };
6
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
7
13
  retry: (messageId: string) => any;
@@ -1,10 +1,11 @@
1
1
  <script setup>
2
2
  const props = defineProps({
3
3
  messages: { type: Array, required: true },
4
- loading: { type: Boolean, required: false }
4
+ loading: { type: Boolean, required: false },
5
+ variant: { type: String, required: false }
5
6
  });
6
7
  const emit = defineEmits(["retry"]);
7
- const containerRef = ref(null);
8
+ const conversationRef = ref(null);
8
9
  const skeletonMessages = [
9
10
  { id: "user-1", align: "end", width: "58%", height: "44px" },
10
11
  { id: "assistant-1", align: "start", width: "92%", height: "116px" },
@@ -12,46 +13,49 @@ const skeletonMessages = [
12
13
  { id: "assistant-2", align: "start", width: "84%", height: "92px" }
13
14
  ];
14
15
  function scrollToBottom() {
15
- nextTick(() => {
16
- if (containerRef.value) {
17
- containerRef.value.scrollTop = containerRef.value.scrollHeight;
18
- }
19
- });
16
+ conversationRef.value?.scrollToBottom();
20
17
  }
18
+ const hasPendingMessage = computed(
19
+ () => props.messages.some((message) => message.status === "pending")
20
+ );
21
21
  watch(() => props.messages.length, scrollToBottom);
22
22
  onMounted(scrollToBottom);
23
23
  </script>
24
24
 
25
25
  <template>
26
- <div
27
- ref="containerRef"
28
- flex-1
29
- overflow-y-auto
30
- pt-72px pb-24px px-16px
26
+ <TelaChatConversation
27
+ ref="conversationRef"
28
+ :class="{
29
+ 'pt-72px!': (variant ?? 'page') === 'page',
30
+ 'pt-72px pb-24px px-16px': variant === 'mobile'
31
+ }"
31
32
  >
32
- <div max-w-800px mx-auto>
33
- <div v-if="loading" flex="~ col" gap-16px>
33
+ <TelaChatConversationContent class="max-w-700px!">
34
+ <template v-if="loading">
34
35
  <div
35
36
  v-for="message in skeletonMessages"
36
37
  :key="message.id"
37
38
  flex
38
39
  :class="message.align === 'end' ? 'justify-end' : 'justify-start'"
39
40
  >
40
- <TelaSkeleton
41
- rounded-16px
42
- :style="{ width: message.width, height: message.height }"
43
- />
41
+ <TelaSkeleton :style="{ width: message.width, height: message.height }" />
44
42
  </div>
45
- </div>
43
+ </template>
46
44
 
47
- <div v-else flex="~ col" gap-16px>
45
+ <template v-else>
48
46
  <ChatMessageBubble
49
47
  v-for="message in messages"
50
48
  :key="message.id"
51
49
  :message="message"
52
50
  @retry="emit('retry', $event)"
53
51
  />
54
- </div>
55
- </div>
56
- </div>
52
+ <p
53
+ v-if="messages.length > 0 && !hasPendingMessage"
54
+ text-center body-11-regular text-tertiary
55
+ >
56
+ As respostas podem conter erros. Verifique documentos originais.
57
+ </p>
58
+ </template>
59
+ </TelaChatConversationContent>
60
+ </TelaChatConversation>
57
61
  </template>
@@ -2,6 +2,12 @@ import type { Message } from '../../types/schemas/chat/conversations.js';
2
2
  type __VLS_Props = {
3
3
  messages: readonly Message[];
4
4
  loading?: boolean;
5
+ /**
6
+ * Where the list renders: `page`/`widget` sit inside a `TelaChatBody` that
7
+ * owns the conversation padding; `mobile` renders standalone and keeps its
8
+ * own spacing. Defaults to `page`.
9
+ */
10
+ variant?: 'page' | 'widget' | 'mobile';
5
11
  };
6
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
7
13
  retry: (messageId: string) => any;
@@ -1,16 +1,16 @@
1
1
  <script setup>
2
2
  import { chatModelOptions } from "../../types/schemas/chat/ui.js";
3
- import AnthropicIcon from "../../shared/components/icons/anthropic.vue";
4
- import GeminiIcon from "../../shared/components/icons/gemini.vue";
5
- import OpenAiIcon from "../../shared/components/icons/openai.vue";
3
+ import TelaChatIconsAnthropic from "@meistrari/tela-build/components/tela/chat/icons/anthropic.vue";
4
+ import TelaChatIconsGemini from "@meistrari/tela-build/components/tela/chat/icons/gemini.vue";
5
+ import TelaChatIconsOpenai from "@meistrari/tela-build/components/tela/chat/icons/openai.vue";
6
6
  defineProps({
7
7
  disabled: { type: Boolean, required: false }
8
8
  });
9
9
  const model = defineModel({ type: [String, null], ...{ default: null } });
10
10
  const providerIcons = {
11
- Anthropic: AnthropicIcon,
12
- OpenAI: OpenAiIcon,
13
- Google: GeminiIcon
11
+ Anthropic: TelaChatIconsAnthropic,
12
+ OpenAI: TelaChatIconsOpenai,
13
+ Google: TelaChatIconsGemini
14
14
  };
15
15
  const options = chatModelOptions.map((option) => ({
16
16
  value: option.value,
@@ -30,27 +30,15 @@ const tabLabels = {
30
30
  cheap: "Econ\xF4micos",
31
31
  multimodal: "Multimodal"
32
32
  };
33
- function getTabLabel(tab) {
34
- return tabLabels[tab] ?? tab;
35
- }
36
33
  </script>
37
34
 
38
35
  <template>
39
- <TelaCombobox
40
- :model-value="model ?? void 0"
36
+ <TelaChatModelSelector
37
+ v-model="model"
41
38
  :options="options"
42
- :class="disabled && 'opacity-50 pointer-events-none'"
39
+ :disabled="disabled"
43
40
  placeholder="Modelo"
44
41
  input-placeholder="Buscar modelo..."
45
- :get-tab-label="getTabLabel"
46
- trigger-class="border-none!"
47
- label-class="!text-14px !leading-18px !font-normal -tracking-0.01em! !text-gray-500"
48
- icon-class="scale-110 mr-1px !text-gray-500"
49
- label-item-class="!text-gray-500"
50
- @update:model-value="model = $event ?? null"
42
+ :tab-labels="tabLabels"
51
43
  />
52
44
  </template>
53
-
54
- <style>
55
- [role=option] svg{color:#6b7280}
56
- </style>
@@ -5,7 +5,6 @@ import ChatDocSdkWidget from "../widgets/chat-doc-sdk-widget.vue";
5
5
  import ChatGenericToolWidget from "../widgets/chat-generic-tool-widget.vue";
6
6
  import ChatGrepWidget from "../widgets/chat-grep-widget.vue";
7
7
  import ChatReadWidget from "../widgets/chat-read-widget.vue";
8
- import ChatReasoningStepItem from "./chat-reasoning-step-item.vue";
9
8
  import ChatTelaSkillWidget from "../widgets/chat-tela-skill-widget.vue";
10
9
  import ChatThinkingWidget from "../widgets/chat-thinking-widget.vue";
11
10
  import ChatWebSearchWidget from "../widgets/chat-web-search-widget.vue";
@@ -64,7 +63,7 @@ const isTitleSameAsContent = computed(() => {
64
63
  </script>
65
64
 
66
65
  <template>
67
- <ChatReasoningStepItem
66
+ <TelaChatReasoningStep
68
67
  :title="stepTitle"
69
68
  :icon="stepIcon"
70
69
  :is-first="isFirst"
@@ -98,5 +97,5 @@ const isTitleSameAsContent = computed(() => {
98
97
  />
99
98
  </div>
100
99
  </div>
101
- </ChatReasoningStepItem>
100
+ </TelaChatReasoningStep>
102
101
  </template>