@meistrari/chat-nuxt 1.10.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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "1.10.0",
4
+ "version": "1.11.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -15,24 +15,24 @@ type __VLS_Props = {
15
15
  };
16
16
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
17
17
  delete: () => any;
18
- openDrawer: () => any;
19
- openOutdatedSettings: () => any;
20
18
  rename: () => any;
21
19
  duplicate: () => any;
22
20
  export: () => any;
23
21
  copyLink: () => any;
24
22
  debug: () => any;
25
23
  tabChange: (tab: TopbarTab) => any;
24
+ openDrawer: () => any;
25
+ openOutdatedSettings: () => any;
26
26
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
27
27
  onDelete?: (() => any) | undefined;
28
- onOpenDrawer?: (() => any) | undefined;
29
- onOpenOutdatedSettings?: (() => any) | undefined;
30
28
  onRename?: (() => any) | undefined;
31
29
  onDuplicate?: (() => any) | undefined;
32
30
  onExport?: (() => any) | undefined;
33
31
  onCopyLink?: (() => any) | undefined;
34
32
  onDebug?: (() => any) | undefined;
35
33
  onTabChange?: ((tab: TopbarTab) => any) | undefined;
34
+ onOpenDrawer?: (() => any) | undefined;
35
+ onOpenOutdatedSettings?: (() => any) | undefined;
36
36
  }>, {
37
37
  showDrawerButton: boolean;
38
38
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -15,24 +15,24 @@ type __VLS_Props = {
15
15
  };
16
16
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
17
17
  delete: () => any;
18
- openDrawer: () => any;
19
- openOutdatedSettings: () => any;
20
18
  rename: () => any;
21
19
  duplicate: () => any;
22
20
  export: () => any;
23
21
  copyLink: () => any;
24
22
  debug: () => any;
25
23
  tabChange: (tab: TopbarTab) => any;
24
+ openDrawer: () => any;
25
+ openOutdatedSettings: () => any;
26
26
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
27
27
  onDelete?: (() => any) | undefined;
28
- onOpenDrawer?: (() => any) | undefined;
29
- onOpenOutdatedSettings?: (() => any) | undefined;
30
28
  onRename?: (() => any) | undefined;
31
29
  onDuplicate?: (() => any) | undefined;
32
30
  onExport?: (() => any) | undefined;
33
31
  onCopyLink?: (() => any) | undefined;
34
32
  onDebug?: (() => any) | undefined;
35
33
  onTabChange?: ((tab: TopbarTab) => any) | undefined;
34
+ onOpenDrawer?: (() => any) | undefined;
35
+ onOpenOutdatedSettings?: (() => any) | undefined;
36
36
  }>, {
37
37
  showDrawerButton: boolean;
38
38
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -50,7 +50,10 @@ type __VLS_Slots = {} & {
50
50
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
51
51
  delete: () => any;
52
52
  cancel: () => any;
53
- settings: () => any;
53
+ selectConversation: (id: string) => any;
54
+ rename: () => any;
55
+ duplicate: () => any;
56
+ export: () => any;
54
57
  send: (content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
55
58
  type: "file";
56
59
  name: string;
@@ -63,26 +66,26 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
63
66
  content: string;
64
67
  })[] | undefined) => any;
65
68
  "update:model": (value: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any;
66
- rename: () => any;
67
- duplicate: () => any;
68
- export: () => any;
69
+ settings: () => any;
70
+ "update:editTitle": (value: string) => any;
71
+ saveTitle: () => any;
72
+ cancelEditing: () => any;
69
73
  copyLink: () => any;
70
74
  debug: () => any;
71
75
  tabChange: (tab: TopbarTab) => any;
72
76
  newConversation: () => any;
73
77
  switchWorkspace: () => any;
74
78
  signout: () => any;
75
- cancelEditing: () => any;
76
79
  dismissOutdatedBanner: () => any;
77
- saveTitle: () => any;
78
- selectConversation: (id: string) => any;
79
80
  "update:drawerOpen": (value: boolean) => any;
80
- "update:editTitle": (value: string) => any;
81
81
  "update:workspaceSheetOpen": (value: boolean) => any;
82
82
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
83
83
  onDelete?: (() => any) | undefined;
84
84
  onCancel?: (() => any) | undefined;
85
- onSettings?: (() => any) | undefined;
85
+ onSelectConversation?: ((id: string) => any) | undefined;
86
+ onRename?: (() => any) | undefined;
87
+ onDuplicate?: (() => any) | undefined;
88
+ onExport?: (() => any) | undefined;
86
89
  onSend?: ((content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
87
90
  type: "file";
88
91
  name: string;
@@ -95,21 +98,18 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
95
98
  content: string;
96
99
  })[] | undefined) => any) | undefined;
