@meistrari/chat-nuxt 1.11.0 → 1.12.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 (72) 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/topbar.d.vue.ts +2 -2
  22. package/dist/runtime/components/chat/topbar.vue.d.ts +2 -2
  23. package/dist/runtime/composables/useConversationGroups.d.ts +12 -0
  24. package/dist/runtime/composables/useConversationGroups.js +160 -0
  25. package/dist/runtime/composables/useConversationItem.d.ts +48 -0
  26. package/dist/runtime/composables/useConversationItem.js +176 -0
  27. package/dist/runtime/composables/useConversationList.d.ts +128 -0
  28. package/dist/runtime/composables/useConversationList.js +553 -0
  29. package/dist/runtime/composables/useConversations.d.ts +2 -0
  30. package/dist/runtime/composables/useConversations.js +45 -1
  31. package/dist/runtime/composables/usePendingConversationGroup.d.ts +5 -0
  32. package/dist/runtime/composables/usePendingConversationGroup.js +33 -0
  33. package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +1 -1
  34. package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +1 -1
  35. package/dist/runtime/embed/components/ChatEmbedInner.vue +24 -5
  36. package/dist/runtime/server/api/chat/conversations/[id]/group.patch.d.ts +1 -0
  37. package/dist/runtime/server/api/chat/conversations/[id]/group.patch.js +1 -0
  38. package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.delete.d.ts +1 -0
  39. package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.delete.js +1 -0
  40. package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.patch.d.ts +1 -0
  41. package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.patch.js +1 -0
  42. package/dist/runtime/server/api/chat/conversations/groups/index.get.d.ts +1 -0
  43. package/dist/runtime/server/api/chat/conversations/groups/index.get.js +1 -0
  44. package/dist/runtime/server/api/chat/conversations/groups/index.post.d.ts +1 -0
  45. package/dist/runtime/server/api/chat/conversations/groups/index.post.js +1 -0
  46. package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +1 -0
  47. package/dist/runtime/server/api/conversations/[id]/group.patch.d.ts +2 -0
  48. package/dist/runtime/server/api/conversations/[id]/group.patch.js +46 -0
  49. package/dist/runtime/server/api/conversations/groups/[groupId]/index.delete.d.ts +4 -0
  50. package/dist/runtime/server/api/conversations/groups/[groupId]/index.delete.js +25 -0
  51. package/dist/runtime/server/api/conversations/groups/[groupId]/index.patch.d.ts +2 -0
  52. package/dist/runtime/server/api/conversations/groups/[groupId]/index.patch.js +30 -0
  53. package/dist/runtime/server/api/conversations/groups/index.get.d.ts +2 -0
  54. package/dist/runtime/server/api/conversations/groups/index.get.js +13 -0
  55. package/dist/runtime/server/api/conversations/groups/index.post.d.ts +2 -0
  56. package/dist/runtime/server/api/conversations/groups/index.post.js +26 -0
  57. package/dist/runtime/server/api/conversations/index.post.js +1 -0
  58. package/dist/runtime/server/db/schema/conversation-groups.d.ts +143 -0
  59. package/dist/runtime/server/db/schema/conversation-groups.js +20 -0
  60. package/dist/runtime/server/db/schema/conversations.d.ts +17 -0
  61. package/dist/runtime/server/db/schema/conversations.js +3 -0
  62. package/dist/runtime/server/db/schema/index.d.ts +1 -0
  63. package/dist/runtime/server/db/schema/index.js +1 -0
  64. package/dist/runtime/server/utils/conversation-group.d.ts +4 -0
  65. package/dist/runtime/server/utils/conversation-group.js +48 -0
  66. package/dist/runtime/types/chat.d.ts +2 -1
  67. package/dist/runtime/utils/conversation-groups.d.ts +18 -0
  68. package/dist/runtime/utils/conversation-groups.js +30 -0
  69. package/drizzle/0016_dry_aaron_stack.sql +16 -0
  70. package/drizzle/meta/0016_snapshot.json +887 -0
  71. package/drizzle/meta/_journal.json +7 -0
  72. package/package.json +1 -1
