@meistrari/chat-nuxt 1.9.0 → 1.10.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 (39) 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/header.d.vue.ts +4 -4
  16. package/dist/runtime/components/chat/mobile/shell/header.vue.d.ts +4 -4
  17. package/dist/runtime/components/chat/mobile/shell/shell.d.vue.ts +14 -14
  18. package/dist/runtime/components/chat/mobile/shell/shell.vue.d.ts +14 -14
  19. package/dist/runtime/components/chat/topbar.d.vue.ts +8 -8
  20. package/dist/runtime/components/chat/topbar.vue.d.ts +8 -8
  21. package/dist/runtime/composables/useConversations.d.ts +2 -0
  22. package/dist/runtime/composables/useConversations.js +81 -36
  23. package/dist/runtime/composables/useEmbedConfig.d.ts +3 -0
  24. package/dist/runtime/composables/useEmbedConfig.js +4 -0
  25. package/dist/runtime/embed/components/ChatEmbed.vue +2 -0
  26. package/dist/runtime/embed/components/ChatEmbedInner.vue +10 -39
  27. package/dist/runtime/embed/components/configuration/ChatConfigurationContextFilesTab.d.vue.ts +4 -4
  28. package/dist/runtime/embed/components/configuration/ChatConfigurationContextFilesTab.vue.d.ts +4 -4
  29. package/dist/runtime/embed/components/configuration/ChatConfigurationMobileShell.d.vue.ts +2 -2
  30. package/dist/runtime/embed/components/configuration/ChatConfigurationMobileShell.vue.d.ts +2 -2
  31. package/dist/runtime/server/api/conversations/index.get.js +5 -4
  32. package/dist/runtime/server/utils/conversation-list.d.ts +4 -0
  33. package/dist/runtime/server/utils/conversation-list.js +28 -0
  34. package/dist/runtime/types/chat.d.ts +8 -0
  35. package/dist/runtime/types/chat.js +4 -0
  36. package/dist/runtime/types/embed.d.ts +3 -0
  37. package/dist/runtime/utils/conversation-creator-filter.d.ts +5 -0
  38. package/dist/runtime/utils/conversation-creator-filter.js +24 -0
  39. package/package.json +1 -1
@@ -19,25 +19,25 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
19
19
  rename: () => any;
20
20
  duplicate: () => any;
21
21
  export: () => any;
22
- "update:isEditing": (value: boolean) => any;
23
- "update:editTitle": (value: string) => any;
24
- saveTitle: () => any;
25
- cancelEditing: () => any;
26
22
  copyLink: () => any;
27
23
  debug: () => any;
28
24
  tabChange: (tab: TopbarTab) => any;
25
+ cancelEditing: () => any;
26
+ saveTitle: () => any;
27
+ "update:editTitle": (value: string) => any;
28
+ "update:isEditing": (value: boolean) => any;
29
29
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
30
30
  onDelete?: (() => any) | undefined;
31
31
  onRename?: (() => any) | undefined;
32
32
  onDuplicate?: (() => any) | undefined;
33
33
  onExport?: (() => any) | undefined;
34
- "onUpdate:isEditing"?: ((value: boolean) => any) | undefined;
35
- "onUpdate:editTitle"?: ((value: string) => any) | undefined;
36
- onSaveTitle?: (() => any) | undefined;
37
- onCancelEditing?: (() => any) | undefined;
38
34
  onCopyLink?: (() => any) | undefined;
39
35
  onDebug?: (() => any) | undefined;
40
36
  onTabChange?: ((tab: TopbarTab) => any) | undefined;
37
+ onCancelEditing?: (() => any) | undefined;
38
+ onSaveTitle?: (() => any) | undefined;
39
+ "onUpdate:editTitle"?: ((value: string) => any) | undefined;
40
+ "onUpdate:isEditing"?: ((value: boolean) => any) | undefined;
41
41
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
42
42
  declare const _default: typeof __VLS_export;
43
43
  export default _default;
