@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.
Files changed (35) hide show
  1. package/README.md +1 -0
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/MeistrariChatEmbed.vue +3 -0
  4. package/dist/runtime/components/chat/conversation-creator-filter.d.vue.ts +3 -0
  5. package/dist/runtime/components/chat/conversation-creator-filter.vue +95 -0
  6. package/dist/runtime/components/chat/conversation-creator-filter.vue.d.ts +3 -0
  7. package/dist/runtime/components/chat/conversation-item.d.vue.ts +1 -0
  8. package/dist/runtime/components/chat/conversation-item.vue +44 -5
  9. package/dist/runtime/components/chat/conversation-item.vue.d.ts +1 -0
  10. package/dist/runtime/components/chat/conversation-list.d.vue.ts +2 -0
  11. package/dist/runtime/components/chat/conversation-list.vue +19 -6
  12. package/dist/runtime/components/chat/conversation-list.vue.d.ts +2 -0
  13. package/dist/runtime/components/chat/conversation-search.vue +21 -2
  14. package/dist/runtime/components/chat/mobile/shell/drawer.vue +1 -0
  15. package/dist/runtime/components/chat/mobile/shell/shell.d.vue.ts +2 -2
  16. package/dist/runtime/components/chat/mobile/shell/shell.vue.d.ts +2 -2
  17. package/dist/runtime/composables/useConversations.d.ts +2 -0
  18. package/dist/runtime/composables/useConversations.js +250 -110
  19. package/dist/runtime/composables/useEmbedConfig.d.ts +3 -0
  20. package/dist/runtime/composables/useEmbedConfig.js +4 -0
  21. package/dist/runtime/embed/components/ChatEmbed.vue +2 -0
  22. package/dist/runtime/embed/components/ChatEmbedInner.vue +10 -39
  23. package/dist/runtime/server/api/chat/conversations/metadata.get.d.ts +1 -0
  24. package/dist/runtime/server/api/chat/conversations/metadata.get.js +1 -0
  25. package/dist/runtime/server/api/conversations/index.get.js +5 -4
  26. package/dist/runtime/server/api/conversations/metadata.get.d.ts +2 -0
  27. package/dist/runtime/server/api/conversations/metadata.get.js +20 -0
  28. package/dist/runtime/server/utils/conversation-list.d.ts +4 -0
  29. package/dist/runtime/server/utils/conversation-list.js +28 -0
  30. package/dist/runtime/types/chat.d.ts +12 -0
  31. package/dist/runtime/types/chat.js +4 -0
  32. package/dist/runtime/types/embed.d.ts +3 -0
  33. package/dist/runtime/utils/conversation-creator-filter.d.ts +5 -0
  34. package/dist/runtime/utils/conversation-creator-filter.js +24 -0
  35. 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 workspaceScope = computed(() => resolveChatStateScope(
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
- const hasFetched = computed({
99
- get: () => fetchedBuckets.value[workspaceScope.value] ?? false,
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
- mutatedConversationIds.value = [.../* @__PURE__ */ new Set([...mutatedConversationIds.value, id])];
113
+ setBucketValue(mutatedIdBuckets, scope, [
114
+ .../* @__PURE__ */ new Set([...bucketValue(mutatedIdBuckets, scope, []), id])
115
+ ]);
111
116
  }
112
117
  }
113
- function commitConversations(nextConversations, mutatedId) {
114
- conversations.value = sortConversationsByUpdatedAt(nextConversations);
115
- markMutated(mutatedId);
118
+ function commitConversationsToScope(scope, nextConversations, mutatedId) {
119
+ setBucketValue(conversationBuckets, scope, sortConversationsByUpdatedAt(nextConversations));
120
+ markScopeMutated(scope, mutatedId);
116
121
  }
117
- function addDeletedConversationId(id) {
118
- deletedConversationIds.value = [.../* @__PURE__ */ new Set([...deletedConversationIds.value, id])];
119
- markMutated();
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 removeDeletedConversationId(id) {
122
- deletedConversationIds.value = deletedConversationIds.value.filter((deletedId) => deletedId !== id);
123
- markMutated();
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 pruneDeletedConversationIds(serverConversations) {
126
- const returnedIds = new Set(serverConversations.map((conversation) => conversation.id));
127
- deletedConversationIds.value = deletedConversationIds.value.filter((id) => returnedIds.has(id));
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 cloneAppliedSettingsSnapshot(snapshot) {
130
- if (!snapshot)
131
- return null;
132
- return JSON.parse(JSON.stringify(snapshot));
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
- if (loading.value || hasFetched.value && !options.force) {
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
- loading.value = true;
139
- error.value = null;
140
- const mutationVersionAtStart = mutationVersion.value;
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 visibleData = data.filter((conversation) => !deletedConversationIds.value.includes(conversation.id));
147
- if (mutationVersion.value === mutationVersionAtStart) {
148
- conversations.value = sortConversationsByUpdatedAt(visibleData);
149
- mutatedConversationIds.value = [];
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
- conversations.value = mergeFetchedConversations(
274
+ setBucketValue(conversationBuckets, requestScope, mergeFetchedConversations(
152
275
  visibleData,
153
- conversations.value,
154
- deletedConversationIds.value,
155
- mutatedConversationIds.value
156
- );
157
- mutatedConversationIds.value = [];
276
+ bucketValue(conversationBuckets, requestScope, []),
277
+ currentDeletedIds,
278
+ currentMutatedIds
279
+ ));
280
+ setBucketValue(mutatedIdBuckets, requestScope, []);
158
281
  }
159
- pruneDeletedConversationIds(data);
160
- hasFetched.value = true;
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
- error.value = message;
287
+ setBucketValue(errorBuckets, requestScope, message);
164
288
  Sentry.captureException(err);
165
289
  } finally {
166
- loading.value = false;
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
- commitConversations([conversation, ...conversations.value], conversation.id);
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 deletedConversation = conversations.value.find((c) => c.id === id);
191
- addDeletedConversationId(id);
192
- commitConversations(conversations.value.filter((c) => c.id !== id), id);
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
- removeDeletedConversationId(id);
207
- if (deletedConversation) {
208
- commitConversations([...conversations.value, deletedConversation], deletedConversation.id);
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
- const index = conversations.value.findIndex((c) => c.id === updated.id);
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 conversation = conversations.value.find((c) => c.id === id);
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
- const previousTitle = conversation.title;
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
- updateConversationInList({ ...conversation, title: previousTitle });
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
- commitConversations([duplicated, ...conversations.value], duplicated.id);
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((c) => c.title || c.id === activeConversationId.value)
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
- <div v-if="conversationsLoading && visibleConversations.length === 0" flex="~ col" gap-1px px-8px py-8px>
438
- <div
439
- v-for="row in conversationListSkeletonRows"
440
- :key="row.id"
441
- flex items-center h-32px px-8px rounded-8px
442
- >
443
- <TelaSkeleton rounded-6px bg-neutral-200 :style="{ width: row.width, height: '13px' }" />
444
- </div>
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";