@meistrari/chat-nuxt 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/components/chat/conversation-item.vue +102 -62
- package/dist/runtime/components/chat/conversation-list.vue +17 -4
- package/dist/runtime/components/chat/conversation-search.d.vue.ts +16 -0
- package/dist/runtime/components/chat/conversation-search.vue +16 -0
- package/dist/runtime/components/chat/conversation-search.vue.d.ts +16 -0
- package/dist/runtime/components/chat/mobile/shell/drawer.vue +15 -5
- package/dist/runtime/components/chat/mobile/shell/shell.d.vue.ts +2 -2
- package/dist/runtime/components/chat/mobile/shell/shell.vue.d.ts +2 -2
- package/dist/runtime/components/chat/topbar.d.vue.ts +2 -2
- package/dist/runtime/components/chat/topbar.vue +29 -11
- package/dist/runtime/components/chat/topbar.vue.d.ts +2 -2
- package/dist/runtime/composables/usePdf.js +2 -2
- package/dist/runtime/embed/components/ChatEmbedInner.vue +14 -4
- package/dist/runtime/server/utils/conversation-agent-turn.js +4 -0
- package/dist/runtime/utils/conversation-search.d.ts +8 -0
- package/dist/runtime/utils/conversation-search.js +11 -0
- package/dist/runtime/utils/conversation-title.d.ts +1 -0
- package/dist/runtime/utils/conversation-title.js +6 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { useMagicKeys, useMutationObserver } from "@vueuse/core";
|
|
3
|
+
import { resolveConversationRenameTitle } from "../../utils/conversation-title";
|
|
3
4
|
import { capitalizeFirst } from "../../utils/string";
|
|
4
5
|
const props = defineProps({
|
|
5
6
|
conversation: { type: null, required: true },
|
|
@@ -7,13 +8,15 @@ const props = defineProps({
|
|
|
7
8
|
userImage: { type: String, required: false }
|
|
8
9
|
});
|
|
9
10
|
const emit = defineEmits(["delete", "rename", "duplicate", "export"]);
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const justStartedEditing = ref(false);
|
|
11
|
+
const isRenamingInline = ref(false);
|
|
12
|
+
const renameTitle = ref("");
|
|
13
|
+
const renameInputRef = ref(null);
|
|
14
14
|
const showDeleteModal = ref(false);
|
|
15
15
|
const { escape } = useMagicKeys();
|
|
16
16
|
watch(() => escape?.value, (pressed) => {
|
|
17
|
+
if (pressed && isRenamingInline.value) {
|
|
18
|
+
cancelRename();
|
|
19
|
+
}
|
|
17
20
|
if (pressed && showDeleteModal.value) {
|
|
18
21
|
showDeleteModal.value = false;
|
|
19
22
|
}
|
|
@@ -31,48 +34,63 @@ useMutationObserver(menuButtonRef, (mutations) => {
|
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
}, { attributes: true });
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
justStartedEditing.value = false;
|
|
43
|
-
}, 100);
|
|
44
|
-
});
|
|
37
|
+
const conversationPath = computed(() => `/${props.conversation.id}`);
|
|
38
|
+
const conversationNavigationDelayMs = 250;
|
|
39
|
+
let conversationNavigationTimer = null;
|
|
40
|
+
function clearPendingConversationNavigation() {
|
|
41
|
+
if (conversationNavigationTimer === null)
|
|
42
|
+
return;
|
|
43
|
+
clearTimeout(conversationNavigationTimer);
|
|
44
|
+
conversationNavigationTimer = null;
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
if (
|
|
46
|
+
function handleConversationClick(event) {
|
|
47
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
|
|
48
48
|
return;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
clearPendingConversationNavigation();
|
|
51
|
+
if (event.detail > 1)
|
|
52
|
+
return;
|
|
53
|
+
conversationNavigationTimer = setTimeout(() => {
|
|
54
|
+
navigateTo(conversationPath.value);
|
|
55
|
+
conversationNavigationTimer = null;
|
|
56
|
+
}, conversationNavigationDelayMs);
|
|
56
57
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
async function focusRenameInput() {
|
|
59
|
+
await nextTick();
|
|
60
|
+
renameInputRef.value?.focus();
|
|
61
|
+
renameInputRef.value?.select();
|
|
61
62
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
function startRename() {
|
|
64
|
+
isMenuOpen.value = false;
|
|
65
|
+
clearPendingConversationNavigation();
|
|
66
|
+
renameTitle.value = props.conversation.title;
|
|
67
|
+
isRenamingInline.value = true;
|
|
68
|
+
void focusRenameInput();
|
|
69
|
+
}
|
|
70
|
+
function handleConversationDoubleClick() {
|
|
71
|
+
clearPendingConversationNavigation();
|
|
72
|
+
startRename();
|
|
73
|
+
}
|
|
74
|
+
function cancelRename() {
|
|
75
|
+
isRenamingInline.value = false;
|
|
76
|
+
renameTitle.value = "";
|
|
77
|
+
}
|
|
78
|
+
function submitRename() {
|
|
79
|
+
if (!isRenamingInline.value)
|
|
80
|
+
return;
|
|
81
|
+
const title = resolveConversationRenameTitle(renameTitle.value, props.conversation.title);
|
|
82
|
+
if (!title) {
|
|
83
|
+
cancelRename();
|
|
84
|
+
return;
|
|
69
85
|
}
|
|
86
|
+
emit("rename", props.conversation.id, title);
|
|
87
|
+
cancelRename();
|
|
70
88
|
}
|
|
71
89
|
const menuItems = [
|
|
72
90
|
{
|
|
73
91
|
label: "Renomear",
|
|
74
92
|
icon: "i-ph-pencil-simple",
|
|
75
|
-
click: () =>
|
|
93
|
+
click: () => startRename()
|
|
76
94
|
},
|
|
77
95
|
{
|
|
78
96
|
label: "Duplicar",
|
|
@@ -91,11 +109,53 @@ const menuItems = [
|
|
|
91
109
|
click: () => showDeleteModal.value = true
|
|
92
110
|
}
|
|
93
111
|
];
|
|
112
|
+
onBeforeUnmount(clearPendingConversationNavigation);
|
|
94
113
|
</script>
|
|
95
114
|
|
|
96
115
|
<template>
|
|
97
|
-
<
|
|
98
|
-
|
|
116
|
+
<div
|
|
117
|
+
v-if="isRenamingInline"
|
|
118
|
+
px-12px
|
|
119
|
+
h-32px
|
|
120
|
+
rounded-8px
|
|
121
|
+
:class="active ? 'bg-neutral-200' : ''"
|
|
122
|
+
>
|
|
123
|
+
<div flex items-center justify-between gap-12px h-full>
|
|
124
|
+
<div v-if="userImage" w-16px h-16px rounded-full overflow-hidden flex-shrink-0>
|
|
125
|
+
<TelaAvatar
|
|
126
|
+
:image="userImage"
|
|
127
|
+
alt="Usuário"
|
|
128
|
+
size="xs"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div
|
|
133
|
+
flex-1 min-w-0 flex items-center
|
|
134
|
+
text-neutral-700
|
|
135
|
+
pb-1px
|
|
136
|
+
b-b=".5px solid #A3A3A3"
|
|
137
|
+
>
|
|
138
|
+
<input
|
|
139
|
+
ref="renameInputRef"
|
|
140
|
+
v-model="renameTitle"
|
|
141
|
+
type="text"
|
|
142
|
+
autocomplete="off"
|
|
143
|
+
aria-label="Nome da conversa"
|
|
144
|
+
w-full min-w-0
|
|
145
|
+
bg-transparent border-none outline-none
|
|
146
|
+
text-14px font-medium text-neutral-700 leading-18px tracking="-.15px"
|
|
147
|
+
p-0
|
|
148
|
+
@blur="submitRename"
|
|
149
|
+
@keydown.enter.prevent="submitRename"
|
|
150
|
+
@keydown.escape.prevent.stop="cancelRename"
|
|
151
|
+
>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<a
|
|
157
|
+
v-else
|
|
158
|
+
:href="conversationPath"
|
|
99
159
|
block
|
|
100
160
|
px-12px
|
|
101
161
|
h-32px
|
|
@@ -106,6 +166,8 @@ const menuItems = [
|
|
|
106
166
|
:class="[
|
|
107
167
|
active ? 'bg-neutral-200' : 'hover:bg-neutral-200/50'
|
|
108
168
|
]"
|
|
169
|
+
@click="handleConversationClick"
|
|
170
|
+
@dblclick.prevent.stop="handleConversationDoubleClick"
|
|
109
171
|
>
|
|
110
172
|
<div flex items-center justify-between gap-12px h-full>
|
|
111
173
|
<!-- User Avatar -->
|
|
@@ -118,29 +180,7 @@ const menuItems = [
|
|
|
118
180
|
</div>
|
|
119
181
|
|
|
120
182
|
<div flex-1 min-w-0 flex items-center>
|
|
121
|
-
<input
|
|
122
|
-
v-if="isEditing"
|
|
123
|
-
ref="inputRef"
|
|
124
|
-
v-model="editTitle"
|
|
125
|
-
type="text"
|
|
126
|
-
text-14px
|
|
127
|
-
font-medium
|
|
128
|
-
text-neutral-700
|
|
129
|
-
w-full
|
|
130
|
-
bg-white
|
|
131
|
-
border
|
|
132
|
-
border-neutral-300
|
|
133
|
-
rounded-4px
|
|
134
|
-
px-6px
|
|
135
|
-
py-2px
|
|
136
|
-
outline-none
|
|
137
|
-
focus:border-neutral-400
|
|
138
|
-
@keydown="handleKeydown"
|
|
139
|
-
@blur="saveTitle"
|
|
140
|
-
@click.prevent.stop
|
|
141
|
-
>
|
|
142
183
|
<span
|
|
143
|
-
v-else
|
|
144
184
|
text-14px
|
|
145
185
|
font-medium
|
|
146
186
|
text-neutral-700
|
|
@@ -151,7 +191,7 @@ const menuItems = [
|
|
|
151
191
|
</span>
|
|
152
192
|
</div>
|
|
153
193
|
|
|
154
|
-
<div
|
|
194
|
+
<div mt-2px flex items-center opacity-0 group-hover:opacity-100 transition-opacity :class="isMenuOpen && 'opacity-100'">
|
|
155
195
|
<TelaDropdownMenu :items="menuItems">
|
|
156
196
|
<button
|
|
157
197
|
ref="menuButtonRef"
|
|
@@ -168,7 +208,7 @@ const menuItems = [
|
|
|
168
208
|
</TelaDropdownMenu>
|
|
169
209
|
</div>
|
|
170
210
|
</div>
|
|
171
|
-
</
|
|
211
|
+
</a>
|
|
172
212
|
|
|
173
213
|
<TelaConfirmationModal
|
|
174
214
|
v-model:open="showDeleteModal"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { filterConversationsByTitle } from "../../utils/conversation-search";
|
|
2
3
|
import { groupConversationsByDate } from "../../utils/format-time";
|
|
3
4
|
import { exportConversationToMarkdown } from "../../utils/export-conversation";
|
|
4
5
|
import { conversationListSkeletonRows } from "../../utils/conversation-list-skeleton";
|
|
@@ -12,9 +13,17 @@ const chatApi = useChatApi();
|
|
|
12
13
|
const { deleteConversation, renameConversation, duplicateConversation } = useConversations();
|
|
13
14
|
const { setConversation } = useChat();
|
|
14
15
|
const router = useRouter();
|
|
16
|
+
const conversationSearch = ref("");
|
|
17
|
+
const filteredConversations = computed(
|
|
18
|
+
() => filterConversationsByTitle(props.conversations, conversationSearch.value, {
|
|
19
|
+
fallbackTitle: "Nova conversa",
|
|
20
|
+
shouldInclude: (conversation) => !conversation.title && conversation.id === props.currentId
|
|
21
|
+
})
|
|
22
|
+
);
|
|
15
23
|
const groupedConversations = computed(() => {
|
|
16
|
-
return groupConversationsByDate(
|
|
24
|
+
return groupConversationsByDate(filteredConversations.value);
|
|
17
25
|
});
|
|
26
|
+
const hasConversationSearchQuery = computed(() => conversationSearch.value.trim().length > 0);
|
|
18
27
|
function handleDelete(id) {
|
|
19
28
|
const route = useRoute();
|
|
20
29
|
if (route.params.id === id) {
|
|
@@ -46,7 +55,11 @@ async function handleExport(id) {
|
|
|
46
55
|
</script>
|
|
47
56
|
|
|
48
57
|
<template>
|
|
49
|
-
<div flex="~ col" gap-
|
|
58
|
+
<div flex="~ col" gap-12px>
|
|
59
|
+
<div px-8px>
|
|
60
|
+
<ChatConversationSearch v-model="conversationSearch" />
|
|
61
|
+
</div>
|
|
62
|
+
|
|
50
63
|
<div v-if="loading && conversations.length === 0" flex="~ col" gap-4px>
|
|
51
64
|
<div h-20px flex items-center pl-12px pr-4px mb-4px>
|
|
52
65
|
<TelaSkeleton rounded-4px bg-neutral-200 style="width: 68px; height: 12px;" />
|
|
@@ -63,9 +76,9 @@ async function handleExport(id) {
|
|
|
63
76
|
</div>
|
|
64
77
|
</div>
|
|
65
78
|
|
|
66
|
-
<div v-else-if="
|
|
79
|
+
<div v-else-if="filteredConversations.length === 0" p-16px text-center>
|
|
67
80
|
<p text-14px text-neutral-400>
|
|
68
|
-
Nenhuma conversa ainda
|
|
81
|
+
{{ hasConversationSearchQuery ? "Nenhuma conversa encontrada" : "Nenhuma conversa ainda" }}
|
|
69
82
|
</p>
|
|
70
83
|
</div>
|
|
71
84
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
placeholder?: string;
|
|
3
|
+
};
|
|
4
|
+
type __VLS_ModelProps = {
|
|
5
|
+
modelValue: string;
|
|
6
|
+
};
|
|
7
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:modelValue": (value: string) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
|
+
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
12
|
+
}>, {
|
|
13
|
+
placeholder: string;
|
|
14
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
placeholder: { type: String, required: false, default: "Buscar conversas" }
|
|
4
|
+
});
|
|
5
|
+
const searchQuery = defineModel({ type: String, ...{ required: true } });
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<TelaInput
|
|
10
|
+
v-model="searchQuery"
|
|
11
|
+
icon="i-ph-magnifying-glass-bold"
|
|
12
|
+
:placeholder="placeholder"
|
|
13
|
+
show-clear-button
|
|
14
|
+
autocomplete="off"
|
|
15
|
+
/>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
placeholder?: string;
|
|
3
|
+
};
|
|
4
|
+
type __VLS_ModelProps = {
|
|
5
|
+
modelValue: string;
|
|
6
|
+
};
|
|
7
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:modelValue": (value: string) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
|
+
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
12
|
+
}>, {
|
|
13
|
+
placeholder: string;
|
|
14
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { formatRelativeTime } from "../../../../utils/format-time";
|
|
3
3
|
import { conversationListSkeletonRows } from "../../../../utils/conversation-list-skeleton";
|
|
4
|
+
import { filterConversationsByTitle } from "../../../../utils/conversation-search";
|
|
4
5
|
const props = defineProps({
|
|
5
6
|
open: { type: Boolean, required: true },
|
|
6
7
|
conversations: { type: null, required: true },
|
|
@@ -15,6 +16,14 @@ const initial = computed(() => {
|
|
|
15
16
|
const source = props.userName ?? props.userEmail ?? "U";
|
|
16
17
|
return source.charAt(0).toUpperCase();
|
|
17
18
|
});
|
|
19
|
+
const conversationSearch = ref("");
|
|
20
|
+
const filteredConversations = computed(
|
|
21
|
+
() => filterConversationsByTitle(props.conversations, conversationSearch.value, {
|
|
22
|
+
fallbackTitle: "Nova conversa",
|
|
23
|
+
shouldInclude: (conversation) => !conversation.title && conversation.id === props.activeId
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
const hasConversationSearchQuery = computed(() => conversationSearch.value.trim().length > 0);
|
|
18
27
|
function handleSelect(id) {
|
|
19
28
|
emit("select", id);
|
|
20
29
|
emit("close");
|
|
@@ -74,7 +83,7 @@ function handleNew() {
|
|
|
74
83
|
</button>
|
|
75
84
|
</div>
|
|
76
85
|
|
|
77
|
-
<div p-12px>
|
|
86
|
+
<div p-12px flex="~ col" gap-8px>
|
|
78
87
|
<button
|
|
79
88
|
flex items-center justify-center gap-6px
|
|
80
89
|
w-full px-12px py-10px rounded-10px
|
|
@@ -88,6 +97,7 @@ function handleNew() {
|
|
|
88
97
|
Nova conversa
|
|
89
98
|
</span>
|
|
90
99
|
</button>
|
|
100
|
+
<ChatConversationSearch v-model="conversationSearch" />
|
|
91
101
|
</div>
|
|
92
102
|
|
|
93
103
|
<div px-14px pt-4px pb-4px>
|
|
@@ -95,7 +105,7 @@ function handleNew() {
|
|
|
95
105
|
text-12px font-medium uppercase text-neutral-400
|
|
96
106
|
style="letter-spacing: 0.5px"
|
|
97
107
|
>
|
|
98
|
-
Recentes
|
|
108
|
+
{{ hasConversationSearchQuery ? "Resultados" : "Recentes" }}
|
|
99
109
|
</span>
|
|
100
110
|
</div>
|
|
101
111
|
|
|
@@ -109,14 +119,14 @@ function handleNew() {
|
|
|
109
119
|
<TelaSkeleton rounded-6px bg-neutral-200 :style="{ width: row.width, height: '13px' }" />
|
|
110
120
|
</div>
|
|
111
121
|
</div>
|
|
112
|
-
<div v-else-if="
|
|
122
|
+
<div v-else-if="filteredConversations.length === 0" p-16px text-center>
|
|
113
123
|
<p text-14px text-neutral-400>
|
|
114
|
-
Nenhuma conversa ainda
|
|
124
|
+
{{ hasConversationSearchQuery ? "Nenhuma conversa encontrada" : "Nenhuma conversa ainda" }}
|
|
115
125
|
</p>
|
|
116
126
|
</div>
|
|
117
127
|
<div v-else flex="~ col" gap-1px>
|
|
118
128
|
<button
|
|
119
|
-
v-for="conv in
|
|
129
|
+
v-for="conv in filteredConversations"
|
|
120
130
|
:key="conv.id"
|
|
121
131
|
flex="~ col" gap-2px
|
|
122
132
|
w-full px-12px py-10px rounded-10px text-left
|
|
@@ -53,7 +53,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
53
53
|
rename: () => any;
|
|
54
54
|
duplicate: () => any;
|
|
55
55
|
export: () => any;
|
|
56
|
-
saveTitle: () => any;
|
|
57
56
|
send: (content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
|
|
58
57
|
type: "file";
|
|
59
58
|
name: string;
|
|
@@ -68,6 +67,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
68
67
|
"update:model": (value: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any;
|
|
69
68
|
settings: () => any;
|
|
70
69
|
"update:editTitle": (value: string) => any;
|
|
70
|
+
saveTitle: () => any;
|
|
71
71
|
cancelEditing: () => any;
|
|
72
72
|
copyLink: () => any;
|
|
73
73
|
debug: () => any;
|
|
@@ -85,7 +85,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
85
85
|
onRename?: (() => any) | undefined;
|
|
86
86
|
onDuplicate?: (() => any) | undefined;
|
|
87
87
|
onExport?: (() => any) | undefined;
|
|
88
|
-
onSaveTitle?: (() => any) | undefined;
|
|
89
88
|
onSend?: ((content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
|
|
90
89
|
type: "file";
|
|
91
90
|
name: string;
|
|
@@ -100,6 +99,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
100
99
|
"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;
|
|
101
100
|
onSettings?: (() => any) | undefined;
|
|
102
101
|
"onUpdate:editTitle"?: ((value: string) => any) | undefined;
|
|
102
|
+
onSaveTitle?: (() => any) | undefined;
|
|
103
103
|
onCancelEditing?: (() => any) | undefined;
|
|
104
104
|
onCopyLink?: (() => any) | undefined;
|
|
105
105
|
onDebug?: (() => any) | undefined;
|
|
@@ -53,7 +53,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
53
53
|
rename: () => any;
|
|
54
54
|
duplicate: () => any;
|
|
55
55
|
export: () => any;
|
|
56
|
-
saveTitle: () => any;
|
|
57
56
|
send: (content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
|
|
58
57
|
type: "file";
|
|
59
58
|
name: string;
|
|
@@ -68,6 +67,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
68
67
|
"update:model": (value: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6") => any;
|
|
69
68
|
settings: () => any;
|
|
70
69
|
"update:editTitle": (value: string) => any;
|
|
70
|
+
saveTitle: () => any;
|
|
71
71
|
cancelEditing: () => any;
|
|
72
72
|
copyLink: () => any;
|
|
73
73
|
debug: () => any;
|
|
@@ -85,7 +85,6 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
85
85
|
onRename?: (() => any) | undefined;
|
|
86
86
|
onDuplicate?: (() => any) | undefined;
|
|
87
87
|
onExport?: (() => any) | undefined;
|
|
88
|
-
onSaveTitle?: (() => any) | undefined;
|
|
89
88
|
onSend?: ((content: string, files: FileUploadInput[], model?: "claude-sonnet-4-5" | "claude-sonnet-4-6" | "claude-haiku-4-5" | "claude-opus-4-5" | "claude-opus-4-6" | undefined, agentInputs?: ({
|
|
90
89
|
type: "file";
|
|
91
90
|
name: string;
|
|
@@ -100,6 +99,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
100
99
|
"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;
|
|
101
100
|
onSettings?: (() => any) | undefined;
|
|
102
101
|
"onUpdate:editTitle"?: ((value: string) => any) | undefined;
|
|
102
|
+
onSaveTitle?: (() => any) | undefined;
|
|
103
103
|
onCancelEditing?: (() => any) | undefined;
|
|
104
104
|
onCopyLink?: (() => any) | undefined;
|
|
105
105
|
onDebug?: (() => any) | undefined;
|
|
@@ -19,9 +19,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
19
19
|
rename: () => any;
|
|
20
20
|
duplicate: () => any;
|
|
21
21
|
export: () => any;
|
|
22
|
-
saveTitle: () => any;
|
|
23
22
|
"update:isEditing": (value: boolean) => any;
|
|
24
23
|
"update:editTitle": (value: string) => any;
|
|
24
|
+
saveTitle: () => any;
|
|
25
25
|
cancelEditing: () => any;
|
|
26
26
|
copyLink: () => any;
|
|
27
27
|
debug: () => any;
|
|
@@ -31,9 +31,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
31
31
|
onRename?: (() => any) | undefined;
|
|
32
32
|
onDuplicate?: (() => any) | undefined;
|
|
33
33
|
onExport?: (() => any) | undefined;
|
|
34
|
-
onSaveTitle?: (() => any) | undefined;
|
|
35
34
|
"onUpdate:isEditing"?: ((value: boolean) => any) | undefined;
|
|
36
35
|
"onUpdate:editTitle"?: ((value: string) => any) | undefined;
|
|
36
|
+
onSaveTitle?: (() => any) | undefined;
|
|
37
37
|
onCancelEditing?: (() => any) | undefined;
|
|
38
38
|
onCopyLink?: (() => any) | undefined;
|
|
39
39
|
onDebug?: (() => any) | undefined;
|
|
@@ -19,6 +19,7 @@ const props = defineProps({
|
|
|
19
19
|
const emit = defineEmits(["update:isEditing", "update:editTitle", "saveTitle", "cancelEditing", "rename", "duplicate", "export", "copyLink", "debug", "delete", "tabChange"]);
|
|
20
20
|
const titleInputRef = ref(null);
|
|
21
21
|
const currentTab = computed(() => props.activeTab ?? "chat");
|
|
22
|
+
const titleInputWidth = computed(() => `${Math.max(props.editTitle.length + 2, 8)}ch`);
|
|
22
23
|
const headerMenuItems = computed(() => {
|
|
23
24
|
const items = [
|
|
24
25
|
{
|
|
@@ -87,19 +88,36 @@ watch(() => props.isEditing, (editing) => {
|
|
|
87
88
|
:style="{ left: 'var(--chat-panel-offset)' }"
|
|
88
89
|
>
|
|
89
90
|
<div flex="~ col 1" gap-4px min-w-0>
|
|
90
|
-
<
|
|
91
|
+
<div
|
|
91
92
|
v-if="isEditing"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
flex items-center
|
|
94
|
+
text-primary
|
|
95
|
+
w-fit max-w-full
|
|
96
|
+
pb-1px
|
|
97
|
+
b-b=".5px solid #A3A3A3"
|
|
98
|
+
:style="{ width: titleInputWidth }"
|
|
99
|
+
>
|
|
100
|
+
<input
|
|
101
|
+
ref="titleInputRef"
|
|
102
|
+
:value="editTitle"
|
|
103
|
+
type="text"
|
|
104
|
+
autocomplete="off"
|
|
105
|
+
aria-label="Nome da conversa"
|
|
106
|
+
w-full min-w-0 max-w-full
|
|
107
|
+
bg-transparent border-none outline-none
|
|
108
|
+
text-16px font-580 text-primary leading-20px tracking="-.2px"
|
|
109
|
+
p-0
|
|
110
|
+
@input="emit('update:editTitle', $event.target.value)"
|
|
111
|
+
@keydown="handleTitleKeydown"
|
|
112
|
+
@blur="emit('saveTitle')"
|
|
113
|
+
>
|
|
114
|
+
</div>
|
|
115
|
+
<p
|
|
116
|
+
v-else
|
|
117
|
+
text-16px font-580 text-primary leading-20px tracking="-.2px"
|
|
118
|
+
truncate cursor-text
|
|
119
|
+
@dblclick.prevent.stop="emit('rename')"
|
|
101
120
|
>
|
|
102
|
-
<p v-else text-16px font-580 text-primary leading-20px tracking="-.2px" truncate>
|
|
103
121
|
{{ capitalizeFirst(title) }}<span v-if="generatingTitle" class="typing-cursor">|</span>
|
|
104
122
|
</p>
|
|
105
123
|
|
|
@@ -19,9 +19,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
19
19
|
rename: () => any;
|
|
20
20
|
duplicate: () => any;
|
|
21
21
|
export: () => any;
|
|
22
|
-
saveTitle: () => any;
|
|
23
22
|
"update:isEditing": (value: boolean) => any;
|
|
24
23
|
"update:editTitle": (value: string) => any;
|
|
24
|
+
saveTitle: () => any;
|
|
25
25
|
cancelEditing: () => any;
|
|
26
26
|
copyLink: () => any;
|
|
27
27
|
debug: () => any;
|
|
@@ -31,9 +31,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
31
31
|
onRename?: (() => any) | undefined;
|
|
32
32
|
onDuplicate?: (() => any) | undefined;
|
|
33
33
|
onExport?: (() => any) | undefined;
|
|
34
|
-
onSaveTitle?: (() => any) | undefined;
|
|
35
34
|
"onUpdate:isEditing"?: ((value: boolean) => any) | undefined;
|
|
36
35
|
"onUpdate:editTitle"?: ((value: string) => any) | undefined;
|
|
36
|
+
onSaveTitle?: (() => any) | undefined;
|
|
37
37
|
onCancelEditing?: (() => any) | undefined;
|
|
38
38
|
onCopyLink?: (() => any) | undefined;
|
|
39
39
|
onDebug?: (() => any) | undefined;
|
|
@@ -5,8 +5,8 @@ async function loadPdfjs() {
|
|
|
5
5
|
if (!import.meta.client)
|
|
6
6
|
return null;
|
|
7
7
|
if (!pdfjsLibPromise) {
|
|
8
|
-
pdfjsLibPromise = import("pdfjs-dist").then((lib) => {
|
|
9
|
-
lib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${lib.version}/build/pdf.worker.min.mjs`;
|
|
8
|
+
pdfjsLibPromise = import("pdfjs-dist/legacy/build/pdf.mjs").then((lib) => {
|
|
9
|
+
lib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${lib.version}/legacy/build/pdf.worker.min.mjs`;
|
|
10
10
|
return lib;
|
|
11
11
|
});
|
|
12
12
|
}
|
|
@@ -3,6 +3,7 @@ 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";
|
|
6
7
|
import ChatMobileFilePreview from "../../components/chat/mobile/files/file-preview.vue";
|
|
7
8
|
import ChatMobileShell from "../../components/chat/mobile/shell/shell.vue";
|
|
8
9
|
import { conversationListSkeletonRows } from "../../utils/conversation-list-skeleton";
|
|
@@ -51,6 +52,7 @@ const resolvedFeatures = computed(() => resolveChatFeatures(props.features));
|
|
|
51
52
|
const dropZoneRef = ref(null);
|
|
52
53
|
const activeTab = ref("chat");
|
|
53
54
|
const messageInputRef = ref(null);
|
|
55
|
+
const conversationSearch = ref("");
|
|
54
56
|
const breakpoints = useBreakpoints({ mobile: CHAT_MOBILE_BREAKPOINT });
|
|
55
57
|
const isMobile = breakpoints.smaller("mobile");
|
|
56
58
|
const drawerOpen = ref(false);
|
|
@@ -106,6 +108,13 @@ const visibleTelaAgentInputSchemaLoading = computed(() => !hasMessages.value &&
|
|
|
106
108
|
const visibleConversations = computed(
|
|
107
109
|
() => conversations.value.filter((c) => c.title || c.id === activeConversationId.value)
|
|
108
110
|
);
|
|
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);
|
|
109
118
|
function syncConversationId(nextConversationId) {
|
|
110
119
|
activeConversationId.value = normalizeConversationId(nextConversationId);
|
|
111
120
|
}
|
|
@@ -408,7 +417,7 @@ async function handleDelete() {
|
|
|
408
417
|
w-240px h-full flex="~ col" flex-shrink-0
|
|
409
418
|
bg-neutral-50 b-r=".5px neutral-200"
|
|
410
419
|
>
|
|
411
|
-
<div px-12px pt-16px pb-8px>
|
|
420
|
+
<div px-12px pt-16px pb-8px flex="~ col" gap-8px>
|
|
412
421
|
<button
|
|
413
422
|
flex items-center justify-center gap-6px
|
|
414
423
|
h-32px px-12px w-full
|
|
@@ -421,6 +430,7 @@ async function handleDelete() {
|
|
|
421
430
|
Nova conversa
|
|
422
431
|
</span>
|
|
423
432
|
</button>
|
|
433
|
+
<ChatConversationSearch v-model="conversationSearch" />
|
|
424
434
|
</div>
|
|
425
435
|
|
|
426
436
|
<div flex-1 overflow-y-auto px-4px>
|
|
@@ -433,14 +443,14 @@ async function handleDelete() {
|
|
|
433
443
|
<TelaSkeleton rounded-6px bg-neutral-200 :style="{ width: row.width, height: '13px' }" />
|
|
434
444
|
</div>
|
|
435
445
|
</div>
|
|
436
|
-
<div v-else-if="
|
|
446
|
+
<div v-else-if="searchFilteredConversations.length === 0" p-16px text-center>
|
|
437
447
|
<p text-14px text-neutral-400>
|
|
438
|
-
Nenhuma conversa ainda
|
|
448
|
+
{{ hasConversationSearchQuery ? "Nenhuma conversa encontrada" : "Nenhuma conversa ainda" }}
|
|
439
449
|
</p>
|
|
440
450
|
</div>
|
|
441
451
|
<div v-else flex="~ col" gap-1px px-8px py-8px>
|
|
442
452
|
<button
|
|
443
|
-
v-for="conv in
|
|
453
|
+
v-for="conv in searchFilteredConversations"
|
|
444
454
|
:key="conv.id"
|
|
445
455
|
flex items-center gap-8px h-32px px-8px rounded-8px
|
|
446
456
|
w-full text-left truncate transition-colors cursor-pointer
|
|
@@ -161,6 +161,10 @@ export async function resolveDefaultAgentEnvironmentVariables(event, context, op
|
|
|
161
161
|
if (resolveChatNuxtFeatures(useRuntimeConfig()).credentials) {
|
|
162
162
|
try {
|
|
163
163
|
credentialVariables = await resolveWorkspaceCredentials(event, context.workspaceId);
|
|
164
|
+
logger.info({
|
|
165
|
+
...logContext,
|
|
166
|
+
credentialKeys: Object.keys(credentialVariables)
|
|
167
|
+
}, "Workspace credentials loaded for agent API");
|
|
164
168
|
} catch (error) {
|
|
165
169
|
logger.warn({ ...logContext, error }, options.credentialsFailureMessage);
|
|
166
170
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ConversationTitleSearchItem {
|
|
2
|
+
title?: string | null;
|
|
3
|
+
}
|
|
4
|
+
export interface ConversationTitleSearchOptions<T extends ConversationTitleSearchItem> {
|
|
5
|
+
fallbackTitle?: string;
|
|
6
|
+
shouldInclude?: (conversation: T) => boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function filterConversationsByTitle<T extends ConversationTitleSearchItem>(conversations: readonly T[], searchQuery: string, options?: ConversationTitleSearchOptions<T>): T[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function filterConversationsByTitle(conversations, searchQuery, options = {}) {
|
|
2
|
+
const query = searchQuery.toLowerCase().trim();
|
|
3
|
+
if (!query)
|
|
4
|
+
return [...conversations];
|
|
5
|
+
return conversations.filter((conversation) => {
|
|
6
|
+
if (options.shouldInclude?.(conversation))
|
|
7
|
+
return true;
|
|
8
|
+
const title = conversation.title ?? options.fallbackTitle;
|
|
9
|
+
return title?.toLowerCase().includes(query) ?? false;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveConversationRenameTitle(nextTitle: string, currentTitle: string): string | null;
|