@@ -0,0 +1,553 @@
1
+ import { useChatAuth } from "#chat-auth";
2
+ import { filterConversationsByTitle } from "../utils/conversation-search.js";
3
+ import { groupConversationsByGroup } from "../utils/conversation-groups.js";
4
+ import { conversationListSkeletonRows } from "../utils/conversation-list-skeleton.js";
5
+ import { exportConversationToMarkdown } from "../utils/export-conversation.js";
6
+ import { resolveChatStateScope } from "../utils/tela-chat.js";
7
+ const DRAG_OPEN_GROUP_DELAY_MS = 400;
8
+ export function useConversationList(options) {
9
+ const chatApi = useChatApi();
10
+ const embedConfig = useEmbedConfig();
11
+ const { activeOrganization } = useChatAuth();
12
+ const { deleteConversation, renameConversation, duplicateConversation, moveConversationToGroup } = useConversations();
13
+ const { groups, fetchGroups, deleteGroup, renameGroup, error: groupError } = useConversationGroups();
14
+ const { setConversation } = useChat();
15
+ const route = useRoute();
16
+ const router = useRouter();
17
+ const conversationSearch = ref("");
18
+ const draggedConversationId = ref(null);
19
+ const dropTargetGroupId = ref(void 0);
20
+ const openGroupIds = ref(/* @__PURE__ */ new Set());
21
+ const groupsReady = ref(false);
22
+ const showDeleteGroupModal = ref(false);
23
+ const showDeleteGroupConversationsModal = ref(false);
24
+ const groupActionLoading = ref(false);
25
+ const deleteGroupTarget = ref(null);
26
+ const deleteGroupConversationsTarget = ref(null);
27
+ const renamingGroupId = ref(null);
28
+ const renameGroupName = ref("");
29
+ const renameGroupError = ref(null);
30
+ const renameGroupInputRef = ref(null);
31
+ const isIgnoringInitialGroupRenameBlur = ref(false);
32
+ let dragOpenGroupTimeout = null;
33
+ let dragOpenGroupId = null;
34
+ let groupRenameBlurTimer = null;
35
+ let groupRenameSelectionTimers = [];
36
+ const runtimeScope = computed(() => resolveChatStateScope(
37
+ embedConfig?.workspaceId.value || activeOrganization.value?.id,
38
+ embedConfig?.telaAgentId.value,
39
+ embedConfig?.conversationScope?.value
40
+ ));
41
+ const openGroupsStorageKey = computed(() => `conversation-group-open:${runtimeScope.value}`);
42
+ const hasConversationSearchQuery = computed(() => conversationSearch.value.trim().length > 0);
43
+ const filteredConversations = computed(
44
+ () => filterConversationsByTitle(options.conversations, conversationSearch.value, {
45
+ fallbackTitle: "Nova conversa",
46
+ shouldInclude: (conversation) => !conversation.title && conversation.id === options.currentId
47
+ })
48
+ );
49
+ const filteredGroupedConversations = computed(() => groupConversationsByGroup(filteredConversations.value, groups.value));
50
+ const allGroupedConversations = computed(() => groupConversationsByGroup(options.conversations, groups.value));
51
+ const groupedConversations = computed(() => {
52
+ if (!hasConversationSearchQuery.value)
53
+ return filteredGroupedConversations.value;
54
+ const allItemsByGroupId = new Map(
55
+ allGroupedConversations.value.groups.map((groupBucket) => [groupBucket.group.id, groupBucket.items])
56
+ );
57
+ return {
58
+ groups: filteredGroupedConversations.value.groups.map(
59
+ (groupBucket) => doesGroupMatchSearch(groupBucket.group.name) ? {
60
+ ...groupBucket,
61
+ items: allItemsByGroupId.get(groupBucket.group.id) ?? []
62
+ } : groupBucket
63
+ ),
64
+ unfiled: filteredGroupedConversations.value.unfiled
65
+ };
66
+ });
67
+ const shouldShowSkeleton = computed(() => Boolean(options.loading) && options.conversations.length === 0);
68
+ const emptyConversationMessage = computed(
69
+ () => hasConversationSearchQuery.value ? "Nenhuma conversa encontrada" : "Nenhuma conversa ainda"
70
+ );
71
+ const activeGroupId = computed(() => {
72
+ const currentConversation = options.conversations.find((conversation) => conversation.id === options.currentId);
73
+ return currentConversation?.groupId ?? null;
74
+ });
75
+ onMounted(() => {
76
+ void Promise.resolve(fetchGroups()).finally(() => {
77
+ groupsReady.value = true;
78
+ });
79
+ try {
80
+ const stored = localStorage.getItem(openGroupsStorageKey.value);
81
+ if (stored) {
82
+ openGroupIds.value = new Set(JSON.parse(stored));
83
+ }
84
+ } catch {
85
+ }
86
+ });
87
+ watch(openGroupIds, (groupIds) => {
88
+ if (typeof localStorage === "undefined")
89
+ return;
90
+ try {
91
+ localStorage.setItem(openGroupsStorageKey.value, JSON.stringify([...groupIds]));
92
+ } catch {
93
+ }
94
+ });
95
+ watch(activeGroupId, (groupId) => {
96
+ if (groupId) {
97
+ setGroupOpen(groupId, true);
98
+ }
99
+ }, { immediate: true });
100
+ function isGroupOpen(groupId) {
101
+ return openGroupIds.value.has(groupId);
102
+ }
103
+ function setGroupOpen(groupId, open) {
104
+ const nextOpenGroupIds = new Set(openGroupIds.value);
105
+ if (open) {
106
+ nextOpenGroupIds.add(groupId);
107
+ } else {
108
+ nextOpenGroupIds.delete(groupId);
109
+ }
110
+ openGroupIds.value = nextOpenGroupIds;
111
+ }
112
+ function toggleGroup(groupId) {
113
+ setGroupOpen(groupId, !isGroupOpen(groupId));
114
+ }
115
+ function createGroupDeleteTarget(groupId, groupName, conversations) {
116
+ return {
117
+ id: groupId,
118
+ name: groupName,
119
+ conversationIds: conversations.map((conversation) => conversation.id),
120
+ conversationCount: conversations.length
121
+ };
122
+ }
123
+ function openDeleteGroupModal(groupId, groupName, conversations) {
124
+ deleteGroupTarget.value = createGroupDeleteTarget(groupId, groupName, conversations);
125
+ showDeleteGroupModal.value = true;
126
+ }
127
+ function openDeleteGroupConversationsModal(groupId, groupName, conversations) {
128
+ deleteGroupConversationsTarget.value = createGroupDeleteTarget(groupId, groupName, conversations);
129
+ showDeleteGroupConversationsModal.value = true;
130
+ }
131
+ function clearGroupRenameBlurTimer() {
132
+ if (groupRenameBlurTimer === null)
133
+ return;
134
+ clearTimeout(groupRenameBlurTimer);
135
+ groupRenameBlurTimer = null;
136
+ }
137
+ function clearGroupRenameSelectionTimers() {
138
+ if (groupRenameSelectionTimers.length === 0)
139
+ return;
140
+ for (const timer of groupRenameSelectionTimers) {
141
+ clearTimeout(timer);
142
+ }
143
+ groupRenameSelectionTimers = [];
144
+ }
145
+ function allowGroupRenameBlurSoon() {
146
+ clearGroupRenameBlurTimer();
147
+ groupRenameBlurTimer = setTimeout(() => {
148
+ isIgnoringInitialGroupRenameBlur.value = false;
149
+ groupRenameBlurTimer = null;
150
+ }, 100);
151
+ }
152
+ function selectRenameGroupInputText() {
153
+ const input = renameGroupInputRef.value;
154
+ if (!input)
155
+ return;
156
+ input.focus();
157
+ input.select();
158
+ input.setSelectionRange(0, input.value.length);
159
+ }
160
+ function scheduleRenameGroupTextSelection() {
161
+ clearGroupRenameSelectionTimers();
162
+ groupRenameSelectionTimers = [0, 50].map((delay) => setTimeout(() => {
163
+ selectRenameGroupInputText();
164
+ }, delay));
165
+ }
166
+ async function focusRenameGroupInput() {
167
+ await nextTick();
168
+ selectRenameGroupInputText();
169
+ scheduleRenameGroupTextSelection();
170
+ allowGroupRenameBlurSoon();
171
+ }
172
+ function setRenameGroupInputRef(element, groupId) {
173
+ if (groupId !== renamingGroupId.value)
174
+ return;
175
+ if (element && typeof element.focus === "function" && typeof element.select === "function" && typeof element.setSelectionRange === "function" && typeof element.value === "string") {
176
+ renameGroupInputRef.value = element;
177
+ return;
178
+ }
179
+ renameGroupInputRef.value = null;
180
+ }
181
+ function startRenameGroup(groupId, groupName) {
182
+ clearGroupRenameBlurTimer();
183
+ clearGroupRenameSelectionTimers();
184
+ renameGroupInputRef.value = null;
185
+ renamingGroupId.value = groupId;
186
+ renameGroupName.value = groupName;
187
+ renameGroupError.value = null;
188
+ isIgnoringInitialGroupRenameBlur.value = true;
189
+ void focusRenameGroupInput();
190
+ }
191
+ function getGroupMenuItems(groupId, groupName, conversations) {
192
+ return [
193
+ {
194
+ label: "Renomear pasta",
195
+ icon: "i-ph-pencil-simple",
196
+ click: () => startRenameGroup(groupId, groupName)
197
+ },
198
+ {
199
+ label: "Excluir pasta",
200
+ icon: "i-ph-folder-minus",
201
+ color: "negative",
202
+ click: () => openDeleteGroupModal(groupId, groupName, conversations)
203
+ },
204
+ {
205
+ label: "Excluir conversas",
206
+ icon: "i-ph-trash",
207
+ color: "negative",
208
+ disabled: conversations.length === 0,
209
+ click: () => openDeleteGroupConversationsModal(groupId, groupName, conversations)
210
+ }
211
+ ];
212
+ }
213
+ function doesGroupMatchSearch(groupName) {
214
+ const query = conversationSearch.value.trim().toLowerCase();
215
+ return query.length > 0 && groupName.toLowerCase().includes(query);
216
+ }
217
+ const groupItems = computed(() => groupedConversations.value.groups.filter((groupBucket) => {
218
+ if (!hasConversationSearchQuery.value)
219
+ return true;
220
+ return groupBucket.items.length > 0 || doesGroupMatchSearch(groupBucket.group.name);
221
+ }).map((groupBucket) => {
222
+ const open = isGroupOpen(groupBucket.group.id) || hasConversationSearchQuery.value && (groupBucket.items.length > 0 || doesGroupMatchSearch(groupBucket.group.name));
223
+ const dropTarget = isDropTarget(groupBucket.group.id);
224
+ return {
225
+ ...groupBucket,
226
+ open,
227
+ dropTarget,
228
+ folderIcon: open ? "i-ph-folder-open" : "i-ph-folder",
229
+ isRenaming: renamingGroupId.value === groupBucket.group.id,
230
+ conversationCountLabel: groupBucket.items.length > 0 ? String(groupBucket.items.length) : null,
231
+ showNewConversationAction: !hasConversationSearchQuery.value,
232
+ showSearchEmpty: hasConversationSearchQuery.value && groupBucket.items.length === 0,
233
+ menuItems: getGroupMenuItems(groupBucket.group.id, groupBucket.group.name, groupBucket.items)
234
+ };
235
+ }));
236
+ const ungroupedConversations = computed(() => groupedConversations.value.unfiled);
237
+ const ungroupedDropTarget = computed(() => isDropTarget(null));
238
+ const ungroupedConversationLabel = computed(() => `Conversas \xB7 ${ungroupedConversations.value.length}`);
239
+ const shouldShowUngroupedHeader = computed(() => groupItems.value.length > 0);
240
+ const showGroupCreationPrompt = computed(
241
+ () => groupsReady.value && !hasConversationSearchQuery.value && groups.value.length === 0
242
+ );
243
+ const hasConversationContent = computed(() => {
244
+ if (hasConversationSearchQuery.value)
245
+ return groupItems.value.length > 0 || ungroupedConversations.value.length > 0;
246
+ return showGroupCreationPrompt.value || groups.value.length > 0 || filteredConversations.value.length > 0;
247
+ });
248
+ const deleteGroupConfirmationMessage = computed(() => {
249
+ const target = deleteGroupTarget.value;
250
+ return `Tem certeza que deseja excluir a pasta "${target?.name ?? ""}"? ${groupConversationsMoveMessage(target?.conversationCount ?? 0)}`;
251
+ });
252
+ const deleteGroupConversationsConfirmationMessage = computed(() => {
253
+ const target = deleteGroupConversationsTarget.value;
254
+ return `Tem certeza que deseja excluir ${formatConversationCount(target?.conversationCount ?? 0)} da pasta "${target?.name ?? ""}"? A pasta ser\xE1 mantida.`;
255
+ });
256
+ function clearDeleteGroupTarget() {
257
+ if (groupActionLoading.value)
258
+ return;
259
+ deleteGroupTarget.value = null;
260
+ showDeleteGroupModal.value = false;
261
+ }
262
+ function clearDeleteGroupConversationsTarget() {
263
+ if (groupActionLoading.value)
264
+ return;
265
+ deleteGroupConversationsTarget.value = null;
266
+ showDeleteGroupConversationsModal.value = false;
267
+ }
268
+ function resetRenameGroupState() {
269
+ renamingGroupId.value = null;
270
+ renameGroupName.value = "";
271
+ renameGroupError.value = null;
272
+ renameGroupInputRef.value = null;
273
+ isIgnoringInitialGroupRenameBlur.value = false;
274
+ clearGroupRenameBlurTimer();
275
+ clearGroupRenameSelectionTimers();
276
+ }
277
+ function cancelRenameGroup() {
278
+ if (groupActionLoading.value)
279
+ return;
280
+ resetRenameGroupState();
281
+ }
282
+ function formatConversationCount(count) {
283
+ return `${count} ${count === 1 ? "conversa" : "conversas"}`;
284
+ }
285
+ function groupConversationsMoveMessage(count) {
286
+ return count === 1 ? "A conversa desta pasta ser\xE1 movida para Conversas." : "As conversas desta pasta ser\xE3o movidas para Conversas.";
287
+ }
288
+ function isActiveConversation(conversationId) {
289
+ return options.currentId === conversationId || route.params.id === conversationId;
290
+ }
291
+ function resetAfterActiveConversationDeleted() {
292
+ if (options.resetConversation) {
293
+ void options.resetConversation();
294
+ } else {
295
+ router.push("/");
296
+ }
297
+ }
298
+ async function confirmDeleteGroup() {
299
+ const target = deleteGroupTarget.value;
300
+ if (!target || groupActionLoading.value)
301
+ return;
302
+ groupActionLoading.value = true;
303
+ try {
304
+ const deleted = await deleteGroup(target.id);
305
+ if (deleted) {
306
+ setGroupOpen(target.id, false);
307
+ deleteGroupTarget.value = null;
308
+ showDeleteGroupModal.value = false;
309
+ }
310
+ } finally {
311
+ groupActionLoading.value = false;
312
+ }
313
+ }
314
+ async function confirmDeleteGroupConversations() {
315
+ const target = deleteGroupConversationsTarget.value;
316
+ if (!target || groupActionLoading.value)
317
+ return;
318
+ groupActionLoading.value = true;
319
+ let deletedActiveConversation = false;
320
+ const failedConversationIds = [];
321
+ try {
322
+ for (const conversationId of target.conversationIds) {
323
+ const deleted = await deleteConversation(conversationId);
324
+ if (!deleted) {
325
+ failedConversationIds.push(conversationId);
326
+ continue;
327
+ }
328
+ if (deleted && isActiveConversation(conversationId)) {
329
+ deletedActiveConversation = true;
330
+ }
331
+ }
332
+ if (failedConversationIds.length > 0) {
333
+ deleteGroupConversationsTarget.value = {
334
+ ...target,
335
+ conversationIds: failedConversationIds,
336
+ conversationCount: failedConversationIds.length
337
+ };
338
+ if (deletedActiveConversation) {
339
+ resetAfterActiveConversationDeleted();
340
+ }
341
+ return;
342
+ }
343
+ deleteGroupConversationsTarget.value = null;
344
+ showDeleteGroupConversationsModal.value = false;
345
+ if (deletedActiveConversation) {
346
+ resetAfterActiveConversationDeleted();
347
+ }
348
+ } finally {
349
+ groupActionLoading.value = false;
350
+ }
351
+ }
352
+ async function submitRenameGroup() {
353
+ const groupId = renamingGroupId.value;
354
+ const name = renameGroupName.value.trim();
355
+ const currentGroupName = groups.value.find((group) => group.id === groupId)?.name;
356
+ if (!groupId || groupActionLoading.value)
357
+ return;
358
+ if (!name || name === currentGroupName) {
359
+ cancelRenameGroup();
360
+ return;
361
+ }
362
+ renameGroupError.value = null;
363
+ groupActionLoading.value = true;
364
+ try {
365
+ const renamed = await renameGroup(groupId, name);
366
+ if (renamed) {
367
+ resetRenameGroupState();
368
+ } else {
369
+ renameGroupError.value = groupError.value || "Erro ao renomear pasta";
370
+ }
371
+ } finally {
372
+ groupActionLoading.value = false;
373
+ }
374
+ }
375
+ function handleRenameGroupBlur() {
376
+ if (isIgnoringInitialGroupRenameBlur.value)
377
+ return;
378
+ void submitRenameGroup();
379
+ }
380
+ async function handleDelete(id) {
381
+ const shouldResetConversation = options.currentId === id || route.params.id === id;
382
+ const deleted = await deleteConversation(id);
383
+ if (deleted && shouldResetConversation) {
384
+ resetAfterActiveConversationDeleted();
385
+ }
386
+ }
387
+ async function handleRename(id, title) {
388
+ await renameConversation(id, title);
389
+ }
390
+ async function handleDuplicate(id) {
391
+ const duplicated = await duplicateConversation(id);
392
+ if (duplicated) {
393
+ setConversation(duplicated);
394
+ if (options.selectConversation) {
395
+ void options.selectConversation(duplicated.id);
396
+ } else {
397
+ router.push(`/${duplicated.id}`);
398
+ }
399
+ }
400
+ }
401
+ async function handleExport(id) {
402
+ try {
403
+ const conversation = await $fetch(
404
+ chatApi.path(`/conversations/${id}`),
405
+ chatApi.withChatHeaders()
406
+ );
407
+ exportConversationToMarkdown(conversation);
408
+ } catch (err) {
409
+ console.error("Failed to export conversation:", err);
410
+ }
411
+ }
412
+ function handleConversationDragStart(id) {
413
+ draggedConversationId.value = id;
414
+ }
415
+ function clearDragState() {
416
+ draggedConversationId.value = null;
417
+ dropTargetGroupId.value = void 0;
418
+ clearDragOpenGroup();
419
+ }
420
+ function clearDragOpenGroup() {
421
+ if (dragOpenGroupTimeout) {
422
+ clearTimeout(dragOpenGroupTimeout);
423
+ }
424
+ dragOpenGroupTimeout = null;
425
+ dragOpenGroupId = null;
426
+ }
427
+ function scheduleDragOpenGroup(groupId) {
428
+ if (!groupId || isGroupOpen(groupId)) {
429
+ clearDragOpenGroup();
430
+ return;
431
+ }
432
+ if (dragOpenGroupId === groupId)
433
+ return;
434
+ clearDragOpenGroup();
435
+ dragOpenGroupId = groupId;
436
+ dragOpenGroupTimeout = setTimeout(() => {
437
+ setGroupOpen(groupId, true);
438
+ clearDragOpenGroup();
439
+ }, DRAG_OPEN_GROUP_DELAY_MS);
440
+ }
441
+ function handleDragEnter(groupId) {
442
+ if (!draggedConversationId.value)
443
+ return;
444
+ dropTargetGroupId.value = groupId;
445
+ scheduleDragOpenGroup(groupId);
446
+ }
447
+ function handleDragOver(event, groupId) {
448
+ if (!draggedConversationId.value)
449
+ return;
450
+ if (event.dataTransfer) {
451
+ event.dataTransfer.dropEffect = "move";
452
+ }
453
+ dropTargetGroupId.value = groupId;
454
+ scheduleDragOpenGroup(groupId);
455
+ }
456
+ function handleDragLeave(event, groupId) {
457
+ const currentTarget = event.currentTarget;
458
+ const relatedTarget = event.relatedTarget;
459
+ if (currentTarget && relatedTarget && currentTarget.contains(relatedTarget))
460
+ return;
461
+ if (dropTargetGroupId.value === groupId) {
462
+ dropTargetGroupId.value = void 0;
463
+ clearDragOpenGroup();
464
+ }
465
+ }
466
+ function isDropTarget(groupId) {
467
+ return draggedConversationId.value !== null && dropTargetGroupId.value === groupId;
468
+ }
469
+ function getConversationItemProps(conversation) {
470
+ return {
471
+ conversation,
472
+ active: conversation.id === options.currentId,
473
+ draggable: true,
474
+ userImage: options.getMemberImage?.(conversation.userId),
475
+ selectConversation: options.selectConversation
476
+ };
477
+ }
478
+ async function handleConversationDrop(event, groupId) {
479
+ const conversationId = event.dataTransfer?.getData("text/plain") || draggedConversationId.value;
480
+ clearDragState();
481
+ if (!conversationId)
482
+ return;
483
+ await moveConversationToGroup(conversationId, groupId);
484
+ }
485
+ function startConversationInGroup(groupId) {
486
+ setGroupOpen(groupId, true);
487
+ if (options.startConversation) {
488
+ void options.startConversation(groupId);
489
+ } else if (options.resetConversation) {
490
+ void options.resetConversation();
491
+ }
492
+ }
493
+ onBeforeUnmount(() => {
494
+ clearDragOpenGroup();
495
+ clearGroupRenameBlurTimer();
496
+ clearGroupRenameSelectionTimers();
497
+ });
498
+ return {
499
+ conversationSearch,
500
+ groupedConversations,
501
+ hasConversationSearchQuery,
502
+ hasConversationContent,
503
+ shouldShowSkeleton,
504
+ emptyConversationMessage,
505
+ skeletonRows: conversationListSkeletonRows,
506
+ groupItems,
507
+ ungroupedConversations,
508
+ ungroupedDropTarget,
509
+ ungroupedConversationLabel,
510
+ shouldShowUngroupedHeader,
511
+ showGroupCreationPrompt,
512
+ showDeleteGroupModal,
513
+ showDeleteGroupConversationsModal,
514
+ groupActionLoading,
515
+ deleteGroupTarget,
516
+ deleteGroupConversationsTarget,
517
+ renamingGroupId,
518
+ renameGroupName,
519
+ renameGroupError,
520
+ deleteGroupConfirmationMessage,
521
+ deleteGroupConversationsConfirmationMessage,
522
+ isGroupOpen,
523
+ setGroupOpen,
524
+ toggleGroup,
525
+ openDeleteGroupModal,
526
+ openDeleteGroupConversationsModal,
527
+ startRenameGroup,
528
+ setRenameGroupInputRef,
529
+ getGroupMenuItems,
530
+ clearDeleteGroupTarget,
531
+ clearDeleteGroupConversationsTarget,
532
+ cancelRenameGroup,
533
+ formatConversationCount,
534
+ groupConversationsMoveMessage,
535
+ confirmDeleteGroup,
536
+ confirmDeleteGroupConversations,
537
+ submitRenameGroup,
538
+ handleRenameGroupBlur,
539
+ handleDelete,
540
+ handleRename,
541
+ handleDuplicate,
542
+ handleExport,
543
+ handleConversationDragStart,
544
+ clearDragState,
545
+ handleDragEnter,
546
+ handleDragOver,
547
+ handleDragLeave,
548
+ isDropTarget,
549
+ getConversationItemProps,
550
+ handleConversationDrop,
551
+ startConversationInGroup
552
+ };
553
+ }
@@ -13,8 +13,10 @@ export declare function useConversations(): {
13
13
  }) => Promise<Conversation | null>;