97
100
  "onUpdate:model"?: ((value: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any) | undefined;
98
- onRename?: (() => any) | undefined;
99
- onDuplicate?: (() => any) | undefined;
100
- onExport?: (() => any) | undefined;
101
+ onSettings?: (() => any) | undefined;
102
+ "onUpdate:editTitle"?: ((value: string) => any) | undefined;
103
+ onSaveTitle?: (() => any) | undefined;
104
+ onCancelEditing?: (() => any) | undefined;
101
105
  onCopyLink?: (() => any) | undefined;
102
106
  onDebug?: (() => any) | undefined;
103
107
  onTabChange?: ((tab: TopbarTab) => any) | undefined;
104
108
  onNewConversation?: (() => any) | undefined;
105
109
  onSwitchWorkspace?: (() => any) | undefined;
106
110
  onSignout?: (() => any) | undefined;
107
- onCancelEditing?: (() => any) | undefined;
108
111
  onDismissOutdatedBanner?: (() => any) | undefined;
109
- onSaveTitle?: (() => any) | undefined;
110
- onSelectConversation?: ((id: string) => any) | undefined;
111
112
  "onUpdate:drawerOpen"?: ((value: boolean) => any) | undefined;
112
- "onUpdate:editTitle"?: ((value: string) => any) | undefined;
113
113
  "onUpdate:workspaceSheetOpen"?: ((value: boolean) => any) | undefined;
114
114
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
115
115
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -50,7 +50,10 @@ type __VLS_Slots = {} & {
50
50
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
51
51
  delete: () => any;
52
52
  cancel: () => any;
53
- settings: () => any;
53
+ selectConversation: (id: string) => any;
54
+ rename: () => any;
55
+ duplicate: () => any;
56
+ export: () => any;
54
57
  send: (content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
55
58
  type: "file";
56
59
  name: string;
@@ -63,26 +66,26 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
63
66
  content: string;
64
67
  })[] | undefined) => any;
65
68
  "update:model": (value: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any;
66
- rename: () => any;
67
- duplicate: () => any;
68
- export: () => any;
69
+ settings: () => any;
70
+ "update:editTitle": (value: string) => any;
71
+ saveTitle: () => any;
72
+ cancelEditing: () => any;
69
73
  copyLink: () => any;
70
74
  debug: () => any;
71
75
  tabChange: (tab: TopbarTab) => any;
72
76
  newConversation: () => any;
73
77
  switchWorkspace: () => any;
74
78
  signout: () => any;
75
- cancelEditing: () => any;
76
79
  dismissOutdatedBanner: () => any;
77
- saveTitle: () => any;
78
- selectConversation: (id: string) => any;
79
80
  "update:drawerOpen": (value: boolean) => any;
80
- "update:editTitle": (value: string) => any;
81
81
  "update:workspaceSheetOpen": (value: boolean) => any;
82
82
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
83
83
  onDelete?: (() => any) | undefined;
84
84
  onCancel?: (() => any) | undefined;
85
- onSettings?: (() => any) | undefined;
85
+ onSelectConversation?: ((id: string) => any) | undefined;
86
+ onRename?: (() => any) | undefined;
87
+ onDuplicate?: (() => any) | undefined;
88
+ onExport?: (() => any) | undefined;
86
89
  onSend?: ((content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
87
90
  type: "file";
88
91
  name: string;
@@ -95,21 +98,18 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
95
98
  content: string;
96
99
  })[] | undefined) => any) | undefined;
