WIP - using deep agent to create dog using workflow

This commit is contained in:
Francisco Gaona
2026-01-17 22:51:53 +01:00
parent ded413b99b
commit de65aa4025
26 changed files with 2288 additions and 193 deletions

View File

@@ -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>