WIP - using deep agent to create dog using workflow
This commit is contained in:
@@ -7,16 +7,31 @@ import {
|
||||
InputGroupText,
|
||||
} from '@/components/ui/input-group'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { ArrowUp } from 'lucide-vue-next'
|
||||
import { ArrowUp, Loader2 } from 'lucide-vue-next'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useApi } from '@/composables/useApi'
|
||||
|
||||
interface ChatMessage {
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
text: string;
|
||||
isStreaming?: boolean;
|
||||
}
|
||||
|
||||
interface StreamEvent {
|
||||
type: string;
|
||||
data?: any;
|
||||
processId?: string;
|
||||
nodeId?: string;
|
||||
toolName?: string;
|
||||
}
|
||||
|
||||
const chatInput = ref('')
|
||||
const messages = ref<{ role: 'user' | 'assistant'; text: string }[]>([])
|
||||
const messages = ref<ChatMessage[]>([])
|
||||
const sending = ref(false)
|
||||
const route = useRoute()
|
||||
const { api } = useApi()
|
||||
const sessionId = ref<string | null>(null)
|
||||
const eventSource = ref<EventSource | null>(null)
|
||||
|
||||
const getTenantId = () => {
|
||||
if (!import.meta.client) return 'tenant1'
|
||||
@@ -39,6 +54,97 @@ const buildContext = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const connectToStream = (sessionIdValue: string) => {
|
||||
if (eventSource.value) {
|
||||
eventSource.value.close()
|
||||
}
|
||||
|
||||
const baseUrl = window.location.hostname === 'localhost'
|
||||
? 'http://localhost:3000'
|
||||
: `https://${window.location.hostname}`
|
||||
|
||||
eventSource.value = new EventSource(
|
||||
`${baseUrl}/tenants/${getTenantId()}/ai-chat/stream?sessionId=${sessionIdValue}`
|
||||
)
|
||||
|
||||
eventSource.value.onmessage = (event) => {
|
||||
try {
|
||||
const payload: StreamEvent = JSON.parse(event.data)
|
||||
handleStreamEvent(payload)
|
||||
} catch (error) {
|
||||
console.error('Failed to parse stream event:', error)
|
||||
}
|
||||
}
|
||||
|
||||
eventSource.value.onerror = () => {
|
||||
eventSource.value?.close()
|
||||
eventSource.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const handleStreamEvent = (event: StreamEvent) => {
|
||||
switch (event.type) {
|
||||
case 'agent_started':
|
||||
// Agent is thinking
|
||||
break
|
||||
case 'processes_listed':
|
||||
// Processes discovered
|
||||
break
|
||||
case 'process_selected':
|
||||
messages.value.push({
|
||||
role: 'system',
|
||||
text: `🔄 Selected process: ${event.data?.processName || 'Process'}`,
|
||||
})
|
||||
break
|
||||
case 'agent_message':
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: event.data?.message || '',
|
||||
})
|
||||
break
|
||||
case 'node_started':
|
||||
const lastMsg = messages.value[messages.value.length - 1]
|
||||
if (lastMsg?.isStreaming) {
|
||||
lastMsg.text += `\n⚙️ Executing step...`
|
||||
}
|
||||
break
|
||||
case 'tool_called':
|
||||
const lastToolMsg = messages.value[messages.value.length - 1]
|
||||
if (lastToolMsg?.isStreaming) {
|
||||
lastToolMsg.text += `\n🔧 Using tool: ${event.toolName}`
|
||||
}
|
||||
break
|
||||
case 'need_input':
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: event.data?.prompt || 'I need some additional information from you.',
|
||||
})
|
||||
sending.value = false
|
||||
break
|
||||
case 'final':
|
||||
if (event.data?.output) {
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: event.data.message || '✅ Process completed successfully!',
|
||||
})
|
||||
} else if (event.data?.reply) {
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: event.data.reply,
|
||||
})
|
||||
}
|
||||
sending.value = false
|
||||
break
|
||||
case 'error':
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: `❌ Error: ${event.data?.error || 'An error occurred'}`,
|
||||
})
|
||||
sending.value = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!chatInput.value.trim()) return
|
||||
|
||||
@@ -47,8 +153,20 @@ const handleSend = async () => {
|
||||
chatInput.value = ''
|
||||
sending.value = true
|
||||
|
||||
// Add a streaming message placeholder
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: '🤔 Thinking...',
|
||||
isStreaming: true
|
||||
})
|
||||
|
||||
try {
|
||||
const history = messages.value.slice(0, -1).slice(-6)
|
||||
const history = messages.value
|
||||
.filter(m => m.role !== 'system' && !m.isStreaming)
|
||||
.slice(0, -1)
|
||||
.slice(-6)
|
||||
.map(m => ({ role: m.role, text: m.text }))
|
||||
|
||||
const response = await api.post(`/tenants/${getTenantId()}/ai-chat/messages`, {
|
||||
message,
|
||||
history,
|
||||
@@ -56,34 +174,32 @@ const handleSend = async () => {
|
||||
sessionId: sessionId.value || undefined,
|
||||
})
|
||||
|
||||
if (response.sessionId) {
|
||||
if (response.sessionId && !sessionId.value) {
|
||||
sessionId.value = response.sessionId
|
||||
connectToStream(response.sessionId)
|
||||
}
|
||||
|
||||
// Remove streaming placeholder and add response
|
||||
messages.value = messages.value.filter(m => !m.isStreaming)
|
||||
|
||||
if (response.reply) {
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: response.reply,
|
||||
})
|
||||
if (response.action === 'create_record') {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('ai-record-created', {
|
||||
detail: {
|
||||
objectApiName: buildContext().objectApiName,
|
||||
record: response.record,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: 'Process started. I will post updates as soon as they are ready.',
|
||||
})
|
||||
// If process is running, stream will handle updates
|
||||
if (response.runId) {
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: '⏳ Processing workflow...',
|
||||
isStreaming: true,
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Failed to send AI chat message:', error)
|
||||
messages.value = messages.value.filter(m => !m.isStreaming)
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: error.message || 'Sorry, I ran into an error. Please try again.',
|
||||
@@ -92,11 +208,17 @@ const handleSend = async () => {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (eventSource.value) {
|
||||
eventSource.value.close()
|
||||
}
|
||||
})
|
||||
</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 class="ai-chat-messages mb-4 space-y-3 max-h-[400px] overflow-y-auto">
|
||||
<div
|
||||
v-for="(message, index) in messages"
|
||||
:key="`${message.role}-${index}`"
|
||||
@@ -104,14 +226,19 @@ const handleSend = async () => {
|
||||
: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'"
|
||||
class="max-w-[80%] rounded-lg px-3 py-2 text-sm whitespace-pre-line"
|
||||
:class="{
|
||||
'bg-primary text-primary-foreground': message.role === 'user',
|
||||
'bg-white border border-border text-foreground': message.role === 'assistant',
|
||||
'bg-blue-50 border border-blue-200 text-blue-900 text-xs': message.role === 'system',
|
||||
}"
|
||||
>
|
||||
<Loader2 v-if="message.isStreaming" class="inline-block size-3 animate-spin mr-1" />
|
||||
{{ 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.
|
||||
Ask the assistant to execute business processes, add records, or answer questions.
|
||||
</p>
|
||||
</div>
|
||||
<InputGroup>
|
||||
|
||||
Reference in New Issue
Block a user