@meistrari/chat-nuxt 1.11.0 → 1.13.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 (89) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +15 -0
  3. package/dist/runtime/components/chat/conversation-item.d.vue.ts +6 -1
  4. package/dist/runtime/components/chat/conversation-item.vue +30 -141
  5. package/dist/runtime/components/chat/conversation-item.vue.d.ts +6 -1
  6. package/dist/runtime/components/chat/conversation-list.d.vue.ts +1 -0
  7. package/dist/runtime/components/chat/conversation-list.vue +300 -72
  8. package/dist/runtime/components/chat/conversation-list.vue.d.ts +1 -0
  9. package/dist/runtime/components/chat/conversation-new-group-button.d.vue.ts +7 -0
  10. package/dist/runtime/components/chat/conversation-new-group-button.vue +90 -0
  11. package/dist/runtime/components/chat/conversation-new-group-button.vue.d.ts +7 -0
  12. package/dist/runtime/components/chat/conversation-new-group-popover-content.d.vue.ts +23 -0
  13. package/dist/runtime/components/chat/conversation-new-group-popover-content.vue +75 -0
  14. package/dist/runtime/components/chat/conversation-new-group-popover-content.vue.d.ts +23 -0
  15. package/dist/runtime/components/chat/mobile/shell/drawer.d.vue.ts +2 -2
  16. package/dist/runtime/components/chat/mobile/shell/drawer.vue +370 -41
  17. package/dist/runtime/components/chat/mobile/shell/drawer.vue.d.ts +2 -2
  18. package/dist/runtime/components/chat/mobile/shell/shell.d.vue.ts +6 -6
  19. package/dist/runtime/components/chat/mobile/shell/shell.vue +1 -1
  20. package/dist/runtime/components/chat/mobile/shell/shell.vue.d.ts +6 -6
  21. package/dist/runtime/components/chat/reasoning-message.vue +4 -1
  22. package/dist/runtime/components/chat/topbar.d.vue.ts +2 -2
  23. package/dist/runtime/components/chat/topbar.vue.d.ts +2 -2
  24. package/dist/runtime/composables/useConversationGroups.d.ts +12 -0
  25. package/dist/runtime/composables/useConversationGroups.js +160 -0
  26. package/dist/runtime/composables/useConversationItem.d.ts +48 -0
  27. package/dist/runtime/composables/useConversationItem.js +176 -0
  28. package/dist/runtime/composables/useConversationList.d.ts +128 -0
  29. package/dist/runtime/composables/useConversationList.js +553 -0
  30. package/dist/runtime/composables/useConversations.d.ts +2 -0
  31. package/dist/runtime/composables/useConversations.js +45 -1
  32. package/dist/runtime/composables/usePendingConversationGroup.d.ts +5 -0
  33. package/dist/runtime/composables/usePendingConversationGroup.js +33 -0
  34. package/dist/runtime/composables/useVerbConjugation.d.ts +1 -0
  35. package/dist/runtime/composables/useVerbConjugation.js +14 -1
  36. package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +1 -1
  37. package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +1 -1
  38. package/dist/runtime/embed/components/ChatEmbedInner.vue +24 -5
  39. package/dist/runtime/server/api/chat/conversations/[id]/group.patch.d.ts +1 -0
  40. package/dist/runtime/server/api/chat/conversations/[id]/group.patch.js +1 -0
  41. package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.delete.d.ts +1 -0
  42. package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.delete.js +1 -0
  43. package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.patch.d.ts +1 -0
  44. package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.patch.js +1 -0
  45. package/dist/runtime/server/api/chat/conversations/groups/index.get.d.ts +1 -0
  46. package/dist/runtime/server/api/chat/conversations/groups/index.get.js +1 -0
  47. package/dist/runtime/server/api/chat/conversations/groups/index.post.d.ts +1 -0
  48. package/dist/runtime/server/api/chat/conversations/groups/index.post.js +1 -0
  49. package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +1 -0
  50. package/dist/runtime/server/api/conversations/[id]/group.patch.d.ts +2 -0
  51. package/dist/runtime/server/api/conversations/[id]/group.patch.js +46 -0
  52. package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +2 -0
  53. package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +26 -4
  54. package/dist/runtime/server/api/conversations/groups/[groupId]/index.delete.d.ts +4 -0
  55. package/dist/runtime/server/api/conversations/groups/[groupId]/index.delete.js +25 -0
  56. package/dist/runtime/server/api/conversations/groups/[groupId]/index.patch.d.ts +2 -0
  57. package/dist/runtime/server/api/conversations/groups/[groupId]/index.patch.js +30 -0
  58. package/dist/runtime/server/api/conversations/groups/index.get.d.ts +2 -0
  59. package/dist/runtime/server/api/conversations/groups/index.get.js +13 -0
  60. package/dist/runtime/server/api/conversations/groups/index.post.d.ts +2 -0
  61. package/dist/runtime/server/api/conversations/groups/index.post.js +26 -0
  62. package/dist/runtime/server/api/conversations/index.post.js +1 -0
  63. package/dist/runtime/server/db/schema/conversation-groups.d.ts +143 -0
  64. package/dist/runtime/server/db/schema/conversation-groups.js +20 -0
  65. package/dist/runtime/server/db/schema/conversations.d.ts +17 -0
  66. package/dist/runtime/server/db/schema/conversations.js +3 -0
  67. package/dist/runtime/server/db/schema/index.d.ts +1 -0
  68. package/dist/runtime/server/db/schema/index.js +1 -0
  69. package/dist/runtime/server/db/schema/messages.d.ts +34 -0
  70. package/dist/runtime/server/db/schema/messages.js +3 -1
  71. package/dist/runtime/server/utils/conversation-group.d.ts +4 -0
  72. package/dist/runtime/server/utils/conversation-group.js +48 -0
  73. package/dist/runtime/server/utils/conversation-message-sync.d.ts +1 -1
  74. package/dist/runtime/server/utils/conversation-message-sync.js +2 -2
  75. package/dist/runtime/server/utils/tela-agent-api.d.ts +3 -1
  76. package/dist/runtime/server/utils/tela-agent-api.js +285 -26
  77. package/dist/runtime/server/utils/tela-agent-session.d.ts +14 -4
  78. package/dist/runtime/server/utils/tela-agent-session.js +57 -22
  79. package/dist/runtime/types/chat.d.ts +2 -1
  80. package/dist/runtime/types/tela-agent.d.ts +155 -43
  81. package/dist/runtime/types/tela-agent.js +49 -11
  82. package/dist/runtime/utils/conversation-groups.d.ts +18 -0
  83. package/dist/runtime/utils/conversation-groups.js +30 -0
  84. package/drizzle/0016_dry_aaron_stack.sql +16 -0
  85. package/drizzle/0017_typical_dexter_bennett.sql +2 -0
  86. package/drizzle/meta/0016_snapshot.json +887 -0
  87. package/drizzle/meta/0017_snapshot.json +899 -0
  88. package/drizzle/meta/_journal.json +14 -0
  89. package/package.json +1 -1
