@meistrari/chat-nuxt 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -4
- package/dist/module.d.mts +5 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +74 -27
- package/dist/runtime/components/chat/conversation-list.d.vue.ts +1 -0
- package/dist/runtime/components/chat/conversation-list.vue +22 -2
- package/dist/runtime/components/chat/conversation-list.vue.d.ts +1 -0
- package/dist/runtime/components/chat/message-list.d.vue.ts +1 -0
- package/dist/runtime/components/chat/message-list.vue +23 -2
- package/dist/runtime/components/chat/message-list.vue.d.ts +1 -0
- package/dist/runtime/composables/useChat.d.ts +2 -1
- package/dist/runtime/composables/useChat.js +32 -3
- package/dist/runtime/composables/useChatApi.js +2 -1
- package/dist/runtime/composables/useConversations.d.ts +6 -3
- package/dist/runtime/composables/useConversations.js +114 -12
- package/dist/runtime/composables/useEmbedConfig.d.ts +11 -6
- package/dist/runtime/composables/useEmbedConfig.js +35 -7
- package/dist/runtime/composables/useIDB.js +4 -0
- package/dist/runtime/composables/usePdf.d.ts +840 -1
- package/dist/runtime/composables/usePdf.js +21 -3
- package/dist/runtime/composables/useWorkspaceContextFiles.d.ts +3 -3
- package/dist/runtime/composables/useWorkspaceContextFiles.js +19 -10
- package/dist/runtime/composables/useWorkspaceExternalSkills.d.ts +1 -1
- package/dist/runtime/composables/useWorkspaceExternalSkills.js +5 -6
- package/dist/runtime/composables/useWorkspacePersonalization.d.ts +1 -1
- package/dist/runtime/composables/useWorkspacePersonalization.js +4 -5
- package/dist/runtime/composables/useWorkspaceSettings.js +11 -12
- package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +25 -0
- package/dist/runtime/embed/components/ChatConfigurationModal.vue +373 -0
- package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +25 -0
- package/dist/runtime/embed/components/ChatEmbed.vue +12 -1
- package/dist/runtime/embed/components/ChatEmbedInner.vue +35 -19
- package/dist/runtime/embed/components/configuration/ChatConfigurationContextFilesTab.d.vue.ts +22 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationContextFilesTab.vue +133 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationContextFilesTab.vue.d.ts +22 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationCredentialsTab.d.vue.ts +10 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationCredentialsTab.vue +220 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationCredentialsTab.vue.d.ts +10 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationExternalSkillsTab.d.vue.ts +32 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationExternalSkillsTab.vue +222 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationExternalSkillsTab.vue.d.ts +32 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationKnowledgeSourcesTab.d.vue.ts +18 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationKnowledgeSourcesTab.vue +142 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationKnowledgeSourcesTab.vue.d.ts +18 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationPersonalizationTab.d.vue.ts +10 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationPersonalizationTab.vue +37 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationPersonalizationTab.vue.d.ts +10 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationTelaToolsTab.d.vue.ts +20 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationTelaToolsTab.vue +146 -0
- package/dist/runtime/embed/components/configuration/ChatConfigurationTelaToolsTab.vue.d.ts +20 -0
- package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +45 -12
- package/dist/runtime/types/chat.d.ts +2 -1
- package/dist/runtime/types/workspace-settings.d.ts +3 -0
- package/dist/runtime/types/workspace-settings.js +0 -0
- package/dist/runtime/utils/conversation-list-skeleton.d.ts +31 -0
- package/dist/runtime/utils/conversation-list-skeleton.js +12 -0
- package/dist/runtime/utils/file.js +42 -42
- package/dist/types.d.mts +2 -0
- package/package.json +23 -27
- package/dist/runtime/composables/PageNavigation.d.ts +0 -13
- package/dist/runtime/composables/PageNavigation.js +0 -18
- /package/dist/runtime/components/{pdf-preview.d.vue.ts → pdf-preview.client.d.vue.ts} +0 -0
- /package/dist/runtime/components/{pdf-preview.vue → pdf-preview.client.vue} +0 -0
- /package/dist/runtime/components/{pdf-preview.vue.d.ts → pdf-preview.client.vue.d.ts} +0 -0
- /package/dist/runtime/components/{pdf-viewer.d.vue.ts → pdf-viewer.client.d.vue.ts} +0 -0
- /package/dist/runtime/components/{pdf-viewer.vue → pdf-viewer.client.vue} +0 -0
- /package/dist/runtime/components/{pdf-viewer.vue.d.ts → pdf-viewer.client.vue.d.ts} +0 -0
- /package/dist/runtime/public/{github.svg → assets/github.svg} +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DeepReadonly } from 'vue';
|
|
2
|
+
import type { WorkspaceSettings, WorkspaceSettingsUpdate } from '../../composables/useWorkspaceSettings.js';
|
|
3
|
+
export type ChatConfigurationTab = 'personalization' | 'context-files' | 'knowledge-sources' | 'tela-tools' | 'external-skills' | 'credentials';
|
|
4
|
+
export type ChatConfigurationSavePayload = {
|
|
5
|
+
updates: WorkspaceSettingsUpdate;
|
|
6
|
+
onSaved: () => void;
|
|
7
|
+
};
|
|
8
|
+
type __VLS_Props = {
|
|
9
|
+
open: boolean;
|
|
10
|
+
settings?: DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null;
|
|
11
|
+
initialTab?: ChatConfigurationTab;
|
|
12
|
+
saving?: boolean;
|
|
13
|
+
};
|
|
14
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
15
|
+
save: (payload: ChatConfigurationSavePayload) => any;
|
|
16
|
+
"update:open": (value: boolean) => any;
|
|
17
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
18
|
+
onSave?: ((payload: ChatConfigurationSavePayload) => any) | undefined;
|
|
19
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
20
|
+
}>, {
|
|
21
|
+
initialTab: ChatConfigurationTab;
|
|
22
|
+
saving: boolean;
|
|
23
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
|
+
declare const _default: typeof __VLS_export;
|
|
25
|
+
export default _default;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { provideEmbedConfig, useEmbedConfig } from "../../composables/useEmbedConfig";
|
|
2
3
|
import ChatEmbedInner from "./ChatEmbedInner.vue";
|
|
3
4
|
const props = defineProps({
|
|
4
5
|
workspaceId: { type: String, required: true },
|
|
@@ -10,7 +11,17 @@ const props = defineProps({
|
|
|
10
11
|
features: { type: Object, required: false }
|
|
11
12
|
});
|
|
12
13
|
defineEmits(["update:conversationId"]);
|
|
13
|
-
|
|
14
|
+
const parentEmbedConfig = useEmbedConfig();
|
|
15
|
+
if (import.meta.dev && parentEmbedConfig) {
|
|
16
|
+
watchEffect(() => {
|
|
17
|
+
const parentWorkspaceId = parentEmbedConfig.workspaceId.value.trim();
|
|
18
|
+
const propWorkspaceId = props.workspaceId.trim();
|
|
19
|
+
if (parentWorkspaceId && propWorkspaceId && parentWorkspaceId !== propWorkspaceId) {
|
|
20
|
+
console.warn(`[MeistrariChatEmbed] workspaceId prop (${propWorkspaceId}) differs from parent embed config (${parentWorkspaceId}); using prop value for this embed.`);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
provideEmbedConfig(props, { syncGlobal: !parentEmbedConfig });
|
|
14
25
|
</script>
|
|
15
26
|
|
|
16
27
|
<template>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { exportConversationToMarkdown } from "../../utils/export-conversation";
|
|
3
|
+
import { conversationListSkeletonRows } from "../../utils/conversation-list-skeleton";
|
|
3
4
|
import {
|
|
4
5
|
normalizeConversationId,
|
|
5
6
|
resolveChatFeatures,
|
|
@@ -18,9 +19,9 @@ const embedConfig = useEmbedConfig();
|
|
|
18
19
|
const { user: authUser } = useTelaApplicationAuth();
|
|
19
20
|
const { getMember } = useWorkspaceMembers();
|
|
20
21
|
const statusToast = useStatusToast();
|
|
21
|
-
const { conversations, fetchConversations, createConversation, deleteConversation, renameConversation, duplicateConversation } = useConversations();
|
|
22
|
+
const { conversations, loading: conversationsLoading, fetchConversations, createConversation, deleteConversation, renameConversation, duplicateConversation, updateConversationInList } = useConversations();
|
|
22
23
|
const { settings: workspaceSettingsState, fetchSettings } = useWorkspaceSettings();
|
|
23
|
-
const effectiveWorkspaceSettings = computed(() => embedConfig?.workspaceSettings.value
|
|
24
|
+
const effectiveWorkspaceSettings = computed(() => embedConfig?.workspaceSettings.value !== void 0 ? embedConfig.workspaceSettings.value : workspaceSettingsState.value);
|
|
24
25
|
const { closePanel: closeFilesPanel } = useConversationFilesPanel();
|
|
25
26
|
const activeConversationId = ref(
|
|
26
27
|
normalizeConversationId(props.conversationId) ?? normalizeConversationId(props.initialConversationId)
|
|
@@ -45,6 +46,7 @@ const {
|
|
|
45
46
|
animatedTitle,
|
|
46
47
|
hasPendingMessage,
|
|
47
48
|
loadConversation,
|
|
49
|
+
setConversation,
|
|
48
50
|
sendMessage,
|
|
49
51
|
retryMessage,
|
|
50
52
|
cancelConversation,
|
|
@@ -138,14 +140,15 @@ watch(() => props.conversationId, async (nextConversationId) => {
|
|
|
138
140
|
await ensureConversationLoaded(normalizedConversationId);
|
|
139
141
|
});
|
|
140
142
|
onMounted(async () => {
|
|
141
|
-
|
|
143
|
+
void fetchConversations();
|
|
142
144
|
if (!effectiveWorkspaceSettings.value) {
|
|
143
|
-
|
|
145
|
+
void fetchSettings();
|
|
144
146
|
}
|
|
145
|
-
await Promise.allSettled(pendingTasks);
|
|
146
147
|
isReady.value = true;
|
|
147
148
|
if (activeConversationId.value) {
|
|
148
149
|
await ensureConversationLoaded(activeConversationId.value);
|
|
150
|
+
} else {
|
|
151
|
+
resetConversation(false);
|
|
149
152
|
}
|
|
150
153
|
});
|
|
151
154
|
const MAX_DISMISSED_CHAT_IDS = 100;
|
|
@@ -200,7 +203,7 @@ const displayTitle = computed(() => {
|
|
|
200
203
|
});
|
|
201
204
|
watch(
|
|
202
205
|
() => [currentConversation.value?.id ?? null, currentConversation.value?.title ?? null],
|
|
203
|
-
([conversationId, conversationTitle], [previousConversationId]) => {
|
|
206
|
+
([conversationId, conversationTitle], [previousConversationId, previousTitle]) => {
|
|
204
207
|
if (conversationId !== previousConversationId) {
|
|
205
208
|
optimisticTitle.value = null;
|
|
206
209
|
return;
|
|
@@ -208,6 +211,9 @@ watch(
|
|
|
208
211
|
if (optimisticTitle.value !== null && conversationTitle === optimisticTitle.value) {
|
|
209
212
|
optimisticTitle.value = null;
|
|
210
213
|
}
|
|
214
|
+
if (conversationTitle && conversationTitle !== previousTitle && currentConversation.value) {
|
|
215
|
+
updateConversationInList(currentConversation.value);
|
|
216
|
+
}
|
|
211
217
|
}
|
|
212
218
|
);
|
|
213
219
|
const currentUser = computed(() => props.user ?? authUser.value ?? null);
|
|
@@ -218,22 +224,23 @@ const conversationCreator = computed(
|
|
|
218
224
|
})
|
|
219
225
|
);
|
|
220
226
|
async function handleSend(content, files, model) {
|
|
227
|
+
let createdConversationId = null;
|
|
221
228
|
if (!activeConversationId.value || !currentConversation.value) {
|
|
222
229
|
const conversation = await createConversation({
|
|
223
230
|
model: model ?? selectedModel.value
|
|
224
231
|
});
|
|
225
232
|
if (!conversation)
|
|
226
233
|
return;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (!currentConversation.value) {
|
|
230
|
-
statusToast.update({ text: "Erro ao carregar conversa", icon: "i-ph-warning" });
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
234
|
+
setConversation(conversation);
|
|
235
|
+
createdConversationId = conversation.id;
|
|
233
236
|
}
|
|
234
237
|
if (!currentConversation.value)
|
|
235
238
|
return;
|
|
236
|
-
|
|
239
|
+
const sendPromise = sendMessage(content, files.length > 0 ? files : void 0, model);
|
|
240
|
+
if (createdConversationId) {
|
|
241
|
+
updateConversationId(createdConversationId);
|
|
242
|
+
}
|
|
243
|
+
await sendPromise;
|
|
237
244
|
}
|
|
238
245
|
async function handleRetry(messageId) {
|
|
239
246
|
await retryMessage(messageId);
|
|
@@ -295,8 +302,8 @@ async function handleDuplicate() {
|
|
|
295
302
|
return;
|
|
296
303
|
const duplicated = await duplicateConversation(currentConversation.value.id);
|
|
297
304
|
if (duplicated) {
|
|
305
|
+
setConversation(duplicated);
|
|
298
306
|
updateConversationId(duplicated.id);
|
|
299
|
-
await ensureConversationLoaded(duplicated.id);
|
|
300
307
|
}
|
|
301
308
|
}
|
|
302
309
|
async function handleExport() {
|
|
@@ -322,9 +329,6 @@ async function handleDelete() {
|
|
|
322
329
|
await handleNewConversation();
|
|
323
330
|
}
|
|
324
331
|
}
|
|
325
|
-
onBeforeUnmount(() => {
|
|
326
|
-
clearConversation();
|
|
327
|
-
});
|
|
328
332
|
</script>
|
|
329
333
|
|
|
330
334
|
<template>
|
|
@@ -351,7 +355,16 @@ onBeforeUnmount(() => {
|
|
|
351
355
|
</div>
|
|
352
356
|
|
|
353
357
|
<div flex-1 overflow-y-auto px-4px>
|
|
354
|
-
<div v-if="visibleConversations.length === 0"
|
|
358
|
+
<div v-if="conversationsLoading && visibleConversations.length === 0" flex="~ col" gap-1px px-8px py-8px>
|
|
359
|
+
<div
|
|
360
|
+
v-for="row in conversationListSkeletonRows"
|
|
361
|
+
:key="row.id"
|
|
362
|
+
flex items-center h-32px px-8px rounded-8px
|
|
363
|
+
>
|
|
364
|
+
<TelaSkeleton rounded-6px bg-neutral-200 :style="{ width: row.width, height: '13px' }" />
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
<div v-else-if="visibleConversations.length === 0" p-16px text-center>
|
|
355
368
|
<p text-14px text-neutral-400>
|
|
356
369
|
Nenhuma conversa ainda
|
|
357
370
|
</p>
|
|
@@ -449,7 +462,10 @@ onBeforeUnmount(() => {
|
|
|
449
462
|
</template>
|
|
450
463
|
|
|
451
464
|
<template v-else-if="loading">
|
|
452
|
-
<
|
|
465
|
+
<ChatMessageList
|
|
466
|
+
:messages="[]"
|
|
467
|
+
loading
|
|
468
|
+
/>
|
|
453
469
|
<ChatMessageInput
|
|
454
470
|
v-model:model="selectedModel"
|
|
455
471
|
:disabled="true"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ContextFile } from '../../../server/db/schema.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
searchQuery: string;
|
|
4
|
+
filteredContextFiles: readonly ContextFile[];
|
|
5
|
+
filteredPendingFiles: readonly File[];
|
|
6
|
+
currentFileCount: number;
|
|
7
|
+
maxFiles: number;
|
|
8
|
+
getFileType: (fileName: string) => string;
|
|
9
|
+
};
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
11
|
+
handleFileSelect: (event: Event) => any;
|
|
12
|
+
removeContextFile: (url: string) => any;
|
|
13
|
+
removePendingFile: (file: File) => any;
|
|
14
|
+
"update:searchQuery": (value: string) => any;
|
|
15
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
16
|
+
onHandleFileSelect?: ((event: Event) => any) | undefined;
|
|
17
|
+
onRemoveContextFile?: ((url: string) => any) | undefined;
|
|
18
|
+
onRemovePendingFile?: ((file: File) => any) | undefined;
|
|
19
|
+
"onUpdate:searchQuery"?: ((value: string) => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
declare const _default: typeof __VLS_export;
|
|
22
|
+
export default _default;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
searchQuery: { type: String, required: true },
|
|
4
|
+
filteredContextFiles: { type: Array, required: true },
|
|
5
|
+
filteredPendingFiles: { type: Array, required: true },
|
|
6
|
+
currentFileCount: { type: Number, required: true },
|
|
7
|
+
maxFiles: { type: Number, required: true },
|
|
8
|
+
getFileType: { type: Function, required: true }
|
|
9
|
+
});
|
|
10
|
+
const emit = defineEmits(["update:searchQuery", "handleFileSelect", "removePendingFile", "removeContextFile"]);
|
|
11
|
+
const fileInputRef = ref(null);
|
|
12
|
+
function openFileDialog() {
|
|
13
|
+
fileInputRef.value?.click();
|
|
14
|
+
}
|
|
15
|
+
function onFileChange(event) {
|
|
16
|
+
emit("handleFileSelect", event);
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div mb-24px>
|
|
22
|
+
<h2 text-16px font-semibold text-primary tracking="-.2px" leading-20px>
|
|
23
|
+
Arquivos de contexto
|
|
24
|
+
</h2>
|
|
25
|
+
<div h-px bg-gray-200 mt-24px />
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<input ref="fileInputRef" type="file" multiple hidden @change="onFileChange">
|
|
29
|
+
|
|
30
|
+
<div flex items-center justify-between mb-24px>
|
|
31
|
+
<div
|
|
32
|
+
flex items-center gap-8px
|
|
33
|
+
w-240px pl-12px pr-8px py-4px
|
|
34
|
+
bg-white b=".5px gray-200" rounded-10px
|
|
35
|
+
shadow-sm
|
|
36
|
+
>
|
|
37
|
+
<div i-ph-magnifying-glass text-12px text-gray-400 />
|
|
38
|
+
<input
|
|
39
|
+
:value="searchQuery"
|
|
40
|
+
type="text"
|
|
41
|
+
placeholder="Buscar"
|
|
42
|
+
text-14px text-primary placeholder:text-gray-400
|
|
43
|
+
bg-transparent border-none outline-none
|
|
44
|
+
flex-1 leading-18px tracking="-.15px"
|
|
45
|
+
@input="emit('update:searchQuery', $event.target.value)"
|
|
46
|
+
>
|
|
47
|
+
</div>
|
|
48
|
+
<button
|
|
49
|
+
px-16px py-10px
|
|
50
|
+
bg-white b=".5px gray-200" rounded-12px
|
|
51
|
+
text-16px font-semibold text-gray-900
|
|
52
|
+
cursor-pointer hover:bg-gray-50 transition
|
|
53
|
+
:disabled="currentFileCount >= maxFiles"
|
|
54
|
+
:class="{ 'opacity-50 cursor-not-allowed': currentFileCount >= maxFiles }"
|
|
55
|
+
@click="openFileDialog"
|
|
56
|
+
>
|
|
57
|
+
Enviar
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div w-full>
|
|
62
|
+
<div flex items-center b-y=".5px gray-200" bg-white>
|
|
63
|
+
<div flex-1 px-20px py-12px>
|
|
64
|
+
<span text-12px font-semibold text-primary leading-16px>Nome</span>
|
|
65
|
+
</div>
|
|
66
|
+
<div w-100px px-20px py-12px>
|
|
67
|
+
<span text-12px font-semibold text-primary leading-16px>Tipo</span>
|
|
68
|
+
</div>
|
|
69
|
+
<div w-40px />
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div>
|
|
73
|
+
<div
|
|
74
|
+
v-for="file in filteredContextFiles"
|
|
75
|
+
:key="`existing-${file.url}`"
|
|
76
|
+
flex items-center b-b=".5px gray-100" bg-white
|
|
77
|
+
>
|
|
78
|
+
<div flex items-center gap-8px flex-1 h-48px px-20px py-12px>
|
|
79
|
+
<div i-ph-file-pdf text-16px text-red-500 shrink-0 />
|
|
80
|
+
<span text-14px text-gray-900 leading-18px tracking="-.15px" truncate>
|
|
81
|
+
{{ file.name }}
|
|
82
|
+
</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div w-100px h-48px px-20px py-12px flex items-center>
|
|
85
|
+
<span text-12px text-tertiary leading-16px>
|
|
86
|
+
{{ getFileType(file.name) }}
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
<div w-40px px-8px py-12px>
|
|
90
|
+
<button
|
|
91
|
+
p-4px rounded-6px hover:bg-gray-100 transition cursor-pointer
|
|
92
|
+
@click="emit('removeContextFile', file.url)"
|
|
93
|
+
>
|
|
94
|
+
<div i-ph-x text-16px text-primary />
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div
|
|
100
|
+
v-for="file in filteredPendingFiles"
|
|
101
|
+
:key="`pending-${file.name}-${file.size}-${file.lastModified}`"
|
|
102
|
+
flex items-center b-b=".5px blue-200" bg-blue-50
|
|
103
|
+
>
|
|
104
|
+
<div flex items-center gap-8px flex-1 h-48px px-20px py-12px>
|
|
105
|
+
<div i-ph-file-pdf text-16px text-blue-500 shrink-0 />
|
|
106
|
+
<span text-14px text-blue-900 leading-18px tracking="-.15px" truncate>
|
|
107
|
+
{{ file.name }}
|
|
108
|
+
</span>
|
|
109
|
+
</div>
|
|
110
|
+
<div w-100px h-48px px-20px py-12px flex items-center>
|
|
111
|
+
<span text-12px text-blue-500 leading-16px>
|
|
112
|
+
{{ getFileType(file.name) }}
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
<div w-40px px-8px py-12px>
|
|
116
|
+
<button
|
|
117
|
+
p-4px rounded-6px hover:bg-blue-100 transition cursor-pointer
|
|
118
|
+
@click="emit('removePendingFile', file)"
|
|
119
|
+
>
|
|
120
|
+
<div i-ph-x text-16px text-blue-500 />
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div
|
|
126
|
+
v-if="filteredContextFiles.length === 0 && filteredPendingFiles.length === 0"
|
|
127
|
+
flex items-center justify-center py-48px text-14px text-tertiary
|
|
128
|
+
>
|
|
129
|
+
Nenhum arquivo adicionado ainda
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ContextFile } from '../../../server/db/schema.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
searchQuery: string;
|
|
4
|
+
filteredContextFiles: readonly ContextFile[];
|
|
5
|
+
filteredPendingFiles: readonly File[];
|
|
6
|
+
currentFileCount: number;
|
|
7
|
+
maxFiles: number;
|
|
8
|
+
getFileType: (fileName: string) => string;
|
|
9
|
+
};
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
11
|
+
handleFileSelect: (event: Event) => any;
|
|
12
|
+
removeContextFile: (url: string) => any;
|
|
13
|
+
removePendingFile: (file: File) => any;
|
|
14
|
+
"update:searchQuery": (value: string) => any;
|
|
15
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
16
|
+
onHandleFileSelect?: ((event: Event) => any) | undefined;
|
|
17
|
+
onRemoveContextFile?: ((url: string) => any) | undefined;
|
|
18
|
+
onRemovePendingFile?: ((file: File) => any) | undefined;
|
|
19
|
+
"onUpdate:searchQuery"?: ((value: string) => any) | undefined;
|
|
20
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
declare const _default: typeof __VLS_export;
|
|
22
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
searchQuery: string;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
"update:searchQuery": (value: string) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
7
|
+
"onUpdate:searchQuery"?: ((value: string) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
searchQuery: { type: String, required: true }
|
|
4
|
+
});
|
|
5
|
+
const emit = defineEmits(["update:searchQuery"]);
|
|
6
|
+
const localSearchQuery = computed({
|
|
7
|
+
get: () => props.searchQuery,
|
|
8
|
+
set: (value) => emit("update:searchQuery", value)
|
|
9
|
+
});
|
|
10
|
+
const {
|
|
11
|
+
credentials,
|
|
12
|
+
loading: credentialsLoading,
|
|
13
|
+
fetchCredentials,
|
|
14
|
+
filteredCredentials,
|
|
15
|
+
credentialModalOpen,
|
|
16
|
+
deleteModalOpen,
|
|
17
|
+
credentialForm,
|
|
18
|
+
credentialSaving,
|
|
19
|
+
credentialDeleting,
|
|
20
|
+
credentialError,
|
|
21
|
+
editingCredential,
|
|
22
|
+
deletingCredential,
|
|
23
|
+
openCreateModal,
|
|
24
|
+
closeModal,
|
|
25
|
+
handleSave,
|
|
26
|
+
handleDelete,
|
|
27
|
+
getMenuItems,
|
|
28
|
+
formatLastRotated
|
|
29
|
+
} = useWorkspaceCredentials(localSearchQuery);
|
|
30
|
+
onMounted(() => {
|
|
31
|
+
if (!credentials.value.length)
|
|
32
|
+
fetchCredentials();
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<div mb-24px>
|
|
38
|
+
<h2 text-16px font-semibold text-primary tracking="-.2px" leading-20px>
|
|
39
|
+
Credenciais
|
|
40
|
+
</h2>
|
|
41
|
+
<div h-px bg-gray-200 mt-24px />
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div flex items-center justify-between mb-16px>
|
|
45
|
+
<div
|
|
46
|
+
flex items-center gap-8px
|
|
47
|
+
w-240px pl-12px pr-8px py-4px
|
|
48
|
+
bg-white b=".5px gray-200" rounded-10px
|
|
49
|
+
shadow-sm
|
|
50
|
+
>
|
|
51
|
+
<div i-ph-magnifying-glass text-12px text-gray-400 />
|
|
52
|
+
<input
|
|
53
|
+
v-model="localSearchQuery"
|
|
54
|
+
type="text"
|
|
55
|
+
placeholder="Buscar por key"
|
|
56
|
+
text-14px text-primary placeholder:text-gray-400
|
|
57
|
+
bg-transparent border-none outline-none
|
|
58
|
+
flex-1 leading-18px tracking="-.15px"
|
|
59
|
+
>
|
|
60
|
+
</div>
|
|
61
|
+
<TelaButton
|
|
62
|
+
variant="secondary"
|
|
63
|
+
@click="openCreateModal"
|
|
64
|
+
>
|
|
65
|
+
<div i-ph-plus text-16px />
|
|
66
|
+
<span>Adicionar</span>
|
|
67
|
+
</TelaButton>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div v-if="credentialsLoading && !credentials.length" flex items-center justify-center py-48px>
|
|
71
|
+
<div i-ph-spinner animate-spin text-32px text-gray-400 />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div v-else :class="{ 'opacity-50 pointer-events-none': credentialsLoading }">
|
|
75
|
+
<TelaTable v-if="credentials.length > 0" class="table-fixed" w-full overflow-x-hidden>
|
|
76
|
+
<TelaTableHeader>
|
|
77
|
+
<TelaTableRow>
|
|
78
|
+
<TelaTableHead class="!py-10px text-14px font-medium text-secondary">
|
|
79
|
+
Nome
|
|
80
|
+
</TelaTableHead>
|
|
81
|
+
<TelaTableHead class="!py-10px !w-200px text-14px font-medium text-secondary hide-narrow">
|
|
82
|
+
Valor
|
|
83
|
+
</TelaTableHead>
|
|
84
|
+
<TelaTableHead class="!py-10px !w-180px text-14px font-medium text-secondary">
|
|
85
|
+
Última rotação
|
|
86
|
+
</TelaTableHead>
|
|
87
|
+
<TelaTableHead class="!py-10px !w-48px" />
|
|
88
|
+
</TelaTableRow>
|
|
89
|
+
</TelaTableHeader>
|
|
90
|
+
<TelaTableBody>
|
|
91
|
+
<TelaTableRow
|
|
92
|
+
v-for="credential in filteredCredentials"
|
|
93
|
+
:key="credential.id"
|
|
94
|
+
>
|
|
95
|
+
<TelaTableCell class="!py-14px">
|
|
96
|
+
<span text-14px font-mono text-primary>{{ credential.key }}</span>
|
|
97
|
+
</TelaTableCell>
|
|
98
|
+
<TelaTableCell class="!py-14px !w-200px hide-narrow">
|
|
99
|
+
<span text-14px text-primary>••••••••••</span>
|
|
100
|
+
</TelaTableCell>
|
|
101
|
+
<TelaTableCell class="!py-14px !w-180px">
|
|
102
|
+
<span text-14px text-secondary>{{ formatLastRotated(credential.updatedAt, credential.createdAt) }}</span>
|
|
103
|
+
</TelaTableCell>
|
|
104
|
+
<TelaTableCell class="!py-14px !w-48px">
|
|
105
|
+
<TelaDropdownMenu :items="getMenuItems(credential)" align="end">
|
|
106
|
+
<button p-6px rounded-6px text-gray-400 hover:text-gray-600 transition-colors cursor-pointer>
|
|
107
|
+
<div i-ph-dots-three text-18px />
|
|
108
|
+
</button>
|
|
109
|
+
</TelaDropdownMenu>
|
|
110
|
+
</TelaTableCell>
|
|
111
|
+
</TelaTableRow>
|
|
112
|
+
<TelaTableRow v-if="filteredCredentials.length === 0 && searchQuery">
|
|
113
|
+
<TelaTableCell :colspan="4" class="!py-32px text-center">
|
|
114
|
+
<span text-14px text-secondary>Nenhum resultado para "{{ searchQuery }}"</span>
|
|
115
|
+
</TelaTableCell>
|
|
116
|
+
</TelaTableRow>
|
|
117
|
+
</TelaTableBody>
|
|
118
|
+
</TelaTable>
|
|
119
|
+
|
|
120
|
+
<div v-else flex="~ col" items-center justify-center py-48px gap-8px>
|
|
121
|
+
<div i-ph-password-bold-duotone text-32px text-gray-300 />
|
|
122
|
+
<p text-14px text-secondary>
|
|
123
|
+
Nenhuma credencial adicionada ainda
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<TelaModal
|
|
129
|
+
v-model="credentialModalOpen"
|
|
130
|
+
:prevent-close="credentialSaving"
|
|
131
|
+
class="w-420px! relative p-20px! pt-16px!"
|
|
132
|
+
compact
|
|
133
|
+
:title="editingCredential ? 'Editar credencial' : 'Nova credencial'"
|
|
134
|
+
small-title
|
|
135
|
+
:is-close-icon="false"
|
|
136
|
+
hide-dividers
|
|
137
|
+
@keydown.esc.stop="credentialModalOpen = false"
|
|
138
|
+
>
|
|
139
|
+
<div relative flex="~ col" pt-24px gap-24px w-full>
|
|
140
|
+
<TelaIconButton
|
|
141
|
+
icon="i-ph-x"
|
|
142
|
+
size="md"
|
|
143
|
+
color="secondary"
|
|
144
|
+
outline-none
|
|
145
|
+
absolute p-8px top--32px right--12px
|
|
146
|
+
@click="credentialModalOpen = false"
|
|
147
|
+
/>
|
|
148
|
+
<form flex="~ col" gap-24px w-full @submit.prevent="handleSave">
|
|
149
|
+
<div v-if="credentialError" body-12-regular text-red-500 bg-red-50 px-12px py-8px rounded-8px>
|
|
150
|
+
{{ credentialError }}
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div flex="~ col" gap-8px>
|
|
154
|
+
<TelaLabel for="credentialKey">
|
|
155
|
+
Nome
|
|
156
|
+
</TelaLabel>
|
|
157
|
+
<TelaInput
|
|
158
|
+
id="credentialKey"
|
|
159
|
+
v-model="credentialForm.key"
|
|
160
|
+
size="sm"
|
|
161
|
+
:disabled="!!editingCredential"
|
|
162
|
+
:autofocus="!editingCredential"
|
|
163
|
+
placeholder="DATABASE_URL"
|
|
164
|
+
input-class="!px-9px !py-4px"
|
|
165
|
+
/>
|
|
166
|
+
<p body-12-regular text-secondary>
|
|
167
|
+
Apenas letras maiúsculas, números e underscore (ex: API_KEY)
|
|
168
|
+
</p>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div flex="~ col" gap-8px>
|
|
172
|
+
<TelaLabel for="credentialValue">
|
|
173
|
+
Valor
|
|
174
|
+
</TelaLabel>
|
|
175
|
+
<TelaInput
|
|
176
|
+
id="credentialValue"
|
|
177
|
+
v-model="credentialForm.value"
|
|
178
|
+
size="sm"
|
|
179
|
+
type="password"
|
|
180
|
+
:autofocus="!!editingCredential"
|
|
181
|
+
:placeholder="editingCredential ? 'Novo valor (deixe vazio para manter)' : 'Valor da credencial'"
|
|
182
|
+
input-class="!px-9px !py-4px"
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div flex items-center justify-end gap-8px>
|
|
187
|
+
<TelaButton type="button" size="md" variant="secondary" @click="closeModal">
|
|
188
|
+
Cancelar
|
|
189
|
+
</TelaButton>
|
|
190
|
+
<TelaButton
|
|
191
|
+
type="submit"
|
|
192
|
+
size="md"
|
|
193
|
+
:loading="credentialSaving"
|
|
194
|
+
:disabled="!editingCredential && (!credentialForm.key || !credentialForm.value)"
|
|
195
|
+
>
|
|
196
|
+
{{ editingCredential ? "Salvar" : "Adicionar" }}
|
|
197
|
+
</TelaButton>
|
|
198
|
+
</div>
|
|
199
|
+
</form>
|
|
200
|
+
</div>
|
|
201
|
+
</TelaModal>
|
|
202
|
+
|
|
203
|
+
<TelaConfirmationModal
|
|
204
|
+
v-model:open="deleteModalOpen"
|
|
205
|
+
title="Excluir credencial?"
|
|
206
|
+
variant="danger"
|
|
207
|
+
confirm-button-text="Excluir"
|
|
208
|
+
cancel-button-text="Cancelar"
|
|
209
|
+
:loading="credentialDeleting"
|
|
210
|
+
@confirm="handleDelete"
|
|
211
|
+
@cancel="deleteModalOpen = false"
|
|
212
|
+
@keydown.esc.stop="deleteModalOpen = false"
|
|
213
|
+
>
|
|
214
|
+
A credencial <span font-mono font-semibold>{{ deletingCredential?.key }}</span> será excluída permanentemente.
|
|
215
|
+
</TelaConfirmationModal>
|
|
216
|
+
</template>
|
|
217
|
+
|
|
218
|
+
<style scoped>
|
|
219
|
+
@media (max-width:950px){.hide-narrow{display:none}}
|
|
220
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
searchQuery: string;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
"update:searchQuery": (value: string) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
7
|
+
"onUpdate:searchQuery"?: ((value: string) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const _default: typeof __VLS_export;
|
|
10
|
+
export default _default;
|
package/dist/runtime/embed/components/configuration/ChatConfigurationExternalSkillsTab.d.vue.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ExternalSkill } from '../../../server/db/schema.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
searchQuery: string;
|
|
4
|
+
displayedSkills: readonly ExternalSkill[];
|
|
5
|
+
filteredSkills: readonly ExternalSkill[];
|
|
6
|
+
hasChanges: boolean;
|
|
7
|
+
isPending: (ref: string) => boolean;
|
|
8
|
+
skillModalOpen: boolean;
|
|
9
|
+
skillRefInput: string;
|
|
10
|
+
skillError: string;
|
|
11
|
+
discoveringSkills: boolean;
|
|
12
|
+
gitHubButtonText: string;
|
|
13
|
+
};
|
|
14
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
15
|
+
"update:searchQuery": (value: string) => any;
|
|
16
|
+
"update:skillModalOpen": (value: boolean) => any;
|
|
17
|
+
"update:skillRefInput": (value: string) => any;
|
|
18
|
+
removeSkill: (ref: string) => any;
|
|
19
|
+
closeSkillModal: () => any;
|
|
20
|
+
handleAddSkillAndClose: () => any;
|
|
21
|
+
connectGitHub: () => any;
|
|
22
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
23
|
+
"onUpdate:searchQuery"?: ((value: string) => any) | undefined;
|
|
24
|
+
"onUpdate:skillModalOpen"?: ((value: boolean) => any) | undefined;
|
|
25
|
+
"onUpdate:skillRefInput"?: ((value: string) => any) | undefined;
|
|
26
|
+
onRemoveSkill?: ((ref: string) => any) | undefined;
|
|
27
|
+
onCloseSkillModal?: (() => any) | undefined;
|
|
28
|
+
onHandleAddSkillAndClose?: (() => any) | undefined;
|
|
29
|
+
onConnectGitHub?: (() => any) | undefined;
|
|
30
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
31
|
+
declare const _default: typeof __VLS_export;
|
|
32
|
+
export default _default;
|