97
100
  "onUpdate:model"?: ((value: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any) | undefined;
98
- onRename?: (() => any) | undefined;
99
- onDuplicate?: (() => any) | undefined;
100
- onExport?: (() => any) | undefined;
101
+ onSettings?: (() => any) | undefined;
102
+ "onUpdate:editTitle"?: ((value: string) => any) | undefined;
103
+ onSaveTitle?: (() => any) | undefined;
104
+ onCancelEditing?: (() => any) | undefined;
101
105
  onCopyLink?: (() => any) | undefined;
102
106
  onDebug?: (() => any) | undefined;
103
107
  onTabChange?: ((tab: TopbarTab) => any) | undefined;
104
108
  onNewConversation?: (() => any) | undefined;
105
109
  onSwitchWorkspace?: (() => any) | undefined;
106
110
  onSignout?: (() => any) | undefined;
107
- onCancelEditing?: (() => any) | undefined;
108
111
  onDismissOutdatedBanner?: (() => any) | undefined;
109
- onSaveTitle?: (() => any) | undefined;
110
- onSelectConversation?: ((id: string) => any) | undefined;
111
112
  "onUpdate:drawerOpen"?: ((value: boolean) => any) | undefined;
112
- "onUpdate:editTitle"?: ((value: string) => any) | undefined;
113
113
  "onUpdate:workspaceSheetOpen"?: ((value: boolean) => any) | undefined;
114
114
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
115
115
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -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;
22
26
  copyLink: () => any;
23
27
  debug: () => any;
24
28
  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;
34
38
  onCopyLink?: (() => any) | undefined;
35
39
  onDebug?: (() => any) | undefined;
36
40
  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;
@@ -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;
22
26
  copyLink: () => any;
23
27
  debug: () => any;
24
28
  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;
34
38
  onCopyLink?: (() => any) | undefined;
35
39
  onDebug?: (() => any) | undefined;
36
40
  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;
@@ -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()
@@ -33,6 +35,23 @@ function setBucketValue(bucket, key, value) {
33
35
  [key]: value
34
36
  };
35
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
+ }
36
55
  export function useConversations() {
37
56
  const chatApi = useChatApi();
38
57
  const embedConfig = useEmbedConfig();
@@ -44,6 +63,9 @@ export function useConversations() {
44
63
  const mutationVersionBuckets = useState("conversations-mutation-version", () => ({}));
45
64
  const deletedIdBuckets = useState("conversations-deleted-ids", () => ({}));
46
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", () => ({}));
47
69
  const conversationCreatorFilter = useState(
48
70
  "conversation-creator-filter",
49
71
  () => embedConfig?.defaultConversationCreatorFilter?.value ?? "all"
@@ -54,17 +76,10 @@ export function useConversations() {
54
76
  embedConfig?.telaAgentId.value,
55
77
  embedConfig?.conversationScope?.value
56
78
  ));
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
- });
79
+ const workspaceScope = computed(() => conversationBucketKeyForRuntime(runtimeScope.value, conversationCreatorFilter.value));
80
+ const conversationCreatorFilterCounts = computed(
81
+ () => metadataBuckets.value[runtimeScope.value]?.counts ?? { all: 0, mine: 0 }
82
+ );
68
83
  const conversations = computed({
69
84
  get: () => conversationBuckets.value[workspaceScope.value] ?? [],
70
85
  set: (value) => {
@@ -74,33 +89,6 @@ export function useConversations() {
74
89
  };
75
90
  }
76
91
  });
77
- const mutationVersion = computed({
78
- get: () => mutationVersionBuckets.value[workspaceScope.value] ?? 0,
79
- set: (value) => {
80
- mutationVersionBuckets.value = {
81
- ...mutationVersionBuckets.value,
82
- [workspaceScope.value]: value
83
- };
84
- }
85
- });
86
- const deletedConversationIds = computed({
87
- get: () => deletedIdBuckets.value[workspaceScope.value] ?? [],
88
- set: (value) => {
89
- deletedIdBuckets.value = {
90
- ...deletedIdBuckets.value,
91
- [workspaceScope.value]: value
92
- };
93
- }
94
- });
95
- const mutatedConversationIds = computed({
96
- get: () => mutatedIdBuckets.value[workspaceScope.value] ?? [],
97
- set: (value) => {
98
- mutatedIdBuckets.value = {
99
- ...mutatedIdBuckets.value,
100
- [workspaceScope.value]: value
101
- };
102
- }
103
- });
104
92
  const loading = computed({
105
93
  get: () => loadingBuckets.value[workspaceScope.value] ?? false,
106
94
  set: (value) => {
@@ -119,60 +107,152 @@ export function useConversations() {
119
107
  };
120
108
  }
121
109
  });
