Files
webapp/app/pages/clientarea/ai/index.vue
Ruslan Bakiev b177a567cf
All checks were successful
Build Docker Image / build (push) Successful in 3m43s
Pin chat input to bottom
2026-01-07 18:42:19 +07:00

132 lines
4.5 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<Section variant="plain" paddingY="md">
<div class="grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_420px] gap-6 items-start">
<div class="space-y-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>
</Stack>
</Card>
</div>
<Card
padding="none"
class="w-full flex flex-col min-h-0 lg:sticky lg:top-6 lg:h-[calc(100vh-3rem)] overflow-hidden"
>
<div class="flex flex-col h-full min-h-0">
<div class="bg-base-200 p-4 flex-1 min-h-0 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>
<div class="border-t border-base-300 bg-base-100 p-4">
<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>
</div>
</div>
</Card>
</div>
</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) => ({
type: m.role === 'assistant' ? 'ai' : 'human',
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>