@@ -1,68 +1,55 @@
1
1
  <script setup>
2
- import { filterConversationsByTitle } from "../../utils/conversation-search";
3
- import { groupConversationsByDate } from "../../utils/format-time";
4
- import { exportConversationToMarkdown } from "../../utils/export-conversation";
5
- import { conversationListSkeletonRows } from "../../utils/conversation-list-skeleton";
2
+ import { useConversationList } from "../../composables/useConversationList";
6
3
  const props = defineProps({
7
4
  conversations: { type: null, required: true },
8
5
  currentId: { type: String, required: false },
9
6
  getMemberImage: { type: Function, required: false },
10
7
  selectConversation: { type: Function, required: false },
11
8
  resetConversation: { type: Function, required: false },
9
+ startConversation: { type: Function, required: false },
12
10
  loading: { type: Boolean, required: false }
13
11
  });
14
- const chatApi = useChatApi();
15
- const { deleteConversation, renameConversation, duplicateConversation } = useConversations();
16
- const { setConversation } = useChat();
17
- const router = useRouter();
18
- const conversationSearch = ref("");
19
- const filteredConversations = computed(
20
- () => filterConversationsByTitle(props.conversations, conversationSearch.value, {
21
- fallbackTitle: "Nova conversa",
22
- shouldInclude: (conversation) => !conversation.title && conversation.id === props.currentId
23
- })
24
- );
25
- const groupedConversations = computed(() => {
26
- return groupConversationsByDate(filteredConversations.value);
27
- });
28
- const hasConversationSearchQuery = computed(() => conversationSearch.value.trim().length > 0);
29
- async function handleDelete(id) {
30
- const route = useRoute();
31
- const shouldResetConversation = props.currentId === id || route.params.id === id;
32
- const deleted = await deleteConversation(id);
33
- if (deleted && shouldResetConversation) {
34
- if (props.resetConversation) {
35
- void props.resetConversation();
36
- } else {
37
- router.push("/");
38
- }
39
- }
40
- }
41
- async function handleRename(id, title) {
42
- await renameConversation(id, title);
43
- }
44
- async function handleDuplicate(id) {
45
- const duplicated = await duplicateConversation(id);
46
- if (duplicated) {
47
- setConversation(duplicated);
48
- if (props.selectConversation) {
49
- void props.selectConversation(duplicated.id);
50
- } else {
51
- router.push(`/${duplicated.id}`);
52
- }
53
- }
54
- }
55
- async function handleExport(id) {
56
- try {
57
- const conversation = await $fetch(
58
- chatApi.path(`/conversations/${id}`),
59
- chatApi.withChatHeaders()
60
- );
61
- exportConversationToMarkdown(conversation);
62
- } catch (err) {
63
- console.error("Failed to export conversation:", err);
64
- }
65
- }
12
+ const {
13
+ conversationSearch,
14
+ hasConversationContent,
15
+ shouldShowSkeleton,
16
+ emptyConversationMessage,
17
+ skeletonRows,
18
+ groupItems,
19
+ ungroupedConversations,
20
+ ungroupedDropTarget,
21
+ ungroupedConversationLabel,
22
+ shouldShowUngroupedHeader,
23
+ showGroupCreationPrompt,
24
+ showDeleteGroupModal,
25
+ showDeleteGroupConversationsModal,
26
+ groupActionLoading,
27
+ renameGroupName,
28
+ renameGroupError,
29
+ deleteGroupConfirmationMessage,
30
+ deleteGroupConversationsConfirmationMessage,
31
+ toggleGroup,
32
+ clearDeleteGroupTarget,
33
+ clearDeleteGroupConversationsTarget,
34
+ confirmDeleteGroup,
35
+ confirmDeleteGroupConversations,
36
+ cancelRenameGroup,
37
+ submitRenameGroup,
38
+ handleRenameGroupBlur,
39
+ setRenameGroupInputRef,
40
+ handleDelete,
41
+ handleRename,
42
+ handleDuplicate,
43
+ handleExport,
44
+ handleConversationDragStart,
45
+ clearDragState,
46
+ handleDragEnter,
47
+ handleDragOver,
48
+ handleDragLeave,
49
+ getConversationItemProps,
50
+ handleConversationDrop,
51
+ startConversationInGroup
52
+ } = useConversationList(props);
66
53
  </script>