122
- function markMutated(id) {
123
- mutationVersion.value = mutationVersion.value + 1;
110
+ function markScopeMutated(scope, id) {
111
+ setBucketValue(mutationVersionBuckets, scope, bucketValue(mutationVersionBuckets, scope, 0) + 1);
124
112
  if (id) {
125
- mutatedConversationIds.value = [.../* @__PURE__ */ new Set([...mutatedConversationIds.value, id])];
113
+ setBucketValue(mutatedIdBuckets, scope, [
114
+ .../* @__PURE__ */ new Set([...bucketValue(mutatedIdBuckets, scope, []), id])
115
+ ]);
126
116
  }
127
117
  }
128
- function commitConversations(nextConversations, mutatedId) {
129
- conversations.value = sortConversationsByUpdatedAt(nextConversations);
130
- markMutated(mutatedId);
131
- }
132
118
  function commitConversationsToScope(scope, nextConversations, mutatedId) {
133
119
  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
- ]);
120
+ markScopeMutated(scope, mutatedId);
121
+ }
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])];
126
+ }
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
+ }
139
133
  }
134
+ return null;
135
+ }
136
+ function snapshotConversationBuckets(scopes) {
137
+ return Object.fromEntries(scopes.map((scope) => [
138
+ scope,
139
+ hasBucketValue(conversationBuckets, scope) ? [...bucketValue(conversationBuckets, scope, [])] : void 0
140
+ ]));
140
141
  }
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, []);
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, []);
152
158
  commitConversationsToScope(
153
- mineScope,
154
- [conversation, ...mineConversations.filter((item) => item.id !== conversation.id)],
159
+ scope,
160
+ [
161
+ conversation,
162
+ ...currentConversations.filter((item) => item.id !== conversation.id && item.id !== replacedId)
163
+ ],
155
164
  conversation.id
156
165
  );
157
166
  }
158
167
  }
159
- function addDeletedConversationId(id) {
160
- deletedConversationIds.value = [.../* @__PURE__ */ new Set([...deletedConversationIds.value, id])];
161
- markMutated();
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
+ }
162
178
  }
163
- function removeDeletedConversationId(id) {
164
- deletedConversationIds.value = deletedConversationIds.value.filter((deletedId) => deletedId !== id);
165
- markMutated();
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
+ }
166
190
  }
