@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
|
@@ -12,11 +12,11 @@ type __VLS_Props = {
|
|
|
12
12
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
13
13
|
select: (id: string) => any;
|
|
14
14
|
close: () => any;
|
|
15
|
-
newConvo: () => any;
|
|
15
|
+
newConvo: (groupId?: string | null | undefined) => any;
|
|
16
16
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
17
17
|
onSelect?: ((id: string) => any) | undefined;
|
|
18
18
|
onClose?: (() => any) | undefined;
|
|
19
|
-
onNewConvo?: (() => any) | undefined;
|
|
19
|
+
onNewConvo?: ((groupId?: string | null | undefined) => any) | undefined;
|
|
20
20
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
21
|
declare const _default: typeof __VLS_export;
|
|
22
22
|
export default _default;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { useChatAuth } from "#chat-auth";
|
|
2
3
|
import { formatRelativeTime } from "../../../../utils/format-time";
|
|
3
4
|
import { conversationListSkeletonRows } from "../../../../utils/conversation-list-skeleton";
|
|
4
5
|
import { filterConversationsByTitle } from "../../../../utils/conversation-search";
|
|
6
|
+
import { groupConversationsByGroup } from "../../../../utils/conversation-groups";
|
|
7
|
+
import { resolveChatStateScope } from "../../../../utils/tela-chat";
|
|
5
8
|
const props = defineProps({
|
|
6
9
|
open: { type: Boolean, required: true },
|
|
7
10
|
conversations: { type: null, required: true },
|
|
@@ -12,11 +15,28 @@ const props = defineProps({
|
|
|
12
15
|
userImage: { type: String, required: false }
|
|
13
16
|
});
|
|
14
17
|
const emit = defineEmits(["close", "select", "newConvo"]);
|
|
18
|
+
const DRAG_OPEN_GROUP_DELAY_MS = 400;
|
|
15
19
|
const initial = computed(() => {
|
|
16
20
|
const source = props.userName ?? props.userEmail ?? "U";
|
|
17
21
|
return source.charAt(0).toUpperCase();
|
|
18
22
|
});
|
|
19
23
|
const conversationSearch = ref("");
|
|
24
|
+
const draggedConversationId = ref(null);
|
|
25
|
+
const dropTargetGroupId = ref(void 0);
|
|
26
|
+
const openGroupIds = ref(/* @__PURE__ */ new Set());
|
|
27
|
+
const groupsReady = ref(false);
|
|
28
|
+
let dragOpenGroupTimeout = null;
|
|
29
|
+
let dragOpenGroupId = null;
|
|
30
|
+
const embedConfig = useEmbedConfig();
|
|
31
|
+
const { activeOrganization } = useChatAuth();
|
|
32
|
+
const { groups, fetchGroups } = useConversationGroups();
|
|
33
|
+
const { moveConversationToGroup } = useConversations();
|
|
34
|
+
const runtimeScope = computed(() => resolveChatStateScope(
|
|
35
|
+
embedConfig?.workspaceId.value || activeOrganization.value?.id,
|
|
36
|
+
embedConfig?.telaAgentId.value,
|
|
37
|
+
embedConfig?.conversationScope?.value
|
|
38
|
+
));
|
|
39
|
+
const openGroupsStorageKey = computed(() => `conversation-group-open:${runtimeScope.value}`);
|
|
20
40
|
const filteredConversations = computed(
|
|
21
41
|
() => filterConversationsByTitle(props.conversations, conversationSearch.value, {
|
|
22
42
|
fallbackTitle: "Nova conversa",
|
|
@@ -24,14 +44,179 @@ const filteredConversations = computed(
|
|
|
24
44
|
})
|
|
25
45
|
);
|
|
26
46
|
const hasConversationSearchQuery = computed(() => conversationSearch.value.trim().length > 0);
|
|
47
|
+
const filteredGroupedConversations = computed(() => groupConversationsByGroup(filteredConversations.value, groups.value));
|
|
48
|
+
const allGroupedConversations = computed(() => groupConversationsByGroup(props.conversations, groups.value));
|
|
49
|
+
const groupedConversations = computed(() => {
|
|
50
|
+
if (!hasConversationSearchQuery.value)
|
|
51
|
+
return filteredGroupedConversations.value;
|
|
52
|
+
const allItemsByGroupId = new Map(
|
|
53
|
+
allGroupedConversations.value.groups.map((groupBucket) => [groupBucket.group.id, groupBucket.items])
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
groups: filteredGroupedConversations.value.groups.map(
|
|
57
|
+
(groupBucket) => doesGroupMatchSearch(groupBucket.group.name) ? {
|
|
58
|
+
...groupBucket,
|
|
59
|
+
items: allItemsByGroupId.get(groupBucket.group.id) ?? []
|
|
60
|
+
} : groupBucket
|
|
61
|
+
),
|
|
62
|
+
unfiled: filteredGroupedConversations.value.unfiled
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
const visibleGroupBuckets = computed(() => groupedConversations.value.groups.filter((groupBucket) => {
|
|
66
|
+
if (!hasConversationSearchQuery.value)
|
|
67
|
+
return true;
|
|
68
|
+
return groupBucket.items.length > 0 || doesGroupMatchSearch(groupBucket.group.name);
|
|
69
|
+
}).map((groupBucket) => {
|
|
70
|
+
const open = isGroupOpen(groupBucket.group.id) || hasConversationSearchQuery.value && (groupBucket.items.length > 0 || doesGroupMatchSearch(groupBucket.group.name));
|
|
71
|
+
return {
|
|
72
|
+
...groupBucket,
|
|
73
|
+
open,
|
|
74
|
+
folderIcon: open ? "i-ph-folder-open" : "i-ph-folder",
|
|
75
|
+
conversationCountLabel: groupBucket.items.length > 0 ? String(groupBucket.items.length) : null,
|
|
76
|
+
showNewConversationAction: !hasConversationSearchQuery.value,
|
|
77
|
+
showSearchEmpty: hasConversationSearchQuery.value && groupBucket.items.length === 0
|
|
78
|
+
};
|
|
79
|
+
}));
|
|
80
|
+
const showGroupCreationPrompt = computed(
|
|
81
|
+
() => groupsReady.value && !hasConversationSearchQuery.value && groups.value.length === 0
|
|
82
|
+
);
|
|
83
|
+
const hasConversationContent = computed(() => {
|
|
84
|
+
if (hasConversationSearchQuery.value)
|
|
85
|
+
return visibleGroupBuckets.value.length > 0 || groupedConversations.value.unfiled.length > 0;
|
|
86
|
+
return showGroupCreationPrompt.value || groups.value.length > 0 || filteredConversations.value.length > 0;
|
|
87
|
+
});
|
|
88
|
+
const shouldShowUngroupedHeader = computed(() => visibleGroupBuckets.value.length > 0);
|
|
89
|
+
const ungroupedConversationLabel = computed(() => `Conversas \xB7 ${groupedConversations.value.unfiled.length}`);
|
|
90
|
+
const activeGroupId = computed(() => {
|
|
91
|
+
const currentConversation = props.conversations.find((conversation) => conversation.id === props.activeId);
|
|
92
|
+
return currentConversation?.groupId ?? null;
|
|
93
|
+
});
|
|
94
|
+
onMounted(() => {
|
|
95
|
+
void Promise.resolve(fetchGroups()).finally(() => {
|
|
96
|
+
groupsReady.value = true;
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
const stored = localStorage.getItem(openGroupsStorageKey.value);
|
|
100
|
+
if (stored) {
|
|
101
|
+
openGroupIds.value = new Set(JSON.parse(stored));
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
watch(openGroupIds, (groupIds) => {
|
|
107
|
+
if (typeof localStorage === "undefined")
|
|
108
|
+
return;
|
|
109
|
+
try {
|
|
110
|
+
localStorage.setItem(openGroupsStorageKey.value, JSON.stringify([...groupIds]));
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
watch(activeGroupId, (groupId) => {
|
|
115
|
+
if (groupId) {
|
|
116
|
+
setGroupOpen(groupId, true);
|
|
117
|
+
}
|
|
118
|
+
}, { immediate: true });
|
|
119
|
+
onBeforeUnmount(() => {
|
|
120
|
+
clearDragOpenGroup();
|
|
121
|
+
});
|
|
122
|
+
function isGroupOpen(groupId) {
|
|
123
|
+
return openGroupIds.value.has(groupId);
|
|
124
|
+
}
|
|
125
|
+
function setGroupOpen(groupId, open) {
|
|
126
|
+
const nextOpenGroupIds = new Set(openGroupIds.value);
|
|
127
|
+
if (open) {
|
|
128
|
+
nextOpenGroupIds.add(groupId);
|
|
129
|
+
} else {
|
|
130
|
+
nextOpenGroupIds.delete(groupId);
|
|
131
|
+
}
|
|
132
|
+
openGroupIds.value = nextOpenGroupIds;
|
|
133
|
+
}
|
|
134
|
+
function toggleGroup(groupId) {
|
|
135
|
+
setGroupOpen(groupId, !isGroupOpen(groupId));
|
|
136
|
+
}
|
|
137
|
+
function doesGroupMatchSearch(groupName) {
|
|
138
|
+
const query = conversationSearch.value.trim().toLowerCase();
|
|
139
|
+
return query.length > 0 && groupName.toLowerCase().includes(query);
|
|
140
|
+
}
|
|
27
141
|
function handleSelect(id) {
|
|
28
142
|
emit("select", id);
|
|
29
143
|
emit("close");
|
|
30
144
|
}
|
|
31
|
-
function handleNew() {
|
|
32
|
-
|
|
145
|
+
function handleNew(groupId) {
|
|
146
|
+
if (groupId) {
|
|
147
|
+
setGroupOpen(groupId, true);
|
|
148
|
+
}
|
|
149
|
+
emit("newConvo", groupId);
|
|
33
150
|
emit("close");
|
|
34
151
|
}
|
|
152
|
+
function handleConversationDragStart(event, id) {
|
|
153
|
+
draggedConversationId.value = id;
|
|
154
|
+
event.dataTransfer?.setData("text/plain", id);
|
|
155
|
+
if (event.dataTransfer) {
|
|
156
|
+
event.dataTransfer.effectAllowed = "move";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function clearDragState() {
|
|
160
|
+
draggedConversationId.value = null;
|
|
161
|
+
dropTargetGroupId.value = void 0;
|
|
162
|
+
clearDragOpenGroup();
|
|
163
|
+
}
|
|
164
|
+
function clearDragOpenGroup() {
|
|
165
|
+
if (dragOpenGroupTimeout) {
|
|
166
|
+
clearTimeout(dragOpenGroupTimeout);
|
|
167
|
+
}
|
|
168
|
+
dragOpenGroupTimeout = null;
|
|
169
|
+
dragOpenGroupId = null;
|
|
170
|
+
}
|
|
171
|
+
function scheduleDragOpenGroup(groupId) {
|
|
172
|
+
if (!groupId || isGroupOpen(groupId)) {
|
|
173
|
+
clearDragOpenGroup();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (dragOpenGroupId === groupId)
|
|
177
|
+
return;
|
|
178
|
+
clearDragOpenGroup();
|
|
179
|
+
dragOpenGroupId = groupId;
|
|
180
|
+
dragOpenGroupTimeout = setTimeout(() => {
|
|
181
|
+
setGroupOpen(groupId, true);
|
|
182
|
+
clearDragOpenGroup();
|
|
183
|
+
}, DRAG_OPEN_GROUP_DELAY_MS);
|
|
184
|
+
}
|
|
185
|
+
function handleDragEnter(groupId) {
|
|
186
|
+
if (!draggedConversationId.value)
|
|
187
|
+
return;
|
|
188
|
+
dropTargetGroupId.value = groupId;
|
|
189
|
+
scheduleDragOpenGroup(groupId);
|
|
190
|
+
}
|
|
191
|
+
function handleDragOver(event, groupId) {
|
|
192
|
+
if (!draggedConversationId.value)
|
|
193
|
+
return;
|
|
194
|
+
if (event.dataTransfer) {
|
|
195
|
+
event.dataTransfer.dropEffect = "move";
|
|
196
|
+
}
|
|
197
|
+
dropTargetGroupId.value = groupId;
|
|
198
|
+
scheduleDragOpenGroup(groupId);
|
|
199
|
+
}
|
|
200
|
+
function handleDragLeave(event, groupId) {
|
|
201
|
+
const currentTarget = event.currentTarget;
|
|
202
|
+
const relatedTarget = event.relatedTarget;
|
|
203
|
+
if (currentTarget && relatedTarget && currentTarget.contains(relatedTarget))
|
|
204
|
+
return;
|
|
205
|
+
if (dropTargetGroupId.value === groupId) {
|
|
206
|
+
dropTargetGroupId.value = void 0;
|
|
207
|
+
clearDragOpenGroup();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function isDropTarget(groupId) {
|
|
211
|
+
return draggedConversationId.value !== null && dropTargetGroupId.value === groupId;
|
|
212
|
+
}
|
|
213
|
+
async function handleConversationDrop(event, groupId) {
|
|
214
|
+
const conversationId = event.dataTransfer?.getData("text/plain") || draggedConversationId.value;
|
|
215
|
+
clearDragState();
|
|
216
|
+
if (!conversationId)
|
|
217
|
+
return;
|
|
218
|
+
await moveConversationToGroup(conversationId, groupId);
|
|
219
|
+
}
|
|
35
220
|
</script>
|
|
36
221
|
|
|
37
222
|
<template>
|
|
@@ -84,32 +269,26 @@ function handleNew() {
|
|
|
84
269
|
</div>
|
|
85
270
|
|
|
86
271
|
<div p-12px flex="~ col" gap-8px>
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
272
|
+
<div flex gap-8px>
|
|
273
|
+
<button
|
|
274
|
+
flex items-center justify-center gap-6px
|
|
275
|
+
flex-1 min-w-0 px-12px py-10px rounded-10px
|
|
276
|
+
bg-white b=".5px neutral-300" shadow-sm
|
|
277
|
+
class="hover:bg-neutral-50"
|
|
278
|
+
transition-colors cursor-pointer
|
|
279
|
+
@click="handleNew()"
|
|
280
|
+
>
|
|
281
|
+
<span i-ph-chat-circle text-16px text-neutral-900 />
|
|
282
|
+
<span text-14px font-580 text-neutral-900 leading-18px tracking="-.15px">
|
|
283
|
+
Nova conversa
|
|
284
|
+
</span>
|
|
285
|
+
</button>
|
|
286
|
+
<ChatConversationNewGroupButton />
|
|
287
|
+
</div>
|
|
100
288
|
<ChatConversationSearch v-model="conversationSearch" />
|
|
101
289
|
<ChatConversationCreatorFilter />
|
|
102
290
|
</div>
|
|
103
291
|
|
|
104
|
-
<div px-14px pt-4px pb-4px>
|
|
105
|
-
<span
|
|
106
|
-
text-12px font-medium uppercase text-neutral-400
|
|
107
|
-
style="letter-spacing: 0.5px"
|
|
108
|
-
>
|
|
109
|
-
{{ hasConversationSearchQuery ? "Resultados" : "Recentes" }}
|
|
110
|
-
</span>
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
292
|
<div flex-1 overflow-y-auto px-8px pb-8px>
|
|
114
293
|
<div v-if="loading && conversations.length === 0" flex="~ col" gap-1px py-4px>
|
|
115
294
|
<div
|
|
@@ -120,35 +299,185 @@ function handleNew() {
|
|
|
120
299
|
<TelaSkeleton rounded-6px bg-neutral-200 :style="{ width: row.width, height: '13px' }" />
|
|
121
300
|
</div>
|
|
122
301
|
</div>
|
|
123
|
-
<div v-else-if="
|
|
302
|
+
<div v-else-if="!hasConversationContent" p-16px text-center>
|
|
124
303
|
<p text-14px text-neutral-400>
|
|
125
304
|
{{ hasConversationSearchQuery ? "Nenhuma conversa encontrada" : "Nenhuma conversa ainda" }}
|
|
126
305
|
</p>
|
|
127
306
|
</div>
|
|
128
|
-
<div v-else flex="~ col" gap-
|
|
129
|
-
<
|
|
130
|
-
v-
|
|
131
|
-
|
|
132
|
-
flex="~ col" gap-
|
|
133
|
-
w-full px-12px py-10px rounded-10px text-left
|
|
134
|
-
transition-colors cursor-pointer
|
|
135
|
-
:class="conv.id === activeId ? 'bg-neutral-100' : 'hover:bg-neutral-50'"
|
|
136
|
-
@click="handleSelect(conv.id)"
|
|
307
|
+
<div v-else flex="~ col" gap-8px>
|
|
308
|
+
<div
|
|
309
|
+
v-if="showGroupCreationPrompt"
|
|
310
|
+
mx-4px
|
|
311
|
+
flex="~ col" gap-10px
|
|
137
312
|
>
|
|
138
|
-
<div flex items-center
|
|
139
|
-
<span
|
|
140
|
-
|
|
313
|
+
<div h-20px flex items-center px-4px>
|
|
314
|
+
<span text-12px font-semibold text-neutral-400 uppercase style="letter-spacing: 0">
|
|
315
|
+
PASTAS
|
|
141
316
|
</span>
|
|
142
|
-
|
|
143
|
-
|
|
317
|
+
</div>
|
|
318
|
+
<div flex="~ col" gap-6px>
|
|
319
|
+
<div
|
|
320
|
+
h-36px px-10px
|
|
321
|
+
flex items-center gap-10px
|
|
322
|
+
rounded-10px
|
|
323
|
+
b=".5px dashed neutral-200"
|
|
324
|
+
>
|
|
325
|
+
<span i-ph-folder text-16px text-neutral-400 shrink-0 />
|
|
326
|
+
<span h-10px w-120px rounded-full bg-neutral-100 />
|
|
327
|
+
</div>
|
|
328
|
+
<div
|
|
329
|
+
h-36px px-10px
|
|
330
|
+
flex items-center gap-10px
|
|
331
|
+
rounded-10px
|
|
332
|
+
b=".5px dashed neutral-200"
|
|
333
|
+
>
|
|
334
|
+
<span i-ph-folder text-16px text-neutral-400 shrink-0 />
|
|
335
|
+
<span h-10px w-84px rounded-full bg-neutral-100 />
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
<p px-4px text-14px text-neutral-500 leading-20px>
|
|
339
|
+
Suas pastas aparecem aqui. Crie uma para começar a agrupar conversas.
|
|
340
|
+
</p>
|
|
341
|
+
<ChatConversationNewGroupButton label="Criar pasta" variant="empty-state" />
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<div v-if="visibleGroupBuckets.length > 0" flex="~ col" gap-2px>
|
|
345
|
+
<div
|
|
346
|
+
v-for="groupBucket in visibleGroupBuckets"
|
|
347
|
+
:key="groupBucket.group.id"
|
|
348
|
+
flex="~ col"
|
|
349
|
+
rounded-10px
|
|
350
|
+
transition-colors
|
|
351
|
+
:class="isDropTarget(groupBucket.group.id) ? 'bg-neutral-100' : ''"
|
|
352
|
+
@dragenter.prevent="handleDragEnter(groupBucket.group.id)"
|
|
353
|
+
@dragover.prevent="handleDragOver($event, groupBucket.group.id)"
|
|
354
|
+
@dragleave="handleDragLeave($event, groupBucket.group.id)"
|
|
355
|
+
@drop.prevent="handleConversationDrop($event, groupBucket.group.id)"
|
|
356
|
+
>
|
|
357
|
+
<div rounded-10px>
|
|
358
|
+
<button
|
|
359
|
+
type="button"
|
|
360
|
+
flex items-center gap-8px
|
|
361
|
+
w-full h-40px px-10px rounded-10px text-left
|
|
362
|
+
transition-colors cursor-pointer
|
|
363
|
+
class="hover:bg-neutral-50"
|
|
364
|
+
@click="toggleGroup(groupBucket.group.id)"
|
|
365
|
+
>
|
|
366
|
+
<span
|
|
367
|
+
i-ph-caret-right
|
|
368
|
+
text-12px text-neutral-400
|
|
369
|
+
transition-transform
|
|
370
|
+
:class="groupBucket.open && 'rotate-90'"
|
|
371
|
+
/>
|
|
372
|
+
<span
|
|
373
|
+
:class="groupBucket.folderIcon"
|
|
374
|
+
text-16px text-neutral-500 shrink-0
|
|
375
|
+
/>
|
|
376
|
+
<span flex-1 text-14px font-semibold text-neutral-900 leading-18px truncate>
|
|
377
|
+
{{ groupBucket.group.name }}
|
|
378
|
+
</span>
|
|
379
|
+
<span v-if="groupBucket.conversationCountLabel" text-12px font-medium text-neutral-400 tabular-nums>
|
|
380
|
+
{{ groupBucket.conversationCountLabel }}
|
|
381
|
+
</span>
|
|
382
|
+
</button>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<Transition name="group-slide">
|
|
386
|
+
<div v-if="groupBucket.open" class="group-slide-panel">
|
|
387
|
+
<div class="group-slide__inner">
|
|
388
|
+
<div flex="~ col" gap-1px ml-18px>
|
|
389
|
+
<button
|
|
390
|
+
v-for="conv in groupBucket.items"
|
|
391
|
+
:key="conv.id"
|
|
392
|
+
draggable="true"
|
|
393
|
+
flex="~ col" gap-2px
|
|
394
|
+
w-full px-12px py-10px rounded-10px text-left
|
|
395
|
+
transition-colors cursor-pointer
|
|
396
|
+
:class="conv.id === activeId ? 'bg-neutral-100' : 'hover:bg-neutral-50'"
|
|
397
|
+
@click="handleSelect(conv.id)"
|
|
398
|
+
@dragstart="handleConversationDragStart($event, conv.id)"
|
|
399
|
+
@dragend="clearDragState"
|
|
400
|
+
>
|
|
401
|
+
<div flex items-center gap-8px w-full>
|
|
402
|
+
<span flex-1 text-14px font-medium text-neutral-900 leading-18px truncate>
|
|
403
|
+
{{ conv.title || "Nova conversa" }}
|
|
404
|
+
</span>
|
|
405
|
+
<span text-12px text-neutral-400 leading-16px shrink-0>
|
|
406
|
+
{{ formatRelativeTime(conv.updatedAt) }}
|
|
407
|
+
</span>
|
|
408
|
+
</div>
|
|
409
|
+
</button>
|
|
410
|
+
<button
|
|
411
|
+
v-if="groupBucket.showNewConversationAction"
|
|
412
|
+
type="button"
|
|
413
|
+
flex items-center gap-8px
|
|
414
|
+
w-full px-12px py-10px rounded-10px text-left
|
|
415
|
+
transition-colors cursor-pointer
|
|
416
|
+
class="hover:bg-neutral-50"
|
|
417
|
+
@click="handleNew(groupBucket.group.id)"
|
|
418
|
+
>
|
|
419
|
+
<span i-ph-plus text-14px text-neutral-500 shrink-0 />
|
|
420
|
+
<span text-14px font-medium text-neutral-500 leading-18px>
|
|
421
|
+
Nova conversa
|
|
422
|
+
</span>
|
|
423
|
+
</button>
|
|
424
|
+
<div
|
|
425
|
+
v-else-if="groupBucket.showSearchEmpty"
|
|
426
|
+
px-12px py-10px
|
|
427
|
+
>
|
|
428
|
+
<span text-13px text-neutral-400 leading-17px>
|
|
429
|
+
Sem resultados
|
|
430
|
+
</span>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
</Transition>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
|
|
439
|
+
<div
|
|
440
|
+
flex="~ col" gap-1px
|
|
441
|
+
rounded-10px
|
|
442
|
+
transition-colors
|
|
443
|
+
:class="isDropTarget(null) ? 'bg-neutral-50' : ''"
|
|
444
|
+
@dragenter.prevent="handleDragEnter(null)"
|
|
445
|
+
@dragover.prevent="handleDragOver($event, null)"
|
|
446
|
+
@dragleave="handleDragLeave($event, null)"
|
|
447
|
+
@drop.prevent="handleConversationDrop($event, null)"
|
|
448
|
+
>
|
|
449
|
+
<div v-if="shouldShowUngroupedHeader" px-6px pt-4px pb-2px>
|
|
450
|
+
<span text-12px font-semibold uppercase text-neutral-400 style="letter-spacing: 0">
|
|
451
|
+
{{ ungroupedConversationLabel }}
|
|
144
452
|
</span>
|
|
145
453
|
</div>
|
|
146
|
-
|
|
454
|
+
<button
|
|
455
|
+
v-for="conv in groupedConversations.unfiled"
|
|
456
|
+
:key="conv.id"
|
|
457
|
+
draggable="true"
|
|
458
|
+
flex="~ col" gap-2px
|
|
459
|
+
w-full px-12px py-10px rounded-10px text-left
|
|
460
|
+
transition-colors cursor-pointer
|
|
461
|
+
:class="conv.id === activeId ? 'bg-neutral-100' : 'hover:bg-neutral-50'"
|
|
462
|
+
@click="handleSelect(conv.id)"
|
|
463
|
+
@dragstart="handleConversationDragStart($event, conv.id)"
|
|
464
|
+
@dragend="clearDragState"
|
|
465
|
+
>
|
|
466
|
+
<div flex items-center gap-8px w-full>
|
|
467
|
+
<span flex-1 text-14px font-medium text-neutral-900 leading-18px truncate>
|
|
468
|
+
{{ conv.title || "Nova conversa" }}
|
|
469
|
+
</span>
|
|
470
|
+
<span text-12px text-neutral-400 leading-16px shrink-0>
|
|
471
|
+
{{ formatRelativeTime(conv.updatedAt) }}
|
|
472
|
+
</span>
|
|
473
|
+
</div>
|
|
474
|
+
</button>
|
|
475
|
+
</div>
|
|
147
476
|
</div>
|
|
148
477
|
</div>
|
|
149
478
|
</div>
|
|
150
479
|
</template>
|
|
151
480
|
|
|
152
481
|
<style scoped>
|
|
153
|
-
.mobile-drawer{box-shadow:0 3px 12px rgba(103,127,148,.08),0 12px 52px rgba(103,127,148,.16);transform:translateX(-100%);transition:transform .26s cubic-bezier(.2,.8,.2,1);width:82%}.mobile-drawer--open{transform:translateX(0)}.scrim-enter-active,.scrim-leave-active{transition:opacity .2s linear}.scrim-enter-from,.scrim-leave-to{opacity:0}
|
|
482
|
+
.mobile-drawer{box-shadow:0 3px 12px rgba(103,127,148,.08),0 12px 52px rgba(103,127,148,.16);transform:translateX(-100%);transition:transform .26s cubic-bezier(.2,.8,.2,1);width:82%}.mobile-drawer--open{transform:translateX(0)}.scrim-enter-active,.scrim-leave-active{transition:opacity .2s linear}.scrim-enter-from,.scrim-leave-to{opacity:0}.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}
|
|
154
483
|
</style>
|
|
@@ -12,11 +12,11 @@ type __VLS_Props = {
|
|
|
12
12
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
13
13
|
select: (id: string) => any;
|
|
14
14
|
close: () => any;
|
|
15
|
-
newConvo: () => any;
|
|
15
|
+
newConvo: (groupId?: string | null | undefined) => any;
|
|
16
16
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
17
17
|
onSelect?: ((id: string) => any) | undefined;
|
|
18
18
|
onClose?: (() => any) | undefined;
|
|
19
|
-
onNewConvo?: (() => any) | undefined;
|
|
19
|
+
onNewConvo?: ((groupId?: string | null | undefined) => any) | undefined;
|
|
20
20
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
21
|
declare const _default: typeof __VLS_export;
|
|
22
22
|
export default _default;
|
|
@@ -50,10 +50,11 @@ type __VLS_Slots = {} & {
|
|
|
50
50
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
51
51
|
delete: () => any;
|
|
52
52
|
cancel: () => any;
|
|
53
|
-
selectConversation: (id: string) => any;
|
|
54
53
|
rename: () => any;
|
|
55
54
|
duplicate: () => any;
|
|
56
55
|
export: () => any;
|
|
56
|
+
copyLink: () => any;
|
|
57
|
+
selectConversation: (id: string) => any;
|
|
57
58
|
send: (content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
|
|
58
59
|
type: "file";
|
|
59
60
|
name: string;
|
|
@@ -70,10 +71,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
70
71
|
"update:editTitle": (value: string) => any;
|
|
71
72
|
saveTitle: () => any;
|
|
72
73
|
cancelEditing: () => any;
|
|
73
|
-
copyLink: () => any;
|
|
74
74
|
debug: () => any;
|
|
75
75
|
tabChange: (tab: TopbarTab) => any;
|
|
76
|
-
newConversation: () => any;
|
|
76
|
+
newConversation: (groupId?: string | null | undefined) => any;
|
|
77
77
|
switchWorkspace: () => any;
|
|
78
78
|
signout: () => any;
|
|
79
79
|
dismissOutdatedBanner: () => any;
|
|
@@ -82,10 +82,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
82
82
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
83
83
|
onDelete?: (() => any) | undefined;
|
|
84
84
|
onCancel?: (() => any) | undefined;
|
|
85
|
-
onSelectConversation?: ((id: string) => any) | undefined;
|
|
86
85
|
onRename?: (() => any) | undefined;
|
|
87
86
|
onDuplicate?: (() => any) | undefined;
|
|
88
87
|
onExport?: (() => any) | undefined;
|
|
88
|
+
onCopyLink?: (() => any) | undefined;
|
|
89
|
+
onSelectConversation?: ((id: string) => any) | undefined;
|
|
89
90
|
onSend?: ((content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
|
|
90
91
|
type: "file";
|
|
91
92
|
name: string;
|
|
@@ -102,10 +103,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
102
103
|
"onUpdate:editTitle"?: ((value: string) => any) | undefined;
|
|
103
104
|
onSaveTitle?: (() => any) | undefined;
|
|
104
105
|
onCancelEditing?: (() => any) | undefined;
|
|
105
|
-
onCopyLink?: (() => any) | undefined;
|
|
106
106
|
onDebug?: (() => any) | undefined;
|
|
107
107
|
onTabChange?: ((tab: TopbarTab) => any) | undefined;
|
|
108
|
-
onNewConversation?: (() => any) | undefined;
|
|
108
|
+
onNewConversation?: ((groupId?: string | null | undefined) => any) | undefined;
|
|
109
109
|
onSwitchWorkspace?: (() => any) | undefined;
|
|
110
110
|
onSignout?: (() => any) | undefined;
|
|
111
111
|
onDismissOutdatedBanner?: (() => any) | undefined;
|
|
@@ -148,7 +148,7 @@ function startNewConversationFromOutdatedSettings() {
|
|
|
148
148
|
:user-image="currentUser?.image ?? void 0"
|
|
149
149
|
@close="setDrawerOpen(false)"
|
|
150
150
|
@select="emit('selectConversation', $event)"
|
|
151
|
-
@new-convo="emit('newConversation')"
|
|
151
|
+
@new-convo="emit('newConversation', $event)"
|
|
152
152
|
/>
|
|
153
153
|
|
|
154
154
|
<ChatMobileBottomSheet
|
|
@@ -50,10 +50,11 @@ type __VLS_Slots = {} & {
|
|
|
50
50
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
51
51
|
delete: () => any;
|
|
52
52
|
cancel: () => any;
|
|
53
|
-
selectConversation: (id: string) => any;
|
|
54
53
|
rename: () => any;
|
|
55
54
|
duplicate: () => any;
|
|
56
55
|
export: () => any;
|
|
56
|
+
copyLink: () => any;
|
|
57
|
+
selectConversation: (id: string) => any;
|
|
57
58
|
send: (content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
|
|
58
59
|
type: "file";
|
|
59
60
|
name: string;
|
|
@@ -70,10 +71,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
70
71
|
"update:editTitle": (value: string) => any;
|
|
71
72
|
saveTitle: () => any;
|
|
72
73
|
cancelEditing: () => any;
|
|
73
|
-
copyLink: () => any;
|
|
74
74
|
debug: () => any;
|
|
75
75
|
tabChange: (tab: TopbarTab) => any;
|
|
76
|
-
newConversation: () => any;
|
|
76
|
+
newConversation: (groupId?: string | null | undefined) => any;
|
|
77
77
|
switchWorkspace: () => any;
|
|
78
78
|
signout: () => any;
|
|
79
79
|
dismissOutdatedBanner: () => any;
|
|
@@ -82,10 +82,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
82
82
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
83
83
|
onDelete?: (() => any) | undefined;
|
|
84
84
|
onCancel?: (() => any) | undefined;
|
|
85
|
-
onSelectConversation?: ((id: string) => any) | undefined;
|
|
86
85
|
onRename?: (() => any) | undefined;
|
|
87
86
|
onDuplicate?: (() => any) | undefined;
|
|
88
87
|
onExport?: (() => any) | undefined;
|
|
88
|
+
onCopyLink?: (() => any) | undefined;
|
|
89
|
+
onSelectConversation?: ((id: string) => any) | undefined;
|
|
89
90
|
onSend?: ((content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
|
|
90
91
|
type: "file";
|
|
91
92
|
name: string;
|
|
@@ -102,10 +103,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
102
103
|
"onUpdate:editTitle"?: ((value: string) => any) | undefined;
|
|
103
104
|
onSaveTitle?: (() => any) | undefined;
|
|
104
105
|
onCancelEditing?: (() => any) | undefined;
|
|
105
|
-
onCopyLink?: (() => any) | undefined;
|
|
106
106
|
onDebug?: (() => any) | undefined;
|
|
107
107
|
onTabChange?: ((tab: TopbarTab) => any) | undefined;
|
|
108
|
-
onNewConversation?: (() => any) | undefined;
|
|
108
|
+
onNewConversation?: ((groupId?: string | null | undefined) => any) | undefined;
|
|
109
109
|
onSwitchWorkspace?: (() => any) | undefined;
|
|
110
110
|
onSignout?: (() => any) | undefined;
|
|
111
111
|
onDismissOutdatedBanner?: (() => any) | undefined;
|
|
@@ -19,11 +19,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
19
19
|
rename: () => any;
|
|
20
20
|
duplicate: () => any;
|
|
21
21
|
export: () => any;
|
|
22
|
+
copyLink: () => any;
|
|
22
23
|
"update:isEditing": (value: boolean) => any;
|
|
23
24
|
"update:editTitle": (value: string) => any;
|
|
24
25
|
saveTitle: () => any;
|
|
25
26
|
cancelEditing: () => any;
|
|
26
|
-
copyLink: () => any;
|
|
27
27
|
debug: () => any;
|
|
28
28
|
tabChange: (tab: TopbarTab) => any;
|
|
29
29
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
@@ -31,11 +31,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
31
31
|
onRename?: (() => any) | undefined;
|
|
32
32
|
onDuplicate?: (() => any) | undefined;
|
|
33
33
|
onExport?: (() => any) | undefined;
|
|
34
|
+
onCopyLink?: (() => any) | undefined;
|
|
34
35
|
"onUpdate:isEditing"?: ((value: boolean) => any) | undefined;
|
|
35
36
|
"onUpdate:editTitle"?: ((value: string) => any) | undefined;
|
|
36
37
|
onSaveTitle?: (() => any) | undefined;
|
|
37
38
|
onCancelEditing?: (() => any) | undefined;
|
|
38
|
-
onCopyLink?: (() => any) | undefined;
|
|
39
39
|
onDebug?: (() => any) | undefined;
|
|
40
40
|
onTabChange?: ((tab: TopbarTab) => any) | undefined;
|
|
41
41
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -19,11 +19,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
19
19
|
rename: () => any;
|
|
20
20
|
duplicate: () => any;
|
|
21
21
|
export: () => any;
|
|
22
|
+
copyLink: () => any;
|
|
22
23
|
"update:isEditing": (value: boolean) => any;
|
|
23
24
|
"update:editTitle": (value: string) => any;
|
|
24
25
|
saveTitle: () => any;
|
|
25
26
|
cancelEditing: () => any;
|
|
26
|
-
copyLink: () => any;
|
|
27
27
|
debug: () => any;
|
|
28
28
|
tabChange: (tab: TopbarTab) => any;
|
|
29
29
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
@@ -31,11 +31,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
31
31
|
onRename?: (() => any) | undefined;
|
|
32
32
|
onDuplicate?: (() => any) | undefined;
|
|
33
33
|
onExport?: (() => any) | undefined;
|
|
34
|
+
onCopyLink?: (() => any) | undefined;
|
|
34
35
|
"onUpdate:isEditing"?: ((value: boolean) => any) | undefined;
|
|
35
36
|
"onUpdate:editTitle"?: ((value: string) => any) | undefined;
|
|
36
37
|
onSaveTitle?: (() => any) | undefined;
|
|
37
38
|
onCancelEditing?: (() => any) | undefined;
|
|
38
|
-
onCopyLink?: (() => any) | undefined;
|
|
39
39
|
onDebug?: (() => any) | undefined;
|
|
40
40
|
onTabChange?: ((tab: TopbarTab) => any) | undefined;
|
|
41
41
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|