14
14
  deleteConversation: (id: string) => Promise<boolean>;
15
15
  renameConversation: (id: string, title: string) => Promise<boolean>;
16
+ moveConversationToGroup: (id: string, groupId: string | null) => Promise<boolean>;
16
17
  duplicateConversation: (id: string) => Promise<ConversationWithMessages | null>;
17
18
  conversationCreatorFilter: any;
18
19
  conversationCreatorFilterCounts: any;
19
20
  updateConversationInList: (updated: Conversation | DeepReadonly<Conversation>) => void;
21
+ clearGroupFromConversations: (groupId: string) => (() => void);
20
22
  };
@@ -298,6 +298,7 @@ export function useConversations() {
298
298
  const optimisticConversation = {
299
299
  id: createOptimisticConversationId(),
300
300
  workspaceId: embedConfig?.workspaceId.value || activeOrganization.value?.id || "",
301
+ groupId: null,
301
302
  userId: authUser.value?.id ?? "",
302
303
  title: "Nova conversa",
303
304
  threadSessionId: null,
@@ -371,6 +372,47 @@ export function useConversations() {
371
372
  const updateConversationInList = (updated) => {
372
373
  updateConversationInLoadedBuckets(updated);
373
374
  };
375
+ const moveConversationToGroup = async (id, groupId) => {
376
+ error.value = null;
377
+ const requestRuntimeScope = runtimeScope.value;
378
+ const affectedScopes = activeAndLoadedRuntimeConversationScopes(requestRuntimeScope);
379
+ const conversationSnapshot = snapshotConversationBuckets(affectedScopes);
380
+ const conversation = findConversationInLoadedBuckets(id, requestRuntimeScope);
381
+ if (!conversation)
382
+ return false;
383
+ updateConversationInLoadedBuckets({ ...conversation, groupId }, requestRuntimeScope);
384
+ try {
385
+ const updated = await $fetch(
386
+ chatApi.path(`/conversations/${id}/group`),
387
+ chatApi.withChatHeaders({
388
+ method: "PATCH",
389
+ body: { groupId }
390
+ })
391
+ );
392
+ updateConversationInLoadedBuckets(updated, requestRuntimeScope);
393
+ return true;
394
+ } catch (err) {
395
+ restoreConversationBuckets(conversationSnapshot, id);
396
+ const message = err instanceof Error ? err.message : "Erro ao mover conversa";
397
+ error.value = message;
398
+ Sentry.captureException(err);
399
+ return false;
400
+ }
401
+ };
402
+ const clearGroupFromConversations = (groupId) => {
403
+ const affectedScopes = activeAndLoadedRuntimeConversationScopes(runtimeScope.value).filter((scope) => bucketValue(conversationBuckets, scope, []).some((conversation) => conversation.groupId === groupId));
404
+ const conversationSnapshot = snapshotConversationBuckets(affectedScopes);
405
+ for (const scope of affectedScopes) {
406
+ const currentConversations = bucketValue(conversationBuckets, scope, []);
407
+ commitConversationsToScope(
408
+ scope,
409
+ currentConversations.map(
410
+ (conversation) => conversation.groupId === groupId ? { ...conversation, groupId: null } : conversation
411
+ )
412
+ );
413
+ }
414
+ return () => restoreConversationBuckets(conversationSnapshot);
415
+ };
374
416
  const renameConversation = async (id, title) => {
375
417
  error.value = null;
376
418
  const requestRuntimeScope = runtimeScope.value;
@@ -434,9 +476,11 @@ export function useConversations() {
434
476
  createConversation,
435
477
  deleteConversation,
436
478
  renameConversation,
479
+ moveConversationToGroup,
437
480
  duplicateConversation,
438
481
  conversationCreatorFilter,
439
482
  conversationCreatorFilterCounts,
440
- updateConversationInList
483
+ updateConversationInList,
484
+ clearGroupFromConversations
441
485
  };
442
486
  }
@@ -0,0 +1,5 @@
1
+ export declare function usePendingConversationGroup(): {
2
+ pendingConversationGroupId: any;
3
+ setPendingConversationGroup: (groupId?: string | null) => void;
4
+ clearPendingConversationGroup: () => void;
5
+ };
@@ -0,0 +1,33 @@
1
+ import { useChatAuth } from "#chat-auth";
2
+ import { resolveChatStateScope } from "../utils/tela-chat.js";
3
+ const PENDING_CONVERSATION_GROUP_STATE_KEY = "chat-pending-conversation-group";
4
+ export function usePendingConversationGroup() {
5
+ const embedConfig = useEmbedConfig();
6
+ const { activeOrganization } = useChatAuth();
7
+ const pendingConversationGroups = useState(
8
+ PENDING_CONVERSATION_GROUP_STATE_KEY,
9
+ () => ({})
10
+ );
11
+ const runtimeScope = computed(() => resolveChatStateScope(
12
+ embedConfig?.workspaceId.value || activeOrganization.value?.id,
13
+ embedConfig?.telaAgentId.value,
14
+ embedConfig?.conversationScope?.value
15
+ ));
16
+ const pendingConversationGroupId = computed(
17
+ () => pendingConversationGroups.value[runtimeScope.value] ?? null
18
+ );
19
+ function setPendingConversationGroup(groupId) {
20
+ pendingConversationGroups.value = {
21
+ ...pendingConversationGroups.value,
22
+ [runtimeScope.value]: groupId ?? null
23
+ };
24
+ }
25
+ function clearPendingConversationGroup() {
26
+ setPendingConversationGroup(null);
27
+ }
28
+ return {
29
+ pendingConversationGroupId,
30
+ setPendingConversationGroup,
31
+ clearPendingConversationGroup
32
+ };
33
+ }
@@ -18,8 +18,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
18
18
  onSave?: ((payload: ChatConfigurationSavePayload) => any) | undefined;
19
19
  "onUpdate:open"?: ((value: boolean) => any) | undefined;
20
20
  }>, {
21
- initialTab: ChatConfigurationTab;
22
21
  saving: boolean;
22
+ initialTab: ChatConfigurationTab;
23
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
24
  declare const _default: typeof __VLS_export;
25
25
  export default _default;
@@ -18,8 +18,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
18
18
  onSave?: ((payload: ChatConfigurationSavePayload) => any) | undefined;
19
19
  "onUpdate:open"?: ((value: boolean) => any) | undefined;
20
20
  }>, {
21
- initialTab: ChatConfigurationTab;
22
21
  saving: boolean;
22
+ initialTab: ChatConfigurationTab;
23
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
24
  declare const _default: typeof __VLS_export;
25
25
  export default _default;