67
54
 
68
55
  <template>
@@ -72,14 +59,14 @@ async function handleExport(id) {
72
59
  <ChatConversationCreatorFilter />
73
60
  </div>
74
61
 
75
- <div v-if="loading && conversations.length === 0" flex="~ col" gap-4px>
62
+ <div v-if="shouldShowSkeleton" flex="~ col" gap-4px>
76
63
  <div h-20px flex items-center pl-12px pr-4px mb-4px>
77
64
  <TelaSkeleton rounded-4px bg-neutral-200 style="width: 68px; height: 12px;" />
78
65
  </div>
79
66
 
80
67
  <div flex="~ col" gap-1px>
81
68
  <div
82
- v-for="row in conversationListSkeletonRows"
69
+ v-for="row in skeletonRows"
83
70
  :key="row.id"
84
71
  flex items-center h-32px px-8px rounded-8px
85
72
  >
@@ -88,37 +75,278 @@ async function handleExport(id) {
88
75
  </div>
89
76
  </div>
90
77
 
91
- <div v-else-if="filteredConversations.length === 0" p-16px text-center>
78
+ <div v-else-if="!hasConversationContent" p-16px text-center>
92
79
  <p text-14px text-neutral-400>
93
- {{ hasConversationSearchQuery ? "Nenhuma conversa encontrada" : "Nenhuma conversa ainda" }}
80
+ {{ emptyConversationMessage }}
94
81
  </p>
95
82
  </div>
96
83
 
97
84
  <template v-else>
98
- <div v-for="group in groupedConversations" :key="group.title" flex="~ col" gap-4px>
99
- <!-- Section Header -->
100
- <div h-20px flex items-center pl-12px pr-4px mb-4px>
101
- <span text-12px font-medium text-neutral-400>
102
- {{ group.title }}
85
+ <div
86
+ v-if="showGroupCreationPrompt"
87
+ mx-8px
88
+ flex="~ col" gap-10px
89
+ >
90
+ <div h-20px flex items-center px-4px>
91
+ <span text-12px font-semibold text-neutral-400 uppercase style="letter-spacing: 0">
92
+ PASTAS
93
+ </span>
94
+ </div>
95
+ <div flex="~ col" gap-6px>
96
+ <div
97
+ h-36px px-10px
98
+ flex items-center gap-10px
99
+ rounded-8px
100
+ b=".5px dashed neutral-200"
101
+ >
102
+ <span i-ph-folder text-16px text-neutral-400 shrink-0 />
103
+ <span h-10px w-120px rounded-full bg-neutral-100 />
104
+ </div>
105
+ <div
106
+ h-36px px-10px
107
+ flex items-center gap-10px
108
+ rounded-8px
109
+ b=".5px dashed neutral-200"
110
+ >
111
+ <span i-ph-folder text-16px text-neutral-400 shrink-0 />
112
+ <span h-10px w-84px rounded-full bg-neutral-100 />
113
+ </div>
114
+ </div>
115
+ <p px-4px text-14px text-neutral-500 leading-20px>
116
+ Suas pastas aparecem aqui. Crie uma para começar a agrupar conversas.
117
+ </p>
118
+ <ChatConversationNewGroupButton label="Criar pasta" variant="empty-state" />
119
+ </div>
120
+
121
+ <div v-if="groupItems.length > 0" flex="~ col" gap-2px>
122
+ <div
123
+ v-for="groupBucket in groupItems"
124
+ :key="groupBucket.group.id"
125
+ flex="~ col"
126
+ rounded-8px
127
+ transition-colors
128
+ :class="groupBucket.dropTarget ? 'bg-neutral-200' : ''"
129
+ @dragenter.prevent="handleDragEnter(groupBucket.group.id)"
130
+ @dragover.prevent="handleDragOver($event, groupBucket.group.id)"
131
+ @dragleave="handleDragLeave($event, groupBucket.group.id)"
132
+ @drop.prevent="handleConversationDrop($event, groupBucket.group.id)"
133
+ >
134
+ <div
135
+ v-if="groupBucket.isRenaming"
136
+ h-32px w-full px-8px
137
+ flex items-center gap-8px
138
+ rounded-8px
139
+ :class="groupBucket.dropTarget ? 'bg-neutral-200' : ''"
140
+ >
141
+ <span
142
+ i-ph-caret-right
143
+ text-12px text-neutral-400
144
+ transition-transform
145
+ :class="groupBucket.open && 'rotate-90'"
146
+ />
147
+ <span
148
+ :class="groupBucket.folderIcon"
149
+ text-16px text-neutral-500 shrink-0
150
+ />
151
+ <div
152
+ flex-1 min-w-0 flex items-center
153
+ pb-1px
154
+ b-b=".5px solid #A3A3A3"
155
+ :class="renameGroupError && 'border-red-500'"
156
+ >
157
+ <input
158
+ :ref="(element) => setRenameGroupInputRef(element, groupBucket.group.id)"
159
+ v-model="renameGroupName"
160
+ type="text"
161
+ autocomplete="off"
162
+ maxlength="80"
163
+ aria-label="Nome da pasta"
164
+ w-full min-w-0
165
+ bg-transparent border-none outline-none
166
+ text-14px font-semibold text-neutral-700 leading-18px
167
+ p-0
168
+ :disabled="groupActionLoading"
169
+ @blur="handleRenameGroupBlur"
170
+ @keydown.enter.prevent="submitRenameGroup"
171
+ @keydown.escape.prevent.stop="cancelRenameGroup"
172
+ @click.stop
173
+ @dblclick.stop
174
+ >
175
+ </div>
176
+ </div>
177
+
178
+ <div v-else rounded-8px class="group" :class="groupBucket.dropTarget ? '' : 'hover:bg-neutral-100'">
179
+ <div h-32px w-full px-8px flex items-center gap-4px text-left>
180
+ <button
181
+ type="button"
182
+ h-full flex-1 min-w-0
183
+ flex items-center gap-8px
184
+ text-left
185
+ cursor-pointer
186
+ @click="toggleGroup(groupBucket.group.id)"
187
+ >
188
+ <span
189
+ i-ph-caret-right
190
+ text-12px text-neutral-400
191
+ transition-transform
192
+ :class="groupBucket.open && 'rotate-90'"
193
+ />
194
+ <span
195
+ :class="groupBucket.folderIcon"
196
+ text-16px text-neutral-500 shrink-0
197
+ />
198
+ <span flex-1 min-w-0 truncate text-14px font-semibold text-neutral-900 leading-18px>
199
+ {{ groupBucket.group.name }}
200
+ </span>
201
+ <span v-if="groupBucket.conversationCountLabel" text-13px font-medium text-neutral-400 tabular-nums>
202
+ {{ groupBucket.conversationCountLabel }}
203
+ </span>
204
+ </button>
205
+
206
+ <div
207
+ flex items-center justify-center shrink-0
208
+ class="opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity"
209
+ >
210
+ <TelaDropdownMenu
211
+ :items="groupBucket.menuItems"
212
+ align="end"
213
+ content-class="chat-sidebar-floating"
214
+ >
215
+ <button
216
+ type="button"
217
+ aria-label="Ações da pasta"
218
+ p-4px
219
+ rounded-6px
220
+ hover:bg-neutral-200
221
+ transition-colors
222
+ cursor-pointer
223
+ @click.prevent.stop
224
+ >
225
+ <div i-ph-dots-three-vertical text-16px text-neutral-400 />
226
+ </button>
227
+ </TelaDropdownMenu>
228
+ </div>
229
+ </div>
230
+ </div>
231
+
232
+ <div
233
+ v-if="groupBucket.isRenaming && renameGroupError"
234
+ ml-44px mr-8px mb-4px
235
+ >
236
+ <span text-12px text-red-600 leading-16px aria-live="polite">
237
+ {{ renameGroupError }}
238
+ </span>
239
+ </div>
240
+
241
+ <Transition name="group-slide">
242
+ <div v-if="groupBucket.open" class="group-slide-panel">
243
+ <div class="group-slide__inner">
244
+ <div flex="~ col" gap-1px ml-20px mt-1px mb-4px>
245
+ <ChatConversationItem
246
+ v-for="conversation in groupBucket.items"
247
+ :key="conversation.id"
248
+ v-bind="getConversationItemProps(conversation)"
249
+ @delete="handleDelete"
250
+ @rename="handleRename"
251
+ @duplicate="handleDuplicate"
252
+ @export="handleExport"
253
+ @drag-start="handleConversationDragStart"
254
+ @drag-end="clearDragState"
255
+ />
256
+ <button
257
+ v-if="groupBucket.showNewConversationAction"
258
+ type="button"
259
+ h-32px px-8px
260
+ flex items-center gap-8px
261
+ rounded-8px
262
+ text-left
263
+ hover:bg-neutral-100
264
+ transition-colors
265
+ cursor-pointer
266
+ @click="startConversationInGroup(groupBucket.group.id)"
267
+ >
268
+ <span i-ph-plus text-14px text-neutral-500 shrink-0 />
269
+ <span text-14px font-medium text-neutral-500 leading-18px>
270
+ Nova conversa
271
+ </span>
272
+ </button>
273
+ <div
274
+ v-else-if="groupBucket.showSearchEmpty"
275
+ h-32px px-8px flex items-center
276
+ >
277
+ <span text-13px text-neutral-400 leading-17px>
278
+ Sem resultados
279
+ </span>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ </div>
284
+ </Transition>
285
+ </div>
286
+ </div>
287
+
288
+ <div
289
+ flex="~ col" gap-4px
290
+ rounded-8px
291
+ transition-colors
292
+ :class="ungroupedDropTarget ? 'bg-neutral-100' : ''"
293
+ @dragenter.prevent="handleDragEnter(null)"
294
+ @dragover.prevent="handleDragOver($event, null)"
295
+ @dragleave="handleDragLeave($event, null)"
296
+ @drop.prevent="handleConversationDrop($event, null)"
297
+ >
298
+ <div v-if="shouldShowUngroupedHeader" h-20px flex items-center pl-12px pr-4px mb-4px>
299
+ <span text-12px font-semibold text-neutral-400 uppercase style="letter-spacing: 0">
300
+ {{ ungroupedConversationLabel }}
103
301
  </span>
104
302
  </div>
105
303
 
106
- <!-- Conversation Items -->
107
- <div flex="~ col" gap-1px>
304
+ <div v-if="ungroupedConversations.length > 0" flex="~ col" gap-1px>
108
305
  <ChatConversationItem
109
- v-for="conversation in group.items"
306
+ v-for="conversation in ungroupedConversations"
110
307
  :key="conversation.id"
111
- :conversation="conversation"
112
- :active="conversation.id === currentId"
113
- :user-image="getMemberImage?.(conversation.userId)"
114
- :select-conversation="selectConversation"
308
+ v-bind="getConversationItemProps(conversation)"
115
309
  @delete="handleDelete"
116
310
  @rename="handleRename"
117
311
  @duplicate="handleDuplicate"
118
312
  @export="handleExport"
313
+ @drag-start="handleConversationDragStart"
314
+ @drag-end="clearDragState"
119
315
  />
120
316
  </div>
121
317
  </div>
122
318
  </template>
123
319
  </div>
320
+
321
+ <TelaConfirmationModal
322
+ v-model:open="showDeleteGroupModal"
323
+ title="Excluir pasta"
324
+ variant="danger"
325
+ class="chat-sidebar-floating"
326
+ confirm-button-text="Excluir pasta"
327
+ cancel-button-text="Cancelar"
328
+ :loading="groupActionLoading"
329
+ @confirm="confirmDeleteGroup"
330
+ @cancel="clearDeleteGroupTarget"
331
+ >
332
+ {{ deleteGroupConfirmationMessage }}
333
+ </TelaConfirmationModal>
334
+
335
+ <TelaConfirmationModal
336
+ v-model:open="showDeleteGroupConversationsModal"
337
+ title="Excluir conversas"
338
+ variant="danger"
339
+ class="chat-sidebar-floating"
340
+ confirm-button-text="Excluir conversas"
341
+ cancel-button-text="Cancelar"
342
+ :loading="groupActionLoading"
343
+ @confirm="confirmDeleteGroupConversations"
344
+ @cancel="clearDeleteGroupConversationsTarget"
345
+ >
346
+ {{ deleteGroupConversationsConfirmationMessage }}
347
+ </TelaConfirmationModal>
124
348
  </template>
349
+
350
+ <style scoped>
351
+ .group-slide-panel{display:grid;grid-template-rows:1fr}.group-slide-enter-active,.group-slide-leave-active{overflow:hidden;transition:grid-template-rows .18s cubic-bezier(.2,.8,.2,1),opacity .14s ease,transform .18s cubic-bezier(.2,.8,.2,1)}.group-slide-enter-from,.group-slide-leave-to{grid-template-rows:0fr;opacity:0;transform:translateY(-4px)}.group-slide-enter-to,.group-slide-leave-from{grid-template-rows:1fr;opacity:1;transform:translateY(0)}.group-slide__inner{min-height:0;overflow:hidden}
352
+ </style>
@@ -6,6 +6,7 @@ type __VLS_Props = {
6
6
  getMemberImage?: (userId: string) => string | undefined;
7
7
  selectConversation?: (id: string) => void | Promise<void>;
8
8
  resetConversation?: () => void | Promise<void>;
9
+ startConversation?: (groupId?: string | null) => void | Promise<void>;
9
10
  loading?: boolean;
10
11
  };
11
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -0,0 +1,7 @@
1
+ type __VLS_Props = {
2
+ label?: string;
3
+ variant?: 'empty-state';
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,90 @@
1
+ <script setup>
2
+ import ChatConversationNewGroupPopoverContent from "./conversation-new-group-popover-content.vue";
3
+ const props = defineProps({
4
+ label: { type: String, required: false },
5
+ variant: { type: String, required: false }
6
+ });
7
+ const { createGroup, error: groupCreationError } = useConversationGroups();
8
+ const isOpen = ref(false);
9
+ const groupName = ref("");
10
+ const isSaving = ref(false);
11
+ const creationErrorMessage = ref(null);
12
+ const popoverContentRef = ref(null);
13
+ const hasLabel = computed(() => Boolean(props.label?.trim()));
14
+ const isEmptyState = computed(() => props.variant === "empty-state");
15
+ const buttonClasses = computed(() => {
16
+ if (isEmptyState.value)
17
+ return "h-32px w-full gap-6px px-12px border-neutral-300 hover:bg-neutral-50";
18
+ if (hasLabel.value)
19
+ return "h-28px px-10px gap-6px self-start border-neutral-300 hover:bg-neutral-50";
20
+ return "h-32px w-40px border-neutral-300 hover:bg-neutral-50";
21
+ });
22
+ const iconClasses = computed(() => isEmptyState.value ? "text-neutral-900" : "text-neutral-700");
23
+ const labelClasses = computed(
24
+ () => isEmptyState.value ? "text-14px font-semibold text-neutral-900 leading-18px" : "text-13px font-medium text-neutral-700 leading-17px"
25
+ );
26
+ watch(isOpen, async (open) => {
27
+ if (!open) {
28
+ creationErrorMessage.value = null;
29
+ return;
30
+ }
31
+ await nextTick();
32
+ popoverContentRef.value?.focusInput?.();
33
+ });
34
+ watch(groupName, () => {
35
+ creationErrorMessage.value = null;
36
+ });
37
+ async function submitGroup() {
38
+ const name = groupName.value.trim();
39
+ if (!name || isSaving.value)
40
+ return;
41
+ creationErrorMessage.value = null;
42
+ isSaving.value = true;
43
+ const group = await createGroup(name);
44
+ isSaving.value = false;
45
+ if (!group) {
46
+ creationErrorMessage.value = groupCreationError.value || "Erro ao criar pasta";
47
+ return;
48
+ }
49
+ groupName.value = "";
50
+ creationErrorMessage.value = null;
51
+ isOpen.value = false;
52
+ }
53
+ function cancelGroup() {
54
+ groupName.value = "";
55
+ creationErrorMessage.value = null;
56
+ isOpen.value = false;
57
+ }
58
+ </script>
59
+
60
+ <template>
61
+ <TelaPopover v-model:open="isOpen">
62
+ <TelaPopoverTrigger as-child>
63
+ <button
64
+ type="button"
65
+ flex items-center justify-center
66
+ bg-white b=".5px solid"
67
+ rounded-10px
68
+ transition-colors
69
+ cursor-pointer
70
+ :class="buttonClasses"
71
+ :aria-label="label || 'Nova pasta'"
72
+ :title="label || 'Nova pasta'"
73
+ >
74
+ <span i-ph-folder-plus text-16px :class="iconClasses" />
75
+ <span v-if="hasLabel" :class="labelClasses">
76
+ {{ label }}
77
+ </span>
78
+ </button>
79
+ </TelaPopoverTrigger>
80
+
81
+ <ChatConversationNewGroupPopoverContent
82
+ ref="popoverContentRef"
83
+ v-model="groupName"
84
+ :error-message="creationErrorMessage"
85
+ :saving="isSaving"
86
+ @submit="submitGroup"
87
+ @cancel="cancelGroup"
88
+ />
89
+ </TelaPopover>
90
+ </template>
@@ -0,0 +1,7 @@
1
+ type __VLS_Props = {
2
+ label?: string;
3
+ variant?: 'empty-state';
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,23 @@
1
+ type __VLS_Props = {
2
+ errorMessage?: string | null;
3
+ saving?: boolean;
4
+ };
5
+ declare function focusInput(): void;
6
+ type __VLS_ModelProps = {
7
+ modelValue: string;
8
+ };
9
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
11
+ focusInput: typeof focusInput;
12
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ "update:modelValue": (value: string) => any;
14
+ } & {
15
+ submit: () => any;
16
+ cancel: () => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
18
+ onSubmit?: (() => any) | undefined;
19
+ onCancel?: (() => any) | undefined;
20
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
21
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
+ declare const _default: typeof __VLS_export;
23
+ export default _default;
@@ -0,0 +1,75 @@
1
+ <script setup>
2
+ defineProps({
3
+ errorMessage: { type: [String, null], required: false },
4
+ saving: { type: Boolean, required: false }
5
+ });
6
+ const emit = defineEmits(["submit", "cancel"]);
7
+ const groupName = defineModel({ type: String, ...{ required: true } });
8
+ const inputRef = ref(null);
9
+ function focusInput() {
10
+ inputRef.value?.focus();
11
+ }
12
+ defineExpose({
13
+ focusInput
14
+ });
15
+ </script>
16
+
17
+ <template>
18
+ <TelaPopoverContent
19
+ align="end"
20
+ :side-offset="4"
21
+ data-chat-sidebar-floating
22
+ class="!w-auto !rounded-12px !border-neutral-200 !bg-white !p-0 !text-neutral-900 !shadow-lg"
23
+ >
24
+ <form w-236px p-8px flex="~ col" gap-8px @submit.prevent="emit('submit')">
25
+ <input
26
+ ref="inputRef"
27
+ v-model="groupName"
28
+ type="text"
29
+ autocomplete="off"
30
+ maxlength="80"
31
+ placeholder="Nome da pasta"
32
+ aria-label="Nome da pasta"
33
+ h-32px w-full
34
+ px-10px
35
+ rounded-8px
36
+ b=".5px neutral-300"
37
+ bg-white
38
+ outline-none
39
+ text-14px text-neutral-900 leading-18px
40
+ focus:b-neutral-900
41
+ @keydown.escape.prevent.stop="emit('cancel')"
42
+ >
43
+ <p
44
+ v-if="errorMessage"
45
+ text-12px text-red-600 leading-16px
46
+ aria-live="polite"
47
+ >
48
+ {{ errorMessage }}
49
+ </p>
50
+ <div flex justify-end gap-6px>
51
+ <button
52
+ type="button"
53
+ h-28px px-10px rounded-8px
54
+ text-13px font-medium text-neutral-700
55
+ hover:bg-neutral-100
56
+ transition-colors
57
+ @click="emit('cancel')"
58
+ >
59
+ Cancelar
60
+ </button>
61
+ <button
62
+ type="submit"
63
+ h-28px px-10px rounded-8px
64
+ bg-neutral-900 text-white
65
+ text-13px font-medium
66
+ transition-opacity
67
+ :disabled="!groupName.trim() || saving"
68
+ :class="(!groupName.trim() || saving) && 'opacity-40 cursor-not-allowed'"
69
+ >
70
+ Criar
71
+ </button>
72
+ </div>
73
+ </form>
74
+ </TelaPopoverContent>
75
+ </template>
@@ -0,0 +1,23 @@
1
+ type __VLS_Props = {
2
+ errorMessage?: string | null;
3
+ saving?: boolean;
4
+ };
5
+ declare function focusInput(): void;
6
+ type __VLS_ModelProps = {
7
+ modelValue: string;
8
+ };
9
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
11
+ focusInput: typeof focusInput;
12
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ "update:modelValue": (value: string) => any;
14
+ } & {
15
+ submit: () => any;
16
+ cancel: () => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
18
+ onSubmit?: (() => any) | undefined;
19
+ onCancel?: (() => any) | undefined;
20
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
21
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
+ declare const _default: typeof __VLS_export;
23
+ export default _default;