167
- function cloneAppliedSettingsSnapshot(snapshot) {
168
- if (!snapshot)
169
- return null;
170
- return JSON.parse(JSON.stringify(snapshot));
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
+ }
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
+ }
171
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
+ };
172
250
  const fetchConversations = async (options = {}) => {
173
251
  const requestScope = workspaceScope.value;
174
252
  const requestCreatorFilter = conversationCreatorFilter.value;
253
+ const metadataPromise = fetchConversationMetadata({ force: options.force });
175
254
  if (bucketValue(loadingBuckets, requestScope, false) || bucketValue(fetchedBuckets, requestScope, false) && !options.force) {
255
+ await metadataPromise;
176
256
  return;
177
257
  }
178
258
  setBucketValue(loadingBuckets, requestScope, true);
@@ -186,6 +266,7 @@ export function useConversations() {
186
266
  const currentDeletedIds = bucketValue(deletedIdBuckets, requestScope, []);
187
267
  const currentMutatedIds = bucketValue(mutatedIdBuckets, requestScope, []);
188
268
  const visibleData = data.filter((conversation) => !currentDeletedIds.includes(conversation.id));
269
+ await metadataPromise;
189
270
  if (bucketValue(mutationVersionBuckets, requestScope, 0) === mutationVersionAtStart) {
190
271
  setBucketValue(conversationBuckets, requestScope, sortConversationsByUpdatedAt(visibleData));
191
272
  setBucketValue(mutatedIdBuckets, requestScope, []);
@@ -211,6 +292,26 @@ export function useConversations() {
211
292
  };
212
293
  const createConversation = async (options) => {
213
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);
214
315
  try {
215
316
  const conversation = await $fetch(
216
317
  chatApi.path("/conversations"),
@@ -219,9 +320,11 @@ export function useConversations() {
219
320
  body: !isTelaAgentMode.value && options?.model ? { model: options.model } : void 0
220
321
  })
221
322
  );
222
- upsertCreatedConversation(conversation);
323
+ upsertConversationInLoadedBuckets(conversation, requestRuntimeScope, optimisticConversation.id);
223
324
  return conversation;
224
325
  } catch (err) {
326
+ removeConversationFromLoadedBuckets(optimisticConversation.id, requestRuntimeScope);
327
+ restoreConversationMetadata(requestRuntimeScope, metadataSnapshot);
225
328
  const message = err instanceof Error ? err.message : "Erro ao criar conversa";
226
329
  error.value = message;
227
330
  return null;
@@ -230,9 +333,16 @@ export function useConversations() {
230
333
  const deleteConversation = async (id) => {
231
334
  error.value = null;
232
335
  const statusToast = useStatusToast();
233
- const deletedConversation = conversations.value.find((c) => c.id === id);
234
- addDeletedConversationId(id);
235
- 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
+ }
236
346
  try {
237
347
  await $fetch(
238
348
  chatApi.path(`/conversations/${id}`),
@@ -246,10 +356,9 @@ export function useConversations() {
246
356
  });
247
357
  return true;
248
358
  } catch (err) {
249
- removeDeletedConversationId(id);
250
- if (deletedConversation) {
251
- commitConversations([...conversations.value, deletedConversation], deletedConversation.id);
252
- }
359
+ removeDeletedConversationIdFromScopes(id, affectedScopes);
360
+ restoreConversationBuckets(conversationSnapshot, id);
361
+ restoreConversationMetadata(requestRuntimeScope, metadataSnapshot);
253
362
  const message = err instanceof Error ? err.message : "Erro ao deletar conversa";
254
363
  error.value = message;
255
364
  statusToast.update({
@@ -260,45 +369,29 @@ export function useConversations() {
260
369
  }
261
370
  };
262
371
  const updateConversationInList = (updated) => {
263
- const index = conversations.value.findIndex((c) => c.id === updated.id);
264
- if (index !== -1) {
265
- const nextConversations = [...conversations.value];
266
- nextConversations[index] = {
267
- id: updated.id,
268
- workspaceId: updated.workspaceId,
269
- userId: updated.userId,
270
- title: updated.title,
271
- threadSessionId: updated.threadSessionId,
272
- model: updated.model,
273
- appliedSettingsAt: updated.appliedSettingsAt,
274
- appliedSettingsSnapshot: cloneAppliedSettingsSnapshot(updated.appliedSettingsSnapshot),
275
- telaAgentId: updated.telaAgentId,
276
- createdBy: updated.createdBy,
277
- conversationScope: updated.conversationScope,
278
- createdAt: updated.createdAt,
279
- updatedAt: updated.updatedAt
280
- };
281
- commitConversations(nextConversations, updated.id);
282
- }
372
+ updateConversationInLoadedBuckets(updated);
283
373
  };
284
374
  const renameConversation = async (id, title) => {
285
375
  error.value = null;
286
- 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);
287
380
  if (!conversation)
288
381
  return false;
289
- const previousTitle = conversation.title;
290
- updateConversationInList({ ...conversation, title, updatedAt: /* @__PURE__ */ new Date() });
382
+ updateConversationInLoadedBuckets({ ...conversation, title, updatedAt: /* @__PURE__ */ new Date() }, requestRuntimeScope);
291
383
  try {
292
- await $fetch(
384
+ const updated = await $fetch(
293
385
  chatApi.path(`/conversations/${id}`),
294
386
  chatApi.withChatHeaders({
295
387
  method: "PATCH",
296
388
  body: { title }
297
389
  })
298
390
  );
391
+ updateConversationInLoadedBuckets(updated, requestRuntimeScope);
299
392
  return true;
300
393
  } catch (err) {
301
- updateConversationInList({ ...conversation, title: previousTitle });
394
+ restoreConversationBuckets(conversationSnapshot, id);
302
395
  const message = err instanceof Error ? err.message : "Erro ao renomear conversa";
303
396
  error.value = message;
304
397
  Sentry.captureException(err);
@@ -308,6 +401,7 @@ export function useConversations() {
308
401
  const duplicateConversation = async (id) => {
309
402
  error.value = null;
310
403
  const statusToast = useStatusToast();
404
+ const requestRuntimeScope = runtimeScope.value;
311
405
  try {
312
406
  const duplicated = await $fetch(
313
407
  chatApi.path(`/conversations/${id}/duplicate`),
@@ -315,7 +409,8 @@ export function useConversations() {
315
409
  method: "POST"
316
410
  })
317
411
  );
318
- upsertCreatedConversation(duplicated);
412
+ upsertConversationInLoadedBuckets(duplicated, requestRuntimeScope);
413
+ updateConversationCounts(requestRuntimeScope, duplicated, 1);
319
414
  statusToast.update({
320
415
  text: "Conversa duplicada",
321
416
  icon: "i-ph-check"
@@ -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;
12
11
  handleFileSelect: (event: Event) => any;
13
- removePendingFile: (file: File) => any;
14
12
  removeContextFile: (url: string) => any;
13
+ removePendingFile: (file: File) => any;
14
+ "update:searchQuery": (value: string) => any;
15
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
- "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
17
16
  onHandleFileSelect?: ((event: Event) => any) | undefined;
18
- onRemovePendingFile?: ((file: File) => any) | undefined;
19
17
  onRemoveContextFile?: ((url: string) => any) | undefined;
18
+ onRemovePendingFile?: ((file: File) => any) | undefined;
19
+ "onUpdate:searchQuery"?: ((value: 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;
12
11
  handleFileSelect: (event: Event) => any;
13
- removePendingFile: (file: File) => any;
14
12
  removeContextFile: (url: string) => any;
13
+ removePendingFile: (file: File) => any;
14
+ "update:searchQuery": (value: string) => any;
15
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
- "onUpdate:searchQuery"?: ((value: string) => any) | undefined;
17
16
  onHandleFileSelect?: ((event: Event) => any) | undefined;
18
- onRemovePendingFile?: ((file: File) => any) | undefined;
19
17
  onRemoveContextFile?: ((url: string) => any) | undefined;
18
+ onRemovePendingFile?: ((file: File) => any) | undefined;
19
+ "onUpdate:searchQuery"?: ((value: 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
- back: () => any;
19
18
  save: () => any;
19
+ back: () => any;
20
20
  selectTab: (value: string) => any;
21
21
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
22
22
  onClose?: (() => any) | undefined;
23
- onBack?: (() => any) | undefined;
24
23
  onSave?: (() => any) | undefined;
24
+ onBack?: (() => 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
- back: () => any;
19
18
  save: () => any;
19
+ back: () => any;
20
20
  selectTab: (value: string) => any;
21
21
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
22
22
  onClose?: (() => any) | undefined;
23
- onBack?: (() => any) | undefined;
24
23
  onSave?: (() => any) | undefined;
24
+ onBack?: (() => 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>;
@@ -0,0 +1 @@
1
+ export { default } from '../../conversations/metadata.get.js';
@@ -0,0 +1 @@
1
+ export { default } from "../../conversations/metadata.get.js";
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<ConversationListMetadata>>;
2
+ export default _default;
@@ -0,0 +1,20 @@
1
+ import { defineEventHandler } from "h3";
2
+ import { sql } from "drizzle-orm";
3
+ import { useDb, schema } from "#chat-runtime/server/db";
4
+ import { resolveChatRuntimeContext } from "#chat-runtime/server/utils/chat-context";
5
+ import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
6
+ import { logger } from "#chat-runtime/server/utils/logger";
7
+ export default defineEventHandler(async (event) => {
8
+ const context = resolveChatRuntimeContext(event);
9
+ const db = useDb();
10
+ const [metadata] = await db.select({
11
+ all: sql`COUNT(*)::int`,
12
+ mine: sql`COUNT(*) FILTER (WHERE ${schema.conversations.userId} = ${context.user.id})::int`
13
+ }).from(schema.conversations).where(conversationScopeConditions(context));
14
+ const counts = {
15
+ all: metadata?.all ?? 0,
16
+ mine: metadata?.mine ?? 0
17
+ };
18
+ logger.debug({ workspaceId: context.workspaceId, counts }, "Conversations metadata loaded");
19
+ return { counts };
20
+ });
@@ -6,6 +6,10 @@ export type MessageFile = SchemaMessageFile;
6
6
  export type Conversation = InferSelectModel<typeof conversations>;
7
7
  export type Message = InferSelectModel<typeof messages>;
8
8
  export type ConversationCreatorFilter = 'all' | 'mine';
9
+ export type ConversationCreatorFilterCounts = Record<ConversationCreatorFilter, number>;
10
+ export type ConversationListMetadata = {
11
+ counts: ConversationCreatorFilterCounts;
12
+ };
9
13
  export declare const CONVERSATION_CREATOR_FILTER_OPTIONS: readonly [{
10
14
  readonly label: "Todas";
11
15
  readonly value: "all";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {