@meistrari/chat-nuxt 1.9.0 → 1.11.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/README.md +1 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/MeistrariChatEmbed.vue +3 -0
- package/dist/runtime/components/chat/conversation-creator-filter.d.vue.ts +3 -0
- package/dist/runtime/components/chat/conversation-creator-filter.vue +95 -0
- package/dist/runtime/components/chat/conversation-creator-filter.vue.d.ts +3 -0
- package/dist/runtime/components/chat/conversation-item.d.vue.ts +1 -0
- package/dist/runtime/components/chat/conversation-item.vue +44 -5
- package/dist/runtime/components/chat/conversation-item.vue.d.ts +1 -0
- package/dist/runtime/components/chat/conversation-list.d.vue.ts +2 -0
- package/dist/runtime/components/chat/conversation-list.vue +19 -6
- package/dist/runtime/components/chat/conversation-list.vue.d.ts +2 -0
- package/dist/runtime/components/chat/conversation-search.vue +21 -2
- package/dist/runtime/components/chat/mobile/shell/drawer.vue +1 -0
- package/dist/runtime/components/chat/mobile/shell/shell.d.vue.ts +2 -2
- package/dist/runtime/components/chat/mobile/shell/shell.vue.d.ts +2 -2
- package/dist/runtime/composables/useConversations.d.ts +2 -0
- package/dist/runtime/composables/useConversations.js +250 -110
- package/dist/runtime/composables/useEmbedConfig.d.ts +3 -0
- package/dist/runtime/composables/useEmbedConfig.js +4 -0
- package/dist/runtime/embed/components/ChatEmbed.vue +2 -0
- package/dist/runtime/embed/components/ChatEmbedInner.vue +10 -39
- package/dist/runtime/server/api/chat/conversations/metadata.get.d.ts +1 -0
- package/dist/runtime/server/api/chat/conversations/metadata.get.js +1 -0
- package/dist/runtime/server/api/conversations/index.get.js +5 -4
- package/dist/runtime/server/api/conversations/metadata.get.d.ts +2 -0
- package/dist/runtime/server/api/conversations/metadata.get.js +20 -0
- package/dist/runtime/server/utils/conversation-list.d.ts +4 -0
- package/dist/runtime/server/utils/conversation-list.js +28 -0
- package/dist/runtime/types/chat.d.ts +12 -0
- package/dist/runtime/types/chat.js +4 -0
- package/dist/runtime/types/embed.d.ts +3 -0
- package/dist/runtime/utils/conversation-creator-filter.d.ts +5 -0
- package/dist/runtime/utils/conversation-creator-filter.js +24 -0
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as Sentry from "@sentry/nuxt";
|
|
2
2
|
import { useChatAuth } from "#chat-auth";
|
|
3
|
+
import { DEFAULT_MODEL } from "../types/chat.js";
|
|
3
4
|
import { resolveChatStateScope } from "../utils/tela-chat.js";
|
|
5
|
+
const CONVERSATION_CREATOR_FILTERS = ["all", "mine"];
|
|
4
6
|
function sortConversationsByUpdatedAt(conversations) {
|
|
5
7
|
return [...conversations].sort(
|
|
6
8
|
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
@@ -24,10 +26,36 @@ export function mergeFetchedConversations(fetchedConversations, localConversatio
|
|
|
24
26
|
}
|
|
25
27
|
return sortConversationsByUpdatedAt([...mergedById.values()]);
|
|
26
28
|
}
|
|
29
|
+
function bucketValue(bucket, key, fallback) {
|
|
30
|
+
return bucket.value[key] ?? fallback;
|
|
31
|
+
}
|
|
32
|
+
function setBucketValue(bucket, key, value) {
|
|
33
|
+
bucket.value = {
|
|
34
|
+
...bucket.value,
|
|
35
|
+
[key]: value
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function removeBucketValue(bucket, key) {
|
|
39
|
+
const nextValue = { ...bucket.value };
|
|
40
|
+
delete nextValue[key];
|
|
41
|
+
bucket.value = nextValue;
|
|
42
|
+
}
|
|
43
|
+
function hasBucketValue(bucket, key) {
|
|
44
|
+
return Object.prototype.hasOwnProperty.call(bucket.value, key);
|
|
45
|
+
}
|
|
46
|
+
function conversationBucketKeyForRuntime(runtimeScope, filter) {
|
|
47
|
+
return `${runtimeScope}:creator:${filter}`;
|
|
48
|
+
}
|
|
49
|
+
function createOptimisticConversationId() {
|
|
50
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
51
|
+
return `optimistic-${crypto.randomUUID()}`;
|
|
52
|
+
}
|
|
53
|
+
return `optimistic-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
54
|
+
}
|
|
27
55
|
export function useConversations() {
|
|
28
56
|
const chatApi = useChatApi();
|
|
29
57
|
const embedConfig = useEmbedConfig();
|
|
30
|
-
const { activeOrganization } = useChatAuth();
|
|
58
|
+
const { user: authUser, activeOrganization } = useChatAuth();
|
|
31
59
|
const conversationBuckets = useState("conversations", () => ({}));
|
|
32
60
|
const loadingBuckets = useState("conversations-loading", () => ({}));
|
|
33
61
|
const errorBuckets = useState("conversations-error", () => ({}));
|
|
@@ -35,12 +63,23 @@ export function useConversations() {
|
|
|
35
63
|
const mutationVersionBuckets = useState("conversations-mutation-version", () => ({}));
|
|
36
64
|
const deletedIdBuckets = useState("conversations-deleted-ids", () => ({}));
|
|
37
65
|
const mutatedIdBuckets = useState("conversations-mutated-ids", () => ({}));
|
|
66
|
+
const metadataBuckets = useState("conversations-metadata", () => ({}));
|
|
67
|
+
const metadataLoadingBuckets = useState("conversations-metadata-loading", () => ({}));
|
|
68
|
+
const metadataFetchedBuckets = useState("conversations-metadata-fetched", () => ({}));
|
|
69
|
+
const conversationCreatorFilter = useState(
|
|
70
|
+
"conversation-creator-filter",
|
|
71
|
+
() => embedConfig?.defaultConversationCreatorFilter?.value ?? "all"
|
|
72
|
+
);
|
|
38
73
|
const isTelaAgentMode = computed(() => !!embedConfig?.telaAgentId.value);
|
|
39
|
-
const
|
|
74
|
+
const runtimeScope = computed(() => resolveChatStateScope(
|
|
40
75
|
embedConfig?.workspaceId.value || activeOrganization.value?.id,
|
|
41
76
|
embedConfig?.telaAgentId.value,
|
|
42
77
|
embedConfig?.conversationScope?.value
|
|
43
78
|
));
|
|
79
|
+
const workspaceScope = computed(() => conversationBucketKeyForRuntime(runtimeScope.value, conversationCreatorFilter.value));
|
|
80
|
+
const conversationCreatorFilterCounts = computed(
|
|
81
|
+
() => metadataBuckets.value[runtimeScope.value]?.counts ?? { all: 0, mine: 0 }
|
|
82
|
+
);
|
|
44
83
|
const conversations = computed({
|
|
45
84
|
get: () => conversationBuckets.value[workspaceScope.value] ?? [],
|
|
46
85
|
set: (value) => {
|
|
@@ -50,33 +89,6 @@ export function useConversations() {
|
|
|
50
89
|
};
|
|
51
90
|
}
|
|
52
91
|
});
|
|
53
|
-
const mutationVersion = computed({
|
|
54
|
-
get: () => mutationVersionBuckets.value[workspaceScope.value] ?? 0,
|
|
55
|
-
set: (value) => {
|
|
56
|
-
mutationVersionBuckets.value = {
|
|
57
|
-
...mutationVersionBuckets.value,
|
|
58
|
-
[workspaceScope.value]: value
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
const deletedConversationIds = computed({
|
|
63
|
-
get: () => deletedIdBuckets.value[workspaceScope.value] ?? [],
|
|
64
|
-
set: (value) => {
|
|
65
|
-
deletedIdBuckets.value = {
|
|
66
|
-
...deletedIdBuckets.value,
|
|
67
|
-
[workspaceScope.value]: value
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
const mutatedConversationIds = computed({
|
|
72
|
-
get: () => mutatedIdBuckets.value[workspaceScope.value] ?? [],
|
|
73
|
-
set: (value) => {
|
|
74
|
-
mutatedIdBuckets.value = {
|
|
75
|
-
...mutatedIdBuckets.value,
|
|
76
|
-
[workspaceScope.value]: value
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
92
|
const loading = computed({
|
|
81
93
|
get: () => loadingBuckets.value[workspaceScope.value] ?? false,
|
|
82
94
|
set: (value) => {
|
|
@@ -95,79 +107,211 @@ export function useConversations() {
|
|
|
95
107
|
};
|
|
96
108
|
}
|
|
97
109
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
set: (value) => {
|
|
101
|
-
fetchedBuckets.value = {
|
|
102
|
-
...fetchedBuckets.value,
|
|
103
|
-
[workspaceScope.value]: value
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
function markMutated(id) {
|
|
108
|
-
mutationVersion.value = mutationVersion.value + 1;
|
|
110
|
+
function markScopeMutated(scope, id) {
|
|
111
|
+
setBucketValue(mutationVersionBuckets, scope, bucketValue(mutationVersionBuckets, scope, 0) + 1);
|
|
109
112
|
if (id) {
|
|
110
|
-
|
|
113
|
+
setBucketValue(mutatedIdBuckets, scope, [
|
|
114
|
+
.../* @__PURE__ */ new Set([...bucketValue(mutatedIdBuckets, scope, []), id])
|
|
115
|
+
]);
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
function commitConversationsToScope(scope, nextConversations, mutatedId) {
|
|
119
|
+
setBucketValue(conversationBuckets, scope, sortConversationsByUpdatedAt(nextConversations));
|
|
120
|
+
markScopeMutated(scope, mutatedId);
|
|
116
121
|
}
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
function activeAndLoadedRuntimeConversationScopes(scope) {
|
|
123
|
+
const activeScope = runtimeScope.value === scope ? [workspaceScope.value] : [];
|
|
124
|
+
const loadedScopes = CONVERSATION_CREATOR_FILTERS.map((filter) => conversationBucketKeyForRuntime(scope, filter)).filter((key) => hasBucketValue(conversationBuckets, key));
|
|
125
|
+
return [.../* @__PURE__ */ new Set([...activeScope, ...loadedScopes])];
|
|
120
126
|
}
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
function findConversationInLoadedBuckets(id, runtimeScopeKey) {
|
|
128
|
+
for (const scope of activeAndLoadedRuntimeConversationScopes(runtimeScopeKey)) {
|
|
129
|
+
const found = bucketValue(conversationBuckets, scope, []).find((conversation) => conversation.id === id);
|
|
130
|
+
if (found) {
|
|
131
|
+
return found;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
124
135
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
function snapshotConversationBuckets(scopes) {
|
|
137
|
+
return Object.fromEntries(scopes.map((scope) => [
|
|
138
|
+
scope,
|
|
139
|
+
hasBucketValue(conversationBuckets, scope) ? [...bucketValue(conversationBuckets, scope, [])] : void 0
|
|
140
|
+
]));
|
|
128
141
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
142
|
+
function restoreConversationBuckets(snapshot, mutatedId) {
|
|
143
|
+
for (const [scope, previousConversations] of Object.entries(snapshot)) {
|
|
144
|
+
if (previousConversations) {
|
|
145
|
+
commitConversationsToScope(scope, previousConversations, mutatedId);
|
|
146
|
+
} else {
|
|
147
|
+
removeBucketValue(conversationBuckets, scope);
|
|
148
|
+
markScopeMutated(scope, mutatedId);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function upsertConversationInLoadedBuckets(conversation, runtimeScopeKey, replacedId) {
|
|
153
|
+
for (const scope of activeAndLoadedRuntimeConversationScopes(runtimeScopeKey)) {
|
|
154
|
+
const filter = CONVERSATION_CREATOR_FILTERS.find((item) => scope === conversationBucketKeyForRuntime(runtimeScopeKey, item));
|
|
155
|
+
if (!filter || filter === "mine" && conversation.userId !== authUser.value?.id)
|
|
156
|
+
continue;
|
|
157
|
+
const currentConversations = bucketValue(conversationBuckets, scope, []);
|
|
158
|
+
commitConversationsToScope(
|
|
159
|
+
scope,
|
|
160
|
+
[
|
|
161
|
+
conversation,
|
|
162
|
+
...currentConversations.filter((item) => item.id !== conversation.id && item.id !== replacedId)
|
|
163
|
+
],
|
|
164
|
+
conversation.id
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function updateConversationInLoadedBuckets(updated, runtimeScopeKey = runtimeScope.value) {
|
|
169
|
+
for (const scope of activeAndLoadedRuntimeConversationScopes(runtimeScopeKey)) {
|
|
170
|
+
const currentConversations = bucketValue(conversationBuckets, scope, []);
|
|
171
|
+
const index = currentConversations.findIndex((conversation) => conversation.id === updated.id);
|
|
172
|
+
if (index === -1)
|
|
173
|
+
continue;
|
|
174
|
+
const nextConversations = [...currentConversations];
|
|
175
|
+
nextConversations[index] = updated;
|
|
176
|
+
commitConversationsToScope(scope, nextConversations, updated.id);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function removeConversationFromLoadedBuckets(id, runtimeScopeKey) {
|
|
180
|
+
for (const scope of activeAndLoadedRuntimeConversationScopes(runtimeScopeKey)) {
|
|
181
|
+
const currentConversations = bucketValue(conversationBuckets, scope, []);
|
|
182
|
+
if (currentConversations.some((conversation) => conversation.id === id)) {
|
|
183
|
+
commitConversationsToScope(
|
|
184
|
+
scope,
|
|
185
|
+
currentConversations.filter((conversation) => conversation.id !== id),
|
|
186
|
+
id
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function addDeletedConversationIdToScopes(id, scopes) {
|
|
192
|
+
for (const scope of scopes) {
|
|
193
|
+
setBucketValue(deletedIdBuckets, scope, [
|
|
194
|
+
.../* @__PURE__ */ new Set([...bucketValue(deletedIdBuckets, scope, []), id])
|
|
195
|
+
]);
|
|
196
|
+
markScopeMutated(scope);
|
|
197
|
+
}
|
|
133
198
|
}
|
|
199
|
+
function removeDeletedConversationIdFromScopes(id, scopes) {
|
|
200
|
+
for (const scope of scopes) {
|
|
201
|
+
setBucketValue(
|
|
202
|
+
deletedIdBuckets,
|
|
203
|
+
scope,
|
|
204
|
+
bucketValue(deletedIdBuckets, scope, []).filter((deletedId) => deletedId !== id)
|
|
205
|
+
);
|
|
206
|
+
markScopeMutated(scope);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function snapshotConversationMetadata(scope) {
|
|
210
|
+
const metadata = metadataBuckets.value[scope];
|
|
211
|
+
return metadata ? { counts: { ...metadata.counts } } : void 0;
|
|
212
|
+
}
|
|
213
|
+
function restoreConversationMetadata(scope, metadata) {
|
|
214
|
+
if (metadata) {
|
|
215
|
+
setBucketValue(metadataBuckets, scope, metadata);
|
|
216
|
+
} else {
|
|
217
|
+
removeBucketValue(metadataBuckets, scope);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function updateConversationCounts(scope, conversation, delta) {
|
|
221
|
+
const counts = metadataBuckets.value[scope]?.counts ?? { all: 0, mine: 0 };
|
|
222
|
+
setBucketValue(metadataBuckets, scope, {
|
|
223
|
+
counts: {
|
|
224
|
+
all: Math.max(0, counts.all + delta),
|
|
225
|
+
mine: Math.max(0, counts.mine + (conversation.userId === authUser.value?.id ? delta : 0))
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const fetchConversationMetadata = async (options = {}) => {
|
|
230
|
+
const requestScope = runtimeScope.value;
|
|
231
|
+
if (bucketValue(metadataLoadingBuckets, requestScope, false) || bucketValue(metadataFetchedBuckets, requestScope, false) && !options.force) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
setBucketValue(metadataLoadingBuckets, requestScope, true);
|
|
235
|
+
try {
|
|
236
|
+
const metadata = await $fetch(
|
|
237
|
+
chatApi.path("/conversations/metadata"),
|
|
238
|
+
chatApi.withChatHeaders()
|
|
239
|
+
);
|
|
240
|
+
setBucketValue(metadataBuckets, requestScope, metadata);
|
|
241
|
+
setBucketValue(metadataFetchedBuckets, requestScope, true);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
const message = err instanceof Error ? err.message : "Erro ao carregar metadados das conversas";
|
|
244
|
+
setBucketValue(errorBuckets, workspaceScope.value, message);
|
|
245
|
+
Sentry.captureException(err);
|
|
246
|
+
} finally {
|
|
247
|
+
setBucketValue(metadataLoadingBuckets, requestScope, false);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
134
250
|
const fetchConversations = async (options = {}) => {
|
|
135
|
-
|
|
251
|
+
const requestScope = workspaceScope.value;
|
|
252
|
+
const requestCreatorFilter = conversationCreatorFilter.value;
|
|
253
|
+
const metadataPromise = fetchConversationMetadata({ force: options.force });
|
|
254
|
+
if (bucketValue(loadingBuckets, requestScope, false) || bucketValue(fetchedBuckets, requestScope, false) && !options.force) {
|
|
255
|
+
await metadataPromise;
|
|
136
256
|
return;
|
|
137
257
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const mutationVersionAtStart =
|
|
258
|
+
setBucketValue(loadingBuckets, requestScope, true);
|
|
259
|
+
setBucketValue(errorBuckets, requestScope, null);
|
|
260
|
+
const mutationVersionAtStart = bucketValue(mutationVersionBuckets, requestScope, 0);
|
|
141
261
|
try {
|
|
142
262
|
const data = await $fetch(
|
|
143
263
|
chatApi.path("/conversations"),
|
|
144
|
-
chatApi.withChatHeaders()
|
|
264
|
+
chatApi.withChatHeaders(requestCreatorFilter === "mine" ? { query: { creator: "me" } } : void 0)
|
|
145
265
|
);
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
266
|
+
const currentDeletedIds = bucketValue(deletedIdBuckets, requestScope, []);
|
|
267
|
+
const currentMutatedIds = bucketValue(mutatedIdBuckets, requestScope, []);
|
|
268
|
+
const visibleData = data.filter((conversation) => !currentDeletedIds.includes(conversation.id));
|
|
269
|
+
await metadataPromise;
|
|
270
|
+
if (bucketValue(mutationVersionBuckets, requestScope, 0) === mutationVersionAtStart) {
|
|
271
|
+
setBucketValue(conversationBuckets, requestScope, sortConversationsByUpdatedAt(visibleData));
|
|
272
|
+
setBucketValue(mutatedIdBuckets, requestScope, []);
|
|
150
273
|
} else {
|
|
151
|
-
|
|
274
|
+
setBucketValue(conversationBuckets, requestScope, mergeFetchedConversations(
|
|
152
275
|
visibleData,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
);
|
|
157
|
-
|
|
276
|
+
bucketValue(conversationBuckets, requestScope, []),
|
|
277
|
+
currentDeletedIds,
|
|
278
|
+
currentMutatedIds
|
|
279
|
+
));
|
|
280
|
+
setBucketValue(mutatedIdBuckets, requestScope, []);
|
|
158
281
|
}
|
|
159
|
-
|
|
160
|
-
|
|
282
|
+
const returnedIds = new Set(data.map((conversation) => conversation.id));
|
|
283
|
+
setBucketValue(deletedIdBuckets, requestScope, currentDeletedIds.filter((id) => returnedIds.has(id)));
|
|
284
|
+
setBucketValue(fetchedBuckets, requestScope, true);
|
|
161
285
|
} catch (err) {
|
|
162
286
|
const message = err instanceof Error ? err.message : "Erro ao carregar conversas";
|
|
163
|
-
|
|
287
|
+
setBucketValue(errorBuckets, requestScope, message);
|
|
164
288
|
Sentry.captureException(err);
|
|
165
289
|
} finally {
|
|
166
|
-
|
|
290
|
+
setBucketValue(loadingBuckets, requestScope, false);
|
|
167
291
|
}
|
|
168
292
|
};
|
|
169
293
|
const createConversation = async (options) => {
|
|
170
294
|
error.value = null;
|
|
295
|
+
const requestRuntimeScope = runtimeScope.value;
|
|
296
|
+
const metadataSnapshot = snapshotConversationMetadata(requestRuntimeScope);
|
|
297
|
+
const now = /* @__PURE__ */ new Date();
|
|
298
|
+
const optimisticConversation = {
|
|
299
|
+
id: createOptimisticConversationId(),
|
|
300
|
+
workspaceId: embedConfig?.workspaceId.value || activeOrganization.value?.id || "",
|
|
301
|
+
userId: authUser.value?.id ?? "",
|
|
302
|
+
title: "Nova conversa",
|
|
303
|
+
threadSessionId: null,
|
|
304
|
+
model: isTelaAgentMode.value ? null : options?.model ?? DEFAULT_MODEL,
|
|
305
|
+
appliedSettingsAt: null,
|
|
306
|
+
appliedSettingsSnapshot: null,
|
|
307
|
+
telaAgentId: embedConfig?.telaAgentId.value ?? null,
|
|
308
|
+
createdBy: authUser.value?.email ?? null,
|
|
309
|
+
conversationScope: embedConfig?.conversationScope?.value ?? null,
|
|
310
|
+
createdAt: now,
|
|
311
|
+
updatedAt: now
|
|
312
|
+
};
|
|
313
|
+
upsertConversationInLoadedBuckets(optimisticConversation, requestRuntimeScope);
|
|
314
|
+
updateConversationCounts(requestRuntimeScope, optimisticConversation, 1);
|
|
171
315
|
try {
|
|
172
316
|
const conversation = await $fetch(
|
|
173
317
|
chatApi.path("/conversations"),
|
|
@@ -176,9 +320,11 @@ export function useConversations() {
|
|
|
176
320
|
body: !isTelaAgentMode.value && options?.model ? { model: options.model } : void 0
|
|
177
321
|
})
|
|
178
322
|
);
|
|
179
|
-
|
|
323
|
+
upsertConversationInLoadedBuckets(conversation, requestRuntimeScope, optimisticConversation.id);
|
|
180
324
|
return conversation;
|
|
181
325
|
} catch (err) {
|
|
326
|
+
removeConversationFromLoadedBuckets(optimisticConversation.id, requestRuntimeScope);
|
|
327
|
+
restoreConversationMetadata(requestRuntimeScope, metadataSnapshot);
|
|
182
328
|
const message = err instanceof Error ? err.message : "Erro ao criar conversa";
|
|
183
329
|
error.value = message;
|
|
184
330
|
return null;
|
|
@@ -187,9 +333,16 @@ export function useConversations() {
|
|
|
187
333
|
const deleteConversation = async (id) => {
|
|
188
334
|
error.value = null;
|
|
189
335
|
const statusToast = useStatusToast();
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
336
|
+
const requestRuntimeScope = runtimeScope.value;
|
|
337
|
+
const affectedScopes = activeAndLoadedRuntimeConversationScopes(requestRuntimeScope);
|
|
338
|
+
const conversationSnapshot = snapshotConversationBuckets(affectedScopes);
|
|
339
|
+
const metadataSnapshot = snapshotConversationMetadata(requestRuntimeScope);
|
|
340
|
+
const deletedConversation = findConversationInLoadedBuckets(id, requestRuntimeScope);
|
|
341
|
+
addDeletedConversationIdToScopes(id, affectedScopes);
|
|
342
|
+
removeConversationFromLoadedBuckets(id, requestRuntimeScope);
|
|
343
|
+
if (deletedConversation) {
|
|
344
|
+
updateConversationCounts(requestRuntimeScope, deletedConversation, -1);
|
|
345
|
+
}
|
|
193
346
|
try {
|
|
194
347
|
await $fetch(
|
|
195
348
|
chatApi.path(`/conversations/${id}`),
|
|
@@ -203,10 +356,9 @@ export function useConversations() {
|
|
|
203
356
|
});
|
|
204
357
|
return true;
|
|
205
358
|
} catch (err) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
359
|
+
removeDeletedConversationIdFromScopes(id, affectedScopes);
|
|
360
|
+
restoreConversationBuckets(conversationSnapshot, id);
|
|
361
|
+
restoreConversationMetadata(requestRuntimeScope, metadataSnapshot);
|
|
210
362
|
const message = err instanceof Error ? err.message : "Erro ao deletar conversa";
|
|
211
363
|
error.value = message;
|
|
212
364
|
statusToast.update({
|
|
@@ -217,45 +369,29 @@ export function useConversations() {
|
|
|
217
369
|
}
|
|
218
370
|
};
|
|
219
371
|
const updateConversationInList = (updated) => {
|
|
220
|
-
|
|
221
|
-
if (index !== -1) {
|
|
222
|
-
const nextConversations = [...conversations.value];
|
|
223
|
-
nextConversations[index] = {
|
|
224
|
-
id: updated.id,
|
|
225
|
-
workspaceId: updated.workspaceId,
|
|
226
|
-
userId: updated.userId,
|
|
227
|
-
title: updated.title,
|
|
228
|
-
threadSessionId: updated.threadSessionId,
|
|
229
|
-
model: updated.model,
|
|
230
|
-
appliedSettingsAt: updated.appliedSettingsAt,
|
|
231
|
-
appliedSettingsSnapshot: cloneAppliedSettingsSnapshot(updated.appliedSettingsSnapshot),
|
|
232
|
-
telaAgentId: updated.telaAgentId,
|
|
233
|
-
createdBy: updated.createdBy,
|
|
234
|
-
conversationScope: updated.conversationScope,
|
|
235
|
-
createdAt: updated.createdAt,
|
|
236
|
-
updatedAt: updated.updatedAt
|
|
237
|
-
};
|
|
238
|
-
commitConversations(nextConversations, updated.id);
|
|
239
|
-
}
|
|
372
|
+
updateConversationInLoadedBuckets(updated);
|
|
240
373
|
};
|
|
241
374
|
const renameConversation = async (id, title) => {
|
|
242
375
|
error.value = null;
|
|
243
|
-
const
|
|
376
|
+
const requestRuntimeScope = runtimeScope.value;
|
|
377
|
+
const affectedScopes = activeAndLoadedRuntimeConversationScopes(requestRuntimeScope);
|
|
378
|
+
const conversationSnapshot = snapshotConversationBuckets(affectedScopes);
|
|
379
|
+
const conversation = findConversationInLoadedBuckets(id, requestRuntimeScope);
|
|
244
380
|
if (!conversation)
|
|
245
381
|
return false;
|
|
246
|
-
|
|
247
|
-
updateConversationInList({ ...conversation, title, updatedAt: /* @__PURE__ */ new Date() });
|
|
382
|
+
updateConversationInLoadedBuckets({ ...conversation, title, updatedAt: /* @__PURE__ */ new Date() }, requestRuntimeScope);
|
|
248
383
|
try {
|
|
249
|
-
await $fetch(
|
|
384
|
+
const updated = await $fetch(
|
|
250
385
|
chatApi.path(`/conversations/${id}`),
|
|
251
386
|
chatApi.withChatHeaders({
|
|
252
387
|
method: "PATCH",
|
|
253
388
|
body: { title }
|
|
254
389
|
})
|
|
255
390
|
);
|
|
391
|
+
updateConversationInLoadedBuckets(updated, requestRuntimeScope);
|
|
256
392
|
return true;
|
|
257
393
|
} catch (err) {
|
|
258
|
-
|
|
394
|
+
restoreConversationBuckets(conversationSnapshot, id);
|
|
259
395
|
const message = err instanceof Error ? err.message : "Erro ao renomear conversa";
|
|
260
396
|
error.value = message;
|
|
261
397
|
Sentry.captureException(err);
|
|
@@ -265,6 +401,7 @@ export function useConversations() {
|
|
|
265
401
|
const duplicateConversation = async (id) => {
|
|
266
402
|
error.value = null;
|
|
267
403
|
const statusToast = useStatusToast();
|
|
404
|
+
const requestRuntimeScope = runtimeScope.value;
|
|
268
405
|
try {
|
|
269
406
|
const duplicated = await $fetch(
|
|
270
407
|
chatApi.path(`/conversations/${id}/duplicate`),
|
|
@@ -272,7 +409,8 @@ export function useConversations() {
|
|
|
272
409
|
method: "POST"
|
|
273
410
|
})
|
|
274
411
|
);
|
|
275
|
-
|
|
412
|
+
upsertConversationInLoadedBuckets(duplicated, requestRuntimeScope);
|
|
413
|
+
updateConversationCounts(requestRuntimeScope, duplicated, 1);
|
|
276
414
|
statusToast.update({
|
|
277
415
|
text: "Conversa duplicada",
|
|
278
416
|
icon: "i-ph-check"
|
|
@@ -297,6 +435,8 @@ export function useConversations() {
|
|
|
297
435
|
deleteConversation,
|
|
298
436
|
renameConversation,
|
|
299
437
|
duplicateConversation,
|
|
438
|
+
conversationCreatorFilter,
|
|
439
|
+
conversationCreatorFilterCounts,
|
|
300
440
|
updateConversationInList
|
|
301
441
|
};
|
|
302
442
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Component, DeepReadonly, MaybeRefOrGetter, Ref } from 'vue';
|
|
2
|
+
import type { ConversationCreatorFilter } from '../types/chat.js';
|
|
2
3
|
import type { TelaAgentExecutionInput } from '../types/tela-agent.js';
|
|
3
4
|
import type { WorkspaceSettings } from '../types/workspace-settings-data.js';
|
|
4
5
|
import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
|
|
@@ -7,6 +8,7 @@ export type EmbedConfig = {
|
|
|
7
8
|
workspaceId: Ref<string>;
|
|
8
9
|
telaAgentId: Ref<string | null>;
|
|
9
10
|
conversationScope: Ref<string | null>;
|
|
11
|
+
defaultConversationCreatorFilter: Ref<ConversationCreatorFilter>;
|
|
10
12
|
telaAgentInputs: Ref<ConfiguredTelaAgentInputs>;
|
|
11
13
|
workspaceSettings: Ref<DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null | undefined>;
|
|
12
14
|
hideSidebar: Ref<boolean>;
|
|
@@ -20,6 +22,7 @@ type EmbedConfigInput = {
|
|
|
20
22
|
workspaceId: MaybeRefOrGetter<string>;
|
|
21
23
|
telaAgentId?: MaybeRefOrGetter<string | null | undefined>;
|
|
22
24
|
conversationScope?: MaybeRefOrGetter<string | null | undefined>;
|
|
25
|
+
defaultConversationCreatorFilter?: MaybeRefOrGetter<ConversationCreatorFilter | null | undefined>;
|
|
23
26
|
telaAgentInputs?: MaybeRefOrGetter<ConfiguredTelaAgentInputs>;
|
|
24
27
|
workspaceSettings?: MaybeRefOrGetter<DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null | undefined>;
|
|
25
28
|
hideSidebar?: MaybeRefOrGetter<boolean | undefined>;
|
|
@@ -11,6 +11,7 @@ function useGlobalEmbedConfigState() {
|
|
|
11
11
|
workspaceId: useState("chat-embed-config-workspace-id", () => ""),
|
|
12
12
|
telaAgentId: useState("chat-embed-config-tela-agent-id", () => null),
|
|
13
13
|
conversationScope: useState("chat-embed-config-conversation-scope", () => null),
|
|
14
|
+
defaultConversationCreatorFilter: useState("chat-embed-config-default-conversation-creator-filter", () => void 0),
|
|
14
15
|
telaAgentInputs: useState("chat-embed-config-tela-agent-inputs", () => void 0),
|
|
15
16
|
workspaceSettings: useState("chat-embed-config-workspace-settings", () => void 0),
|
|
16
17
|
hideSidebar: useState("chat-embed-config-hide-sidebar", () => void 0),
|
|
@@ -26,6 +27,7 @@ export function provideEmbedConfig(input, options = {}) {
|
|
|
26
27
|
workspaceId: computed(() => toValue(input.workspaceId)),
|
|
27
28
|
telaAgentId: computed(() => toValue(input.telaAgentId) ?? null),
|
|
28
29
|
conversationScope: computed(() => normalizeConversationScope(toValue(input.conversationScope))),
|
|
30
|
+
defaultConversationCreatorFilter: computed(() => toValue(input.defaultConversationCreatorFilter) ?? "all"),
|
|
29
31
|
telaAgentInputs: computed(() => toValue(input.telaAgentInputs)),
|
|
30
32
|
workspaceSettings: computed(() => toValue(input.workspaceSettings)),
|
|
31
33
|
hideSidebar: computed(() => toValue(input.hideSidebar) ?? true),
|
|
@@ -42,6 +44,7 @@ export function provideEmbedConfig(input, options = {}) {
|
|
|
42
44
|
globalConfig.workspaceId.value = config.workspaceId.value;
|
|
43
45
|
globalConfig.telaAgentId.value = config.telaAgentId.value;
|
|
44
46
|
globalConfig.conversationScope.value = config.conversationScope.value;
|
|
47
|
+
globalConfig.defaultConversationCreatorFilter.value = config.defaultConversationCreatorFilter.value;
|
|
45
48
|
globalConfig.telaAgentInputs.value = config.telaAgentInputs.value;
|
|
46
49
|
globalConfig.workspaceSettings.value = config.workspaceSettings.value;
|
|
47
50
|
globalConfig.hideSidebar.value = config.hideSidebar.value;
|
|
@@ -65,6 +68,7 @@ export function useEmbedConfig() {
|
|
|
65
68
|
workspaceId: computed(() => globalConfig.workspaceId.value),
|
|
66
69
|
telaAgentId: computed(() => globalConfig.telaAgentId.value),
|
|
67
70
|
conversationScope: computed(() => globalConfig.conversationScope.value),
|
|
71
|
+
defaultConversationCreatorFilter: computed(() => globalConfig.defaultConversationCreatorFilter.value ?? "all"),
|
|
68
72
|
telaAgentInputs: computed(() => globalConfig.telaAgentInputs.value),
|
|
69
73
|
workspaceSettings: computed(() => globalConfig.workspaceSettings.value),
|
|
70
74
|
hideSidebar: computed(() => globalConfig.hideSidebar.value ?? true),
|
|
@@ -8,6 +8,7 @@ const props = defineProps({
|
|
|
8
8
|
conversationId: { type: [String, null], required: false },
|
|
9
9
|
initialConversationId: { type: [String, null], required: false },
|
|
10
10
|
conversationScope: { type: [String, null], required: false },
|
|
11
|
+
defaultConversationCreatorFilter: { type: String, required: false },
|
|
11
12
|
hideSidebar: { type: Boolean, required: false },
|
|
12
13
|
hideSettings: { type: Boolean, required: false },
|
|
13
14
|
loadingMessages: { type: [Array, null], required: false },
|
|
@@ -32,6 +33,7 @@ provideEmbedConfig({
|
|
|
32
33
|
workspaceId,
|
|
33
34
|
telaAgentId: normalizedTelaAgentId,
|
|
34
35
|
conversationScope: normalizedConversationScope,
|
|
36
|
+
defaultConversationCreatorFilter: computed(() => props.defaultConversationCreatorFilter),
|
|
35
37
|
telaAgentInputs: computed(() => props.telaAgentInputs),
|
|
36
38
|
workspaceSettings: computed(() => props.workspaceSettings),
|
|
37
39
|
hideSidebar: computed(() => props.hideSidebar),
|
|
@@ -3,10 +3,8 @@ import { useChatAuth } from "#chat-auth";
|
|
|
3
3
|
import { useBreakpoints } from "@vueuse/core";
|
|
4
4
|
import { exportConversationToMarkdown } from "../../utils/export-conversation";
|
|
5
5
|
import { CHAT_MOBILE_BREAKPOINT } from "../../utils/breakpoints";
|
|
6
|
-
import { filterConversationsByTitle } from "../../utils/conversation-search";
|
|
7
6
|
import ChatMobileFilePreview from "../../components/chat/mobile/files/file-preview.vue";
|
|
8
7
|
import ChatMobileShell from "../../components/chat/mobile/shell/shell.vue";
|
|
9
|
-
import { conversationListSkeletonRows } from "../../utils/conversation-list-skeleton";
|
|
10
8
|
import {
|
|
11
9
|
normalizeConversationId,
|
|
12
10
|
resolveChatFeatures,
|
|
@@ -25,7 +23,7 @@ const chatApi = useChatApi();
|
|
|
25
23
|
const embedConfig = useEmbedConfig();
|
|
26
24
|
const chatAction = useChatAction();
|
|
27
25
|
const { user: authUser, activeOrganization, logout } = useChatAuth();
|
|
28
|
-
const { getMember } = useWorkspaceMembers();
|
|
26
|
+
const { getMember, getMemberImage } = useWorkspaceMembers();
|
|
29
27
|
const statusToast = useStatusToast();
|
|
30
28
|
const { conversations, loading: conversationsLoading, fetchConversations, createConversation, deleteConversation, renameConversation, duplicateConversation, updateConversationInList } = useConversations();
|
|
31
29
|
const { settings: workspaceSettingsState, fetchSettings } = useWorkspaceSettings();
|
|
@@ -52,7 +50,6 @@ const resolvedFeatures = computed(() => resolveChatFeatures(props.features));
|
|
|
52
50
|
const dropZoneRef = ref(null);
|
|
53
51
|
const activeTab = ref("chat");
|
|
54
52
|
const messageInputRef = ref(null);
|
|
55
|
-
const conversationSearch = ref("");
|
|
56
53
|
const breakpoints = useBreakpoints({ mobile: CHAT_MOBILE_BREAKPOINT });
|
|
57
54
|
const isMobile = breakpoints.smaller("mobile");
|
|
58
55
|
const drawerOpen = ref(false);
|
|
@@ -106,15 +103,8 @@ const mobilePreviewFile = computed(() => {
|
|
|
106
103
|
const visibleTelaAgentInputSchema = computed(() => !hasMessages.value ? telaAgentInputSchema.value : null);
|
|
107
104
|
const visibleTelaAgentInputSchemaLoading = computed(() => !hasMessages.value && shouldLoadTelaAgentMetadata.value && telaAgentMetadataLoading.value);
|
|
108
105
|
const visibleConversations = computed(
|
|
109
|
-
() => conversations.value.filter((
|
|
106
|
+
() => conversations.value.filter((conversation) => conversation.title || conversation.id === activeConversationId.value)
|
|
110
107
|
);
|
|
111
|
-
const searchFilteredConversations = computed(
|
|
112
|
-
() => filterConversationsByTitle(visibleConversations.value, conversationSearch.value, {
|
|
113
|
-
fallbackTitle: "Nova conversa",
|
|
114
|
-
shouldInclude: (conversation) => !conversation.title && conversation.id === activeConversationId.value
|
|
115
|
-
})
|
|
116
|
-
);
|
|
117
|
-
const hasConversationSearchQuery = computed(() => conversationSearch.value.trim().length > 0);
|
|
118
108
|
function syncConversationId(nextConversationId) {
|
|
119
109
|
activeConversationId.value = normalizeConversationId(nextConversationId);
|
|
120
110
|
}
|
|
@@ -430,36 +420,17 @@ async function handleDelete() {
|
|
|
430
420
|
Nova conversa
|
|
431
421
|
</span>
|
|
432
422
|
</button>
|
|
433
|
-
<ChatConversationSearch v-model="conversationSearch" />
|
|
434
423
|
</div>
|
|
435
424
|
|
|
436
425
|
<div flex-1 overflow-y-auto px-4px>
|
|
437
|
-
<
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
</div>
|
|
446
|
-
<div v-else-if="searchFilteredConversations.length === 0" p-16px text-center>
|
|
447
|
-
<p text-14px text-neutral-400>
|
|
448
|
-
{{ hasConversationSearchQuery ? "Nenhuma conversa encontrada" : "Nenhuma conversa ainda" }}
|
|
449
|
-
</p>
|
|
450
|
-
</div>
|
|
451
|
-
<div v-else flex="~ col" gap-1px px-8px py-8px>
|
|
452
|
-
<button
|
|
453
|
-
v-for="conv in searchFilteredConversations"
|
|
454
|
-
:key="conv.id"
|
|
455
|
-
flex items-center gap-8px h-32px px-8px rounded-8px
|
|
456
|
-
w-full text-left truncate transition-colors cursor-pointer
|
|
457
|
-
:class="conv.id === activeConversationId ? 'bg-neutral-200' : 'hover:bg-neutral-100'"
|
|
458
|
-
@click="openConversation(conv.id)"
|
|
459
|
-
>
|
|
460
|
-
<span text-13px text-neutral-700 truncate>{{ conv.title || "Nova conversa" }}</span>
|
|
461
|
-
</button>
|
|
462
|
-
</div>
|
|
426
|
+
<ChatConversationList
|
|
427
|
+
:conversations="visibleConversations"
|
|
428
|
+
:current-id="activeConversationId ?? void 0"
|
|
429
|
+
:get-member-image="getMemberImage"
|
|
430
|
+
:select-conversation="openConversation"
|
|
431
|
+
:reset-conversation="handleNewConversation"
|
|
432
|
+
:loading="conversationsLoading"
|
|
433
|
+
/>
|
|
463
434
|
</div>
|
|
464
435
|
</div>
|
|
465
436
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../../conversations/metadata.get.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "../../conversations/metadata.get.js";
|