131 lines
3.6 KiB
Vue
131 lines
3.6 KiB
Vue
<script setup lang="ts">
|
|
import {
|
|
InputGroup,
|
|
InputGroupTextarea,
|
|
InputGroupAddon,
|
|
InputGroupButton,
|
|
InputGroupText,
|
|
} from '@/components/ui/input-group'
|
|
import { Separator } from '@/components/ui/separator'
|
|
import { ArrowUp } from 'lucide-vue-next'
|
|
import { useRoute } from 'vue-router'
|
|
import { useApi } from '@/composables/useApi'
|
|
|
|
const chatInput = ref('')
|
|
const messages = ref<{ role: 'user' | 'assistant'; text: string }[]>([])
|
|
const sending = ref(false)
|
|
const route = useRoute()
|
|
const { api } = useApi()
|
|
|
|
const buildContext = () => {
|
|
const recordId = route.params.recordId ? String(route.params.recordId) : undefined
|
|
const viewParam = route.params.view ? String(route.params.view) : undefined
|
|
const view = viewParam || (recordId ? (recordId === 'new' ? 'edit' : 'detail') : 'list')
|
|
const objectApiName = route.params.objectName
|
|
? String(route.params.objectName)
|
|
: undefined
|
|
|
|
return {
|
|
objectApiName,
|
|
view,
|
|
recordId,
|
|
route: route.fullPath,
|
|
}
|
|
}
|
|
|
|
const handleSend = async () => {
|
|
if (!chatInput.value.trim()) return
|
|
|
|
const message = chatInput.value.trim()
|
|
messages.value.push({ role: 'user', text: message })
|
|
chatInput.value = ''
|
|
sending.value = true
|
|
|
|
try {
|
|
const history = messages.value.slice(0, -1).slice(-6)
|
|
const response = await api.post('/ai/chat', {
|
|
message,
|
|
history,
|
|
context: buildContext(),
|
|
})
|
|
|
|
messages.value.push({
|
|
role: 'assistant',
|
|
text: response.reply || 'Let me know what else you need.',
|
|
})
|
|
|
|
if (response.action === 'create_record') {
|
|
window.dispatchEvent(
|
|
new CustomEvent('ai-record-created', {
|
|
detail: {
|
|
objectApiName: buildContext().objectApiName,
|
|
record: response.record,
|
|
},
|
|
}),
|
|
)
|
|
}
|
|
} catch (error: any) {
|
|
console.error('Failed to send AI chat message:', error)
|
|
messages.value.push({
|
|
role: 'assistant',
|
|
text: error.message || 'Sorry, I ran into an error. Please try again.',
|
|
})
|
|
} finally {
|
|
sending.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="ai-chat-area w-full border-t border-border p-4 bg-neutral-50">
|
|
<div class="ai-chat-messages mb-4 space-y-3">
|
|
<div
|
|
v-for="(message, index) in messages"
|
|
:key="`${message.role}-${index}`"
|
|
class="flex"
|
|
:class="message.role === 'user' ? 'justify-end' : 'justify-start'"
|
|
>
|
|
<div
|
|
class="max-w-[80%] rounded-lg px-3 py-2 text-sm"
|
|
:class="message.role === 'user' ? 'bg-primary text-primary-foreground' : 'bg-white border border-border text-foreground'"
|
|
>
|
|
{{ message.text }}
|
|
</div>
|
|
</div>
|
|
<p v-if="messages.length === 0" class="text-sm text-muted-foreground">
|
|
Ask the assistant to add records, filter lists, or summarize the page.
|
|
</p>
|
|
</div>
|
|
<InputGroup>
|
|
<InputGroupTextarea
|
|
v-model="chatInput"
|
|
placeholder="Ask, Search or Chat..."
|
|
class="min-h-[60px] rounded-lg"
|
|
@keydown.enter.exact.prevent="handleSend"
|
|
:disabled="sending"
|
|
/>
|
|
<InputGroupAddon>
|
|
<InputGroupText class="ml-auto">
|
|
52% used
|
|
</InputGroupText>
|
|
<Separator orientation="vertical" class="!h-4" />
|
|
<InputGroupButton
|
|
variant="default"
|
|
class="rounded-full"
|
|
:disabled="!chatInput.trim() || sending"
|
|
@click="handleSend"
|
|
>
|
|
<ArrowUp class="size-4" />
|
|
<span class="sr-only">Send</span>
|
|
</InputGroupButton>
|
|
</InputGroupAddon>
|
|
</InputGroup>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.ai-chat-area {
|
|
min-height: 190px;
|
|
}
|
|
</style>
|