@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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +15 -0
- package/dist/runtime/components/chat/conversation-item.d.vue.ts +6 -1
- package/dist/runtime/components/chat/conversation-item.vue +30 -141
- package/dist/runtime/components/chat/conversation-item.vue.d.ts +6 -1
- package/dist/runtime/components/chat/conversation-list.d.vue.ts +1 -0
- package/dist/runtime/components/chat/conversation-list.vue +300 -72
- package/dist/runtime/components/chat/conversation-list.vue.d.ts +1 -0
- package/dist/runtime/components/chat/conversation-new-group-button.d.vue.ts +7 -0
- package/dist/runtime/components/chat/conversation-new-group-button.vue +90 -0
- package/dist/runtime/components/chat/conversation-new-group-button.vue.d.ts +7 -0
- package/dist/runtime/components/chat/conversation-new-group-popover-content.d.vue.ts +23 -0
- package/dist/runtime/components/chat/conversation-new-group-popover-content.vue +75 -0
- package/dist/runtime/components/chat/conversation-new-group-popover-content.vue.d.ts +23 -0
- package/dist/runtime/components/chat/mobile/shell/drawer.d.vue.ts +2 -2
- package/dist/runtime/components/chat/mobile/shell/drawer.vue +370 -41
- package/dist/runtime/components/chat/mobile/shell/drawer.vue.d.ts +2 -2
- package/dist/runtime/components/chat/mobile/shell/shell.d.vue.ts +6 -6
- package/dist/runtime/components/chat/mobile/shell/shell.vue +1 -1
- package/dist/runtime/components/chat/mobile/shell/shell.vue.d.ts +6 -6
- package/dist/runtime/components/chat/topbar.d.vue.ts +2 -2
- package/dist/runtime/components/chat/topbar.vue.d.ts +2 -2
- package/dist/runtime/composables/useConversationGroups.d.ts +12 -0
- package/dist/runtime/composables/useConversationGroups.js +160 -0
- package/dist/runtime/composables/useConversationItem.d.ts +48 -0
- package/dist/runtime/composables/useConversationItem.js +176 -0
- package/dist/runtime/composables/useConversationList.d.ts +128 -0
- package/dist/runtime/composables/useConversationList.js +553 -0
- package/dist/runtime/composables/useConversations.d.ts +2 -0
- package/dist/runtime/composables/useConversations.js +45 -1
- package/dist/runtime/composables/usePendingConversationGroup.d.ts +5 -0
- package/dist/runtime/composables/usePendingConversationGroup.js +33 -0
- package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +1 -1
- package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +1 -1
- package/dist/runtime/embed/components/ChatEmbedInner.vue +24 -5
- package/dist/runtime/server/api/chat/conversations/[id]/group.patch.d.ts +1 -0
- package/dist/runtime/server/api/chat/conversations/[id]/group.patch.js +1 -0
- package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.delete.d.ts +1 -0
- package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.delete.js +1 -0
- package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.patch.d.ts +1 -0
- package/dist/runtime/server/api/chat/conversations/groups/[groupId]/index.patch.js +1 -0
- package/dist/runtime/server/api/chat/conversations/groups/index.get.d.ts +1 -0
- package/dist/runtime/server/api/chat/conversations/groups/index.get.js +1 -0
- package/dist/runtime/server/api/chat/conversations/groups/index.post.d.ts +1 -0
- package/dist/runtime/server/api/chat/conversations/groups/index.post.js +1 -0
- package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +1 -0
- package/dist/runtime/server/api/conversations/[id]/group.patch.d.ts +2 -0
- package/dist/runtime/server/api/conversations/[id]/group.patch.js +46 -0
- package/dist/runtime/server/api/conversations/groups/[groupId]/index.delete.d.ts +4 -0
- package/dist/runtime/server/api/conversations/groups/[groupId]/index.delete.js +25 -0
- package/dist/runtime/server/api/conversations/groups/[groupId]/index.patch.d.ts +2 -0
- package/dist/runtime/server/api/conversations/groups/[groupId]/index.patch.js +30 -0
- package/dist/runtime/server/api/conversations/groups/index.get.d.ts +2 -0
- package/dist/runtime/server/api/conversations/groups/index.get.js +13 -0
- package/dist/runtime/server/api/conversations/groups/index.post.d.ts +2 -0
- package/dist/runtime/server/api/conversations/groups/index.post.js +26 -0
- package/dist/runtime/server/api/conversations/index.post.js +1 -0
- package/dist/runtime/server/db/schema/conversation-groups.d.ts +143 -0
- package/dist/runtime/server/db/schema/conversation-groups.js +20 -0
- package/dist/runtime/server/db/schema/conversations.d.ts +17 -0
- package/dist/runtime/server/db/schema/conversations.js +3 -0
- package/dist/runtime/server/db/schema/index.d.ts +1 -0
- package/dist/runtime/server/db/schema/index.js +1 -0
- package/dist/runtime/server/utils/conversation-group.d.ts +4 -0
- package/dist/runtime/server/utils/conversation-group.js +48 -0
- package/dist/runtime/types/chat.d.ts +2 -1
- package/dist/runtime/utils/conversation-groups.d.ts +18 -0
- package/dist/runtime/utils/conversation-groups.js +30 -0
- package/drizzle/0016_dry_aaron_stack.sql +16 -0
- package/drizzle/meta/0016_snapshot.json +887 -0
- package/drizzle/meta/_journal.json +7 -0
- 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,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;
|