* feat: ai agent step streaming * refactor * all * nits * fix other providers * nits * adapt to new streaming process
157 lines
3.6 KiB
Svelte
157 lines
3.6 KiB
Svelte
<script lang="ts">
|
|
import type { GraphModuleState } from './graph'
|
|
import {
|
|
JobService,
|
|
type CompletedJob,
|
|
type FlowModule,
|
|
type FlowStatusModule,
|
|
type Job
|
|
} from '$lib/gen'
|
|
import { workspaceStore } from '$lib/stores'
|
|
import FlowLogViewerWrapper from './FlowLogViewerWrapper.svelte'
|
|
import { z } from 'zod'
|
|
import { onMount } from 'svelte'
|
|
|
|
type AgentActionWithContent = NonNullable<FlowStatusModule['agent_actions']>[number] & {
|
|
content?: unknown
|
|
}
|
|
|
|
const resultSchema = z.object({
|
|
messages: z.array(
|
|
z.object({
|
|
role: z.string(),
|
|
content: z.unknown(),
|
|
agent_action: z
|
|
.union([
|
|
z.object({
|
|
type: z.literal('tool_call'),
|
|
job_id: z.string(),
|
|
module_id: z.string(),
|
|
function_name: z.string()
|
|
}),
|
|
z.object({
|
|
type: z.literal('message')
|
|
})
|
|
])
|
|
.optional()
|
|
})
|
|
)
|
|
})
|
|
|
|
interface Props {
|
|
tools: FlowModule[]
|
|
agentJob: Partial<CompletedJob> & Pick<CompletedJob, 'id'> & { type: 'CompletedJob' }
|
|
workspaceId?: string | undefined
|
|
storedToolCallJobs?: Record<number, Job>
|
|
onToolJobLoaded?: (job: Job, idx: number) => void
|
|
}
|
|
|
|
let { tools, agentJob, workspaceId, onToolJobLoaded, storedToolCallJobs }: Props = $props()
|
|
|
|
const fakeModuleStates: Record<string, GraphModuleState> = $state({})
|
|
|
|
async function loadMissingJobs(agentActions: AgentActionWithContent[]) {
|
|
const promises = agentActions.map(async (toolCall, idx) => {
|
|
if (toolCall.type === 'tool_call') {
|
|
let job: Job | undefined = storedToolCallJobs?.[idx]
|
|
|
|
if (!job || job.type !== 'CompletedJob') {
|
|
job = await JobService.getJob({
|
|
id: toolCall.job_id,
|
|
workspace: workspaceId ?? $workspaceStore!
|
|
})
|
|
}
|
|
fakeModuleStates[idx.toString()] = {
|
|
args: job.args,
|
|
type: job['success'] ? 'Success' : 'Failure',
|
|
logs: job.logs,
|
|
result: job['result'],
|
|
job_id: toolCall.job_id
|
|
}
|
|
onToolJobLoaded?.(job, idx)
|
|
} else {
|
|
fakeModuleStates[idx.toString()] = {
|
|
type: 'Success',
|
|
args: {},
|
|
logs: '',
|
|
result: toolCall.content
|
|
}
|
|
}
|
|
})
|
|
|
|
await Promise.all(promises)
|
|
}
|
|
|
|
let job: Partial<Job> | undefined = $state(undefined)
|
|
async function loadToolCalls() {
|
|
let parsedResult = resultSchema.safeParse(agentJob.result)
|
|
if (!parsedResult.success) {
|
|
console.error('Invalid result', parsedResult.error)
|
|
return
|
|
}
|
|
let agentActions = parsedResult.data.messages
|
|
.map(
|
|
(m) =>
|
|
(m.agent_action?.type === 'message'
|
|
? {
|
|
type: 'message',
|
|
content: m.content
|
|
}
|
|
: m.agent_action?.type === 'tool_call'
|
|
? {
|
|
type: 'tool_call',
|
|
job_id: m.agent_action.job_id,
|
|
module_id: m.agent_action.module_id,
|
|
function_name: m.agent_action.function_name
|
|
}
|
|
: undefined) as AgentActionWithContent | undefined
|
|
)
|
|
.filter((m) => m !== undefined)
|
|
|
|
await loadMissingJobs(agentActions)
|
|
|
|
job = {
|
|
...agentJob,
|
|
raw_flow: {
|
|
modules: agentActions
|
|
.map((toolCall, idx) => {
|
|
if (toolCall.type === 'message') {
|
|
return {
|
|
id: idx.toString(),
|
|
value: {
|
|
type: 'identity' as const
|
|
}
|
|
}
|
|
} else {
|
|
const module = tools.find((m) => m.summary === toolCall.function_name)
|
|
return module
|
|
? {
|
|
...module,
|
|
id: idx.toString()
|
|
}
|
|
: undefined
|
|
}
|
|
})
|
|
.filter((m) => m !== undefined)
|
|
}
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
loadToolCalls()
|
|
})
|
|
</script>
|
|
|
|
{#if job}
|
|
<div class="p-2">
|
|
<FlowLogViewerWrapper
|
|
{job}
|
|
localModuleStates={fakeModuleStates}
|
|
{workspaceId}
|
|
render={true}
|
|
onSelectedIteration={async () => {}}
|
|
mode="aiagent"
|
|
/>
|
|
</div>
|
|
{/if}
|