121 lines
4.0 KiB
Vue
121 lines
4.0 KiB
Vue
<template>
|
||
<Section variant="plain" paddingY="md">
|
||
<Stack gap="6">
|
||
<PageHeader :title="t('aiAssistants.header.title')" />
|
||
|
||
<Card padding="lg" class="w-full">
|
||
<Stack gap="4">
|
||
<Stack direction="row" gap="3" align="center">
|
||
<IconCircle tone="primary" size="lg">🛰️</IconCircle>
|
||
<div>
|
||
<Heading :level="3" weight="semibold">{{ t('aiAssistants.view.agentTitle') }}</Heading>
|
||
<Text tone="muted">{{ t('aiAssistants.view.agentSubtitle') }}</Text>
|
||
</div>
|
||
</Stack>
|
||
|
||
<div class="bg-base-200 rounded-box p-4 h-[70vh] overflow-y-auto space-y-3">
|
||
<div
|
||
v-for="(message, idx) in chat"
|
||
:key="idx"
|
||
class="flex"
|
||
:class="message.role === 'user' ? 'justify-end' : 'justify-start'"
|
||
>
|
||
<div
|
||
class="max-w-[80%] rounded-2xl px-4 py-3 shadow-sm"
|
||
:class="message.role === 'user' ? 'bg-primary text-primary-content' : 'bg-base-100 text-base-content'"
|
||
>
|
||
<Text weight="semibold" class="mb-1">
|
||
{{ message.role === 'user' ? t('aiAssistants.view.you') : t('aiAssistants.view.agentName') }}
|
||
</Text>
|
||
<Text :tone="message.role === 'user' ? undefined : 'muted'">
|
||
{{ message.content }}
|
||
</Text>
|
||
</div>
|
||
</div>
|
||
<div v-if="isStreaming" class="text-sm text-base-content/60">
|
||
{{ t('aiAssistants.view.typing') }}
|
||
</div>
|
||
</div>
|
||
|
||
<form class="flex flex-col gap-3" @submit.prevent="handleSend">
|
||
<Textarea
|
||
v-model="input"
|
||
:placeholder="t('aiAssistants.view.placeholder')"
|
||
rows="3"
|
||
/>
|
||
<div class="flex items-center gap-3">
|
||
<Button type="submit" :loading="isSending" :disabled="!input.trim()">
|
||
{{ t('aiAssistants.view.send') }}
|
||
</Button>
|
||
<Button type="button" variant="ghost" @click="resetChat" :disabled="isSending">
|
||
{{ t('aiAssistants.view.reset') }}
|
||
</Button>
|
||
<div class="text-sm text-base-content/60" v-if="error">
|
||
{{ error }}
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</Stack>
|
||
</Card>
|
||
</Stack>
|
||
</Section>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
const { t } = useI18n()
|
||
const runtimeConfig = useRuntimeConfig()
|
||
|
||
const agentUrl = computed(() => runtimeConfig.public.langAgentUrl || '')
|
||
const chat = ref<{ role: 'user' | 'assistant', content: string }[]>([
|
||
{ role: 'assistant', content: t('aiAssistants.view.welcome') }
|
||
])
|
||
const input = ref('')
|
||
const isSending = ref(false)
|
||
const isStreaming = ref(false)
|
||
const error = ref('')
|
||
|
||
const handleSend = async () => {
|
||
if (!input.value.trim()) return
|
||
error.value = ''
|
||
const userMessage = input.value.trim()
|
||
chat.value.push({ role: 'user', content: userMessage })
|
||
input.value = ''
|
||
isSending.value = true
|
||
isStreaming.value = true
|
||
|
||
try {
|
||
const body = {
|
||
input: {
|
||
messages: chat.value.map((m) => ({
|
||
role: m.role === 'assistant' ? 'assistant' : 'user',
|
||
content: m.content
|
||
}))
|
||
}
|
||
}
|
||
|
||
const response = await $fetch(`${agentUrl.value}/invoke`, {
|
||
method: 'POST',
|
||
body
|
||
})
|
||
|
||
const outputMessages = (response as any)?.output?.messages || []
|
||
const last = outputMessages[outputMessages.length - 1]
|
||
const content = last?.content?.[0]?.text || last?.content || t('aiAssistants.view.emptyResponse')
|
||
chat.value.push({ role: 'assistant', content })
|
||
} catch (e: any) {
|
||
console.error('Agent error', e)
|
||
error.value = e?.message || t('aiAssistants.view.error')
|
||
chat.value.push({ role: 'assistant', content: t('aiAssistants.view.error') })
|
||
} finally {
|
||
isSending.value = false
|
||
isStreaming.value = false
|
||
}
|
||
}
|
||
|
||
const resetChat = () => {
|
||
chat.value = [{ role: 'assistant', content: t('aiAssistants.view.welcome') }]
|
||
input.value = ''
|
||
error.value = ''
|
||
}
|
||
</script>
|