@@ -14,5 +14,7 @@ export declare function useConversations(): {
14
14
  deleteConversation: (id: string) => Promise<boolean>;
15
15
  renameConversation: (id: string, title: string) => Promise<boolean>;
16
16
  duplicateConversation: (id: string) => Promise<ConversationWithMessages | null>;
17
+ conversationCreatorFilter: any;
18
+ conversationCreatorFilterCounts: any;
17
19
  updateConversationInList: (updated: Conversation | DeepReadonly<Conversation>) => void;
18
20
  };
@@ -24,10 +24,19 @@ export function mergeFetchedConversations(fetchedConversations, localConversatio
24
24
  }
25
25
  return sortConversationsByUpdatedAt([...mergedById.values()]);
26
26
  }
27
+ function bucketValue(bucket, key, fallback) {
28
+ return bucket.value[key] ?? fallback;
29
+ }
30
+ function setBucketValue(bucket, key, value) {
31
+ bucket.value = {
32
+ ...bucket.value,
33
+ [key]: value
34
+ };
35
+ }
27
36
  export function useConversations() {
28
37
  const chatApi = useChatApi();
29
38
  const embedConfig = useEmbedConfig();
30
- const { activeOrganization } = useChatAuth();
39
+ const { user: authUser, activeOrganization } = useChatAuth();
31
40
  const conversationBuckets = useState("conversations", () => ({}));
32
41
  const loadingBuckets = useState("conversations-loading", () => ({}));
33
42
  const errorBuckets = useState("conversations-error", () => ({}));
@@ -35,12 +44,27 @@ export function useConversations() {
35
44
  const mutationVersionBuckets = useState("conversations-mutation-version", () => ({}));
36
45
  const deletedIdBuckets = useState("conversations-deleted-ids", () => ({}));
37
46
  const mutatedIdBuckets = useState("conversations-mutated-ids", () => ({}));
47
+ const conversationCreatorFilter = useState(
48
+ "conversation-creator-filter",
49
+ () => embedConfig?.defaultConversationCreatorFilter?.value ?? "all"
50
+ );
38
51
  const isTelaAgentMode = computed(() => !!embedConfig?.telaAgentId.value);
39
- const workspaceScope = computed(() => resolveChatStateScope(
52
+ const runtimeScope = computed(() => resolveChatStateScope(
40
53
  embedConfig?.workspaceId.value || activeOrganization.value?.id,
41
54
  embedConfig?.telaAgentId.value,
42
55
  embedConfig?.conversationScope?.value
43
56
  ));
57
+ const workspaceScope = computed(() => `${runtimeScope.value}:creator:${conversationCreatorFilter.value}`);
58
+ const conversationBucketKey = (filter) => `${runtimeScope.value}:creator:${filter}`;
59
+ const conversationCreatorFilterCounts = computed(() => {
60
+ const allConversations = conversationBuckets.value[conversationBucketKey("all")] ?? [];
61
+ const mineConversations = conversationBuckets.value[conversationBucketKey("mine")];
62
+ const authUserId = authUser.value?.id;
63
+ return {
64
+ all: allConversations.length,
65
+ mine: mineConversations?.length ?? (authUserId ? allConversations.filter((conversation) => conversation.userId === authUserId).length : 0)
66
+ };
67
+ });
44
68
  const conversations = computed({
45
69
  get: () => conversationBuckets.value[workspaceScope.value] ?? [],
46
70
  set: (value) => {
@@ -95,15 +119,6 @@ export function useConversations() {
95
119
  };
96
120
  }
97
121
  });
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
122
  function markMutated(id) {
108
123
  mutationVersion.value = mutationVersion.value + 1;
109
124
  if (id) {
@@ -114,6 +129,33 @@ export function useConversations() {
114
129
  conversations.value = sortConversationsByUpdatedAt(nextConversations);
115
130
  markMutated(mutatedId);
116
131
  }
132
+ function commitConversationsToScope(scope, nextConversations, mutatedId) {
133
+ setBucketValue(conversationBuckets, scope, sortConversationsByUpdatedAt(nextConversations));
134
+ setBucketValue(mutationVersionBuckets, scope, bucketValue(mutationVersionBuckets, scope, 0) + 1);
135
+ if (mutatedId) {
136
+ setBucketValue(mutatedIdBuckets, scope, [
137
+ .../* @__PURE__ */ new Set([...bucketValue(mutatedIdBuckets, scope, []), mutatedId])
138
+ ]);
139
+ }
140
+ }
141
+ function upsertCreatedConversation(conversation) {
142
+ const allScope = conversationBucketKey("all");
143
+ const allConversations = bucketValue(conversationBuckets, allScope, []);
144
+ commitConversationsToScope(
145
+ allScope,
146
+ [conversation, ...allConversations.filter((item) => item.id !== conversation.id)],
147
+ conversation.id
148
+ );
149
+ if (authUser.value?.id === conversation.userId || conversationCreatorFilter.value === "mine") {
150
+ const mineScope = conversationBucketKey("mine");
151
+ const mineConversations = bucketValue(conversationBuckets, mineScope, []);
152
+ commitConversationsToScope(
153
+ mineScope,
154
+ [conversation, ...mineConversations.filter((item) => item.id !== conversation.id)],
155
+ conversation.id
156
+ );
157
+ }
158
+ }
117
159
  function addDeletedConversationId(id) {
118
160
  deletedConversationIds.value = [.../* @__PURE__ */ new Set([...deletedConversationIds.value, id])];
119
161
  markMutated();
@@ -122,48 +164,49 @@ export function useConversations() {
122
164
  deletedConversationIds.value = deletedConversationIds.value.filter((deletedId) => deletedId !== id);
123
165
  markMutated();
124
166
  }
125
- function pruneDeletedConversationIds(serverConversations) {
126
- const returnedIds = new Set(serverConversations.map((conversation) => conversation.id));
127
- deletedConversationIds.value = deletedConversationIds.value.filter((id) => returnedIds.has(id));
128
- }
129
167
  function cloneAppliedSettingsSnapshot(snapshot) {
130
168
  if (!snapshot)
131
169
  return null;
132
170
  return JSON.parse(JSON.stringify(snapshot));
133
171
  }
134
172
  const fetchConversations = async (options = {}) => {
135
- if (loading.value || hasFetched.value && !options.force) {
173
+ const requestScope = workspaceScope.value;
174
+ const requestCreatorFilter = conversationCreatorFilter.value;
175
+ if (bucketValue(loadingBuckets, requestScope, false) || bucketValue(fetchedBuckets, requestScope, false) && !options.force) {
136
176
  return;
137
177
  }
138
- loading.value = true;
139
- error.value = null;
140
- const mutationVersionAtStart = mutationVersion.value;
178
+ setBucketValue(loadingBuckets, requestScope, true);
179
+ setBucketValue(errorBuckets, requestScope, null);
180
+ const mutationVersionAtStart = bucketValue(mutationVersionBuckets, requestScope, 0);
141
181
  try {
142
182
  const data = await $fetch(
143
183
  chatApi.path("/conversations"),
144
- chatApi.withChatHeaders()
184
+ chatApi.withChatHeaders(requestCreatorFilter === "mine" ? { query: { creator: "me" } } : void 0)
145
185
  );
146
- const visibleData = data.filter((conversation) => !deletedConversationIds.value.includes(conversation.id));
147
- if (mutationVersion.value === mutationVersionAtStart) {
148
- conversations.value = sortConversationsByUpdatedAt(visibleData);
149
- mutatedConversationIds.value = [];
186
+ const currentDeletedIds = bucketValue(deletedIdBuckets, requestScope, []);
187
+ const currentMutatedIds = bucketValue(mutatedIdBuckets, requestScope, []);
188
+ const visibleData = data.filter((conversation) => !currentDeletedIds.includes(conversation.id));
189
+ if (bucketValue(mutationVersionBuckets, requestScope, 0) === mutationVersionAtStart) {
190
+ setBucketValue(conversationBuckets, requestScope, sortConversationsByUpdatedAt(visibleData));
191
+ setBucketValue(mutatedIdBuckets, requestScope, []);
150
192
  } else {
151
- conversations.value = mergeFetchedConversations(
193
+ setBucketValue(conversationBuckets, requestScope, mergeFetchedConversations(
152
194
  visibleData,
153
- conversations.value,
154
- deletedConversationIds.value,
155
- mutatedConversationIds.value
156
- );
157
- mutatedConversationIds.value = [];
195
+ bucketValue(conversationBuckets, requestScope, []),
196
+ currentDeletedIds,
197
+ currentMutatedIds
198
+ ));
199
+ setBucketValue(mutatedIdBuckets, requestScope, []);
158
200
  }
159
- pruneDeletedConversationIds(data);
160
- hasFetched.value = true;
201
+ const returnedIds = new Set(data.map((conversation) => conversation.id));
202
+ setBucketValue(deletedIdBuckets, requestScope, currentDeletedIds.filter((id) => returnedIds.has(id)));
203
+ setBucketValue(fetchedBuckets, requestScope, true);
161
204
  } catch (err) {
162
205
  const message = err instanceof Error ? err.message : "Erro ao carregar conversas";
163
- error.value = message;
206
+ setBucketValue(errorBuckets, requestScope, message);
164
207
  Sentry.captureException(err);
165
208
  } finally {
166
- loading.value = false;
209
+ setBucketValue(loadingBuckets, requestScope, false);
167
210
  }
168
211
  };
169
212
  const createConversation = async (options) => {
@@ -176,7 +219,7 @@ export function useConversations() {
176
219
  body: !isTelaAgentMode.value && options?.model ? { model: options.model } : void 0
177
220
  })
178
221
  );
179
- commitConversations([conversation, ...conversations.value], conversation.id);
222
+ upsertCreatedConversation(conversation);
180
223
  return conversation;
181
224
  } catch (err) {
182
225
  const message = err instanceof Error ? err.message : "Erro ao criar conversa";
@@ -272,7 +315,7 @@ export function useConversations() {
272
315
  method: "POST"
273
316
  })
274
317
  );
275
- commitConversations([duplicated, ...conversations.value], duplicated.id);
318
+ upsertCreatedConversation(duplicated);
276
319
  statusToast.update({
277
320
  text: "Conversa duplicada",
278
321
  icon: "i-ph-check"
@@ -297,6 +340,8 @@ export function useConversations() {
297
340
  deleteConversation,
298
341
  renameConversation,
299
342
  duplicateConversation,
343
+ conversationCreatorFilter,
344
+ conversationCreatorFilterCounts,
300
345
  updateConversationInList
301
346
  };
302
347
  }
@@ -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
 
@@ -8,15 +8,15 @@ type __VLS_Props = {
8
8
  getFileType: (fileName: string) => string;
9
9
  };
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
11
+ "update:searchQuery": (value: string) => any;
11
12
  handleFileSelect: (event: Event) => any;
12
- removeContextFile: (url: string) => any;
13
13
  removePendingFile: (file: File) => any;
14
- "update:searchQuery": (value: string) => any;
14
+ removeContextFile: (url: string) => any;
15
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
16
17
  onHandleFileSelect?: ((event: Event) => any) | undefined;
17
- onRemoveContextFile?: ((url: string) => any) | undefined;
18
18
  onRemovePendingFile?: ((file: File) => any) | undefined;
19
- "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
19
+ onRemoveContextFile?: ((url: string) => any) | undefined;
20
20
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
21
  declare const _default: typeof __VLS_export;
22
22
  export default _default;
@@ -8,15 +8,15 @@ type __VLS_Props = {
8
8
  getFileType: (fileName: string) => string;
9
9
  };
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
11
+ "update:searchQuery": (value: string) => any;
11
12
  handleFileSelect: (event: Event) => any;
12
- removeContextFile: (url: string) => any;
13
13
  removePendingFile: (file: File) => any;
14
- "update:searchQuery": (value: string) => any;
14
+ removeContextFile: (url: string) => any;
15
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
16
17
  onHandleFileSelect?: ((event: Event) => any) | undefined;
17
- onRemoveContextFile?: ((url: string) => any) | undefined;
18
18
  onRemovePendingFile?: ((file: File) => any) | undefined;
19
- "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
19
+ onRemoveContextFile?: ((url: string) => any) | undefined;
20
20
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
21
  declare const _default: typeof __VLS_export;
22
22
  export default _default;
@@ -15,13 +15,13 @@ type __VLS_Slots = {} & {
15
15
  };
16
16
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
17
17
  close: () => any;
18
- save: () => any;
19
18
  back: () => any;
19
+ save: () => any;
20
20
  selectTab: (value: string) => any;
21
21
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
22
22
  onClose?: (() => any) | undefined;
23
- onSave?: (() => any) | undefined;
24
23
  onBack?: (() => any) | undefined;
24
+ onSave?: (() => any) | undefined;
25
25
  onSelectTab?: ((value: string) => any) | undefined;
26
26
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
27
27
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -15,13 +15,13 @@ type __VLS_Slots = {} & {
15
15
  };
16
16
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
17
17
  close: () => any;
18
- save: () => any;
19
18
  back: () => any;
19
+ save: () => any;
20
20
  selectTab: (value: string) => any;
21
21
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
22
22
  onClose?: (() => any) | undefined;
23
- onSave?: (() => any) | undefined;
24
23
  onBack?: (() => any) | undefined;
24
+ onSave?: (() => any) | undefined;
25
25
  onSelectTab?: ((value: string) => any) | undefined;
26
26
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
27
27
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,13 +1,14 @@
1
- import { defineEventHandler } from "h3";
1
+ import { defineEventHandler, getQuery } from "h3";
2
2
  import { desc } from "drizzle-orm";
3
3
  import { useDb, schema } from "#chat-runtime/server/db";
4
4
  import { resolveChatRuntimeContext } from "#chat-runtime/server/utils/chat-context";
5
- import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
5
+ import { conversationListConditions, resolveRequestedConversationCreatorFilter } from "#chat-runtime/server/utils/conversation-list";
6
6
  import { logger } from "#chat-runtime/server/utils/logger";
7
7
  export default defineEventHandler(async (event) => {
8
8
  const context = resolveChatRuntimeContext(event);
9
+ const creatorFilter = resolveRequestedConversationCreatorFilter(getQuery(event).creator);
9
10
  const db = useDb();
10
- const conversations = await db.select().from(schema.conversations).where(conversationScopeConditions(context)).orderBy(desc(schema.conversations.updatedAt));
11
- logger.debug({ workspaceId: context.workspaceId, count: conversations.length }, "Conversations listed");
11
+ const conversations = await db.select().from(schema.conversations).where(conversationListConditions(context, creatorFilter)).orderBy(desc(schema.conversations.updatedAt));
12
+ logger.debug({ workspaceId: context.workspaceId, creatorFilter, count: conversations.length }, "Conversations listed");
12
13
  return conversations;
13
14
  });
@@ -0,0 +1,4 @@
1
+ import type { ChatRuntimeContext } from '#chat-runtime/server/utils/chat-context';
2
+ import type { ConversationCreatorFilter } from '#chat-runtime/types/chat';
3
+ export declare function resolveRequestedConversationCreatorFilter(requestedCreator: unknown): ConversationCreatorFilter;
4
+ export declare function conversationListConditions(context: ChatRuntimeContext, creatorFilter?: ConversationCreatorFilter): import("drizzle-orm").SQL<unknown> | undefined;
@@ -0,0 +1,28 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ import { createError } from "h3";
3
+ import { schema } from "#chat-runtime/server/db";
4
+ import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
5
+ export function resolveRequestedConversationCreatorFilter(requestedCreator) {
6
+ if (Array.isArray(requestedCreator)) {
7
+ throw createError({
8
+ statusCode: 400,
9
+ statusMessage: "Invalid conversation creator filter"
10
+ });
11
+ }
12
+ if (requestedCreator === void 0 || requestedCreator === null || requestedCreator === "" || requestedCreator === "all") {
13
+ return "all";
14
+ }
15
+ if (requestedCreator === "me") {
16
+ return "mine";
17
+ }
18
+ throw createError({
19
+ statusCode: 400,
20
+ statusMessage: "Invalid conversation creator filter"
21
+ });
22
+ }
23
+ export function conversationListConditions(context, creatorFilter = "all") {
24
+ return and(
25
+ conversationScopeConditions(context),
26
+ creatorFilter === "mine" ? eq(schema.conversations.userId, context.user.id) : void 0
27
+ );
28
+ }
@@ -5,6 +5,14 @@ export type ExternalSkill = SchemaExternalSkill;
5
5
  export type MessageFile = SchemaMessageFile;
6
6
  export type Conversation = InferSelectModel<typeof conversations>;
7
7
  export type Message = InferSelectModel<typeof messages>;
8
+ export type ConversationCreatorFilter = 'all' | 'mine';
9
+ export declare const CONVERSATION_CREATOR_FILTER_OPTIONS: readonly [{
10
+ readonly label: "Todas";
11
+ readonly value: "all";
12
+ }, {
13
+ readonly label: "Minhas";
14
+ readonly value: "mine";
15
+ }];
8
16
  export type ConversationWithMessages = Conversation & {
9
17
  messages: Message[];
10
18
  };
@@ -1,3 +1,7 @@
1
+ export const CONVERSATION_CREATOR_FILTER_OPTIONS = [
2
+ { label: "Todas", value: "all" },
3
+ { label: "Minhas", value: "mine" }
4
+ ];
1
5
  export const AVAILABLE_MODELS = [
2
6
  { value: "claude-sonnet-4-5", label: "Sonnet 4.5", description: "High performant agent for complex tasks" },
3
7
  { value: "claude-sonnet-4-6", label: "Sonnet 4.6", description: "Fast and intelligent agent" },
@@ -1,6 +1,7 @@
1
1
  import type { Component, DeepReadonly } from 'vue';
2
2
  import type { WorkspaceSettings } from './workspace-settings-data.js';
3
3
  import type { TelaAgentExecutionInput } from './tela-agent.js';
4
+ import type { ConversationCreatorFilter } from './chat.js';
4
5
  import type { ChatLoadingMessageMode } from '../utils/chat-loading-messages.js';
5
6
  import type { ChatActor, ChatFeatureConfig } from '../utils/tela-chat.js';
6
7
  export type ChatEmbedSharedProps = {
@@ -10,6 +11,8 @@ export type ChatEmbedSharedProps = {
10
11
  initialConversationId?: string | null;
11
12
  /** Technical scope that isolates conversation history within the current workspace and runtime. */
12
13
  conversationScope?: string | null;
14
+ /** Initial creator filter for the sidebar conversation list. Defaults to all conversations. */
15
+ defaultConversationCreatorFilter?: ConversationCreatorFilter;
13
16
  /** Hide the conversation list and render only the active chat surface. */
14
17
  hideSidebar?: boolean;
15
18
  /** Hide workspace settings actions while keeping the chat header and content unchanged. */
@@ -0,0 +1,5 @@
1
+ import { type ConversationCreatorFilter } from '../types/chat.js';
2
+ type ConversationCreatorFilterCounts = Record<ConversationCreatorFilter, number>;
3
+ export declare function resolveConversationCreatorFilterKeyboardTarget(currentFilter: ConversationCreatorFilter, key: string): ConversationCreatorFilter | null;
4
+ export declare function hasConversationCreatorFilterResults(counts: ConversationCreatorFilterCounts): boolean;
5
+ export {};
@@ -0,0 +1,24 @@
1
+ import { CONVERSATION_CREATOR_FILTER_OPTIONS } from "../types/chat.js";
2
+ const FILTER_NAVIGATION_KEYS = {
3
+ ArrowLeft: -1,
4
+ ArrowUp: -1,
5
+ ArrowRight: 1,
6
+ ArrowDown: 1
7
+ };
8
+ export function resolveConversationCreatorFilterKeyboardTarget(currentFilter, key) {
9
+ const filters = CONVERSATION_CREATOR_FILTER_OPTIONS.map((option) => option.value);
10
+ if (key === "Home")
11
+ return filters[0] ?? null;
12
+ if (key === "End")
13
+ return filters.at(-1) ?? null;
14
+ if (!(key in FILTER_NAVIGATION_KEYS))
15
+ return null;
16
+ const currentIndex = filters.indexOf(currentFilter);
17
+ if (currentIndex === -1)
18
+ return null;
19
+ const direction = FILTER_NAVIGATION_KEYS[key];
20
+ return filters[(currentIndex + direction + filters.length) % filters.length] ?? null;
21
+ }
22
+ export function hasConversationCreatorFilterResults(counts) {
23
+ return CONVERSATION_CREATOR_FILTER_OPTIONS.some((option) => counts[option.value] > 0);
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {