mirror of
https://github.com/ArthurDanjou/artagents.git
synced 2026-01-14 04:04:32 +01:00
feat: implement chat header component and enhance chat file management functionality
This commit is contained in:
79
app/components/chat/Header.vue
Normal file
79
app/components/chat/Header.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div v-if="currentAgent" class="flex justify-between w-full items-center">
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
color="primary"
|
||||
variant="ghost"
|
||||
class="rounded-full"
|
||||
@click="$router.push({ path: '/' })"
|
||||
/>
|
||||
<div class="flex flex-col items-center justify-center mb-4">
|
||||
<div class="flex items-center justify-center bg-neutral-300 dark:bg-neutral-800 rounded-full p-2">
|
||||
<UIcon
|
||||
:name="currentAgent.icon"
|
||||
size="32"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-2xl">
|
||||
{{ currentAgent.name }}
|
||||
</p>
|
||||
</div>
|
||||
<UPopover
|
||||
:content="{
|
||||
align: 'end',
|
||||
side: 'bottom',
|
||||
}"
|
||||
>
|
||||
<UButton
|
||||
icon="i-heroicons-ellipsis-horizontal"
|
||||
color="primary"
|
||||
variant="ghost"
|
||||
class="rounded-full"
|
||||
/>
|
||||
<template #content>
|
||||
<div class="p-2 space-y-2 max-w-94 min-w-48">
|
||||
<div class="flex items-start gap-2">
|
||||
<p class="text-sm font-bold">
|
||||
Name
|
||||
</p>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{{ currentAgent.name }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<p class="text-sm font-bold">
|
||||
Description
|
||||
</p>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{{ currentAgent.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<p class="text-sm font-bold">
|
||||
Model
|
||||
</p>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{{ currentAgent.defaultModel }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-start gap-4">
|
||||
<p class="text-sm font-bold">
|
||||
Prompt
|
||||
</p>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400 text-justify">
|
||||
{{ currentAgent.prompt }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Agent } from '~~/types';
|
||||
|
||||
defineProps<{
|
||||
currentAgent: Agent
|
||||
}>()
|
||||
</script>
|
||||
@@ -1,37 +1,55 @@
|
||||
import type { Message } from 'ai'
|
||||
|
||||
export async function loadChat(slug: string): Promise<Message[]> {
|
||||
const { blobs } = await $fetch('/api/files')
|
||||
|
||||
if (!blobs.find(item => item.pathname === `chats/${slug}.json`)) {
|
||||
await createChat(slug)
|
||||
export async function useChatFile() {
|
||||
const toast = useToast()
|
||||
async function loadChat(slug: string): Promise<Message[]> {
|
||||
if (!slug) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Slug is required',
|
||||
message: 'Slug is required',
|
||||
})
|
||||
}
|
||||
|
||||
const { blobs } = await $fetch('/api/files')
|
||||
|
||||
if (!blobs.find(item => item.pathname === `chats/${slug}.json`)) {
|
||||
await createChat(slug)
|
||||
}
|
||||
|
||||
const data = await $fetch<string>(`/api/chats/${slug}`)
|
||||
const dataString = JSON.stringify(data)
|
||||
|
||||
if (dataString === '[]')
|
||||
return []
|
||||
return JSON.parse(dataString)
|
||||
}
|
||||
|
||||
async function createChat(slug: string) {
|
||||
await $fetch('/api/chats', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
file: {
|
||||
name: `${slug}.json`,
|
||||
content: '[]',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteChat(slug: string) {
|
||||
await $fetch('/api/files', {
|
||||
method: 'DELETE',
|
||||
body: {
|
||||
pathname: `chats/${slug}.json`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
loadChat,
|
||||
createChat,
|
||||
deleteChat,
|
||||
}
|
||||
|
||||
const data = await $fetch<string>(`/api/chats/${slug}`)
|
||||
const dataString = JSON.stringify(data)
|
||||
|
||||
if (dataString === '[]')
|
||||
return []
|
||||
return JSON.parse(dataString)
|
||||
}
|
||||
|
||||
async function createChat(slug: string) {
|
||||
await $fetch('/api/chats', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
file: {
|
||||
name: `${slug}.json`,
|
||||
content: '[]',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteChat(slug: string) {
|
||||
await $fetch('/api/files', {
|
||||
method: 'DELETE',
|
||||
body: {
|
||||
pathname: `chats/${slug}.json`,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { PROVIDERS } from '~~/types'
|
||||
import { PROVIDERS, type Agent } from '~~/types'
|
||||
import { useChat } from '@ai-sdk/vue'
|
||||
import { onStartTyping } from '@vueuse/core'
|
||||
import { AGENTS, MODELS } from '~~/types'
|
||||
|
||||
const toast = useToast()
|
||||
const { agent } = useRoute().params
|
||||
const currentAgent = AGENTS.find(item => item.slug === agent)
|
||||
const currentAgent = ref(AGENTS.find(item => item.slug === agent) as Agent | undefined)
|
||||
|
||||
if (!currentAgent) {
|
||||
toast.clear()
|
||||
toast.add({
|
||||
title: 'Agent not found',
|
||||
description: `Please try again`,
|
||||
color: 'error',
|
||||
})
|
||||
}
|
||||
|
||||
const model = ref<PROVIDERS>(currentAgent!.defaultModel)
|
||||
const model = ref<PROVIDERS>(currentAgent.value?.defaultModel || PROVIDERS.MISTRAL)
|
||||
const selectedModel = computed(() => MODELS.find(item => item.model === model.value))
|
||||
|
||||
const initialMessages = await loadChat(currentAgent!.slug)
|
||||
const { loadChat, deleteChat } = await useChatFile()
|
||||
const initialMessages = await loadChat(currentAgent.value?.slug || '')
|
||||
const { messages, input, handleSubmit, status, stop, error, reload } = useChat({
|
||||
id: currentAgent?.slug,
|
||||
id: currentAgent.value?.slug,
|
||||
initialMessages,
|
||||
sendExtraMessageFields: true,
|
||||
experimental_prepareRequestBody({ messages, id }) {
|
||||
@@ -30,7 +22,7 @@ const { messages, input, handleSubmit, status, stop, error, reload } = useChat({
|
||||
},
|
||||
body: {
|
||||
model: model.value,
|
||||
agent: currentAgent,
|
||||
agent: currentAgent.value,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -42,7 +34,7 @@ onStartTyping(() => { // TODO: fix focus
|
||||
|
||||
const isModalOpen = ref(false)
|
||||
async function deleteConversation() {
|
||||
await deleteChat(currentAgent!.slug)
|
||||
await deleteChat(currentAgent.value!.slug)
|
||||
window.location.reload()
|
||||
isModalOpen.value = false
|
||||
toast.add({
|
||||
@@ -58,77 +50,7 @@ async function deleteConversation() {
|
||||
v-if="currentAgent"
|
||||
class="flex flex-col justify-between"
|
||||
>
|
||||
<div
|
||||
class="flex justify-between w-full items-center"
|
||||
>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
color="primary"
|
||||
variant="ghost"
|
||||
class="rounded-full"
|
||||
@click="$router.push({ path: '/' })"
|
||||
/>
|
||||
<div class="flex flex-col items-center justify-center mb-4">
|
||||
<div class="flex items-center justify-center bg-neutral-300 dark:bg-neutral-800 rounded-full p-2">
|
||||
<UIcon
|
||||
:name="currentAgent.icon"
|
||||
size="32"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-2xl">
|
||||
{{ currentAgent.name }}
|
||||
</p>
|
||||
</div>
|
||||
<UPopover
|
||||
:content="{
|
||||
align: 'end',
|
||||
side: 'bottom',
|
||||
}"
|
||||
>
|
||||
<UButton
|
||||
icon="i-heroicons-ellipsis-horizontal"
|
||||
color="primary"
|
||||
variant="ghost"
|
||||
class="rounded-full"
|
||||
/>
|
||||
<template #content>
|
||||
<div class="p-2 space-y-2 max-w-94 min-w-48">
|
||||
<div class="flex items-start gap-2">
|
||||
<p class="text-sm font-bold">
|
||||
Name
|
||||
</p>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{{ currentAgent.name }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<p class="text-sm font-bold">
|
||||
Description
|
||||
</p>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{{ currentAgent.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<p class="text-sm font-bold">
|
||||
Model
|
||||
</p>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{{ currentAgent.defaultModel }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-start gap-4">
|
||||
<p class="text-sm font-bold">
|
||||
Prompt
|
||||
</p>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400 text-justify">
|
||||
{{ currentAgent.prompt }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
<ChatHeader :current-agent="currentAgent" />
|
||||
<ChatMessages
|
||||
:messages="messages"
|
||||
:initial-messages="initialMessages"
|
||||
@@ -188,18 +110,18 @@ async function deleteConversation() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex items-center gap-2 px-2">
|
||||
<div class="w-full flex items-center gap-2">
|
||||
<UButton
|
||||
icon="i-heroicons-paper-clip"
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
class="rounded-full"
|
||||
class="rounded-xl"
|
||||
@click="stop"
|
||||
/>
|
||||
<UTooltip text="Select model">
|
||||
<USelect
|
||||
v-model="model"
|
||||
class="rounded-full w-40"
|
||||
class="rounded-xl w-40"
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
:items="MODELS"
|
||||
@@ -222,7 +144,7 @@ async function deleteConversation() {
|
||||
icon="i-heroicons-trash"
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
class="rounded-full"
|
||||
class="rounded-xl"
|
||||
/>
|
||||
</UTooltip>
|
||||
<template #footer>
|
||||
|
||||
Reference in New Issue
Block a user