You've already forked agentic-coding-workflow
feat(web): AgentEventCard component (per-method rendering)
This commit is contained in:
@@ -0,0 +1,148 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import type { AcpEvent } from '@/api/acp'
|
||||||
|
|
||||||
|
const props = defineProps<{ event: AcpEvent }>()
|
||||||
|
|
||||||
|
// Backend may serialize payload as a string in some edge paths; defensively parse.
|
||||||
|
const payload = computed<Record<string, unknown>>(() => {
|
||||||
|
if (typeof props.event.payload === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(props.event.payload) as Record<string, unknown>
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (props.event.payload as Record<string, unknown>) ?? {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const eventType = computed(() => {
|
||||||
|
if (props.event.kind === 'session_terminated') return 'session_terminated'
|
||||||
|
if (props.event.kind === 'client_error') return 'client_error'
|
||||||
|
|
||||||
|
const m = props.event.method ?? ''
|
||||||
|
if (m === 'session/prompt') return 'user_prompt'
|
||||||
|
if (m === 'session/cancel') return 'user_cancel'
|
||||||
|
if (m === 'fs/read_text_file') return 'tool_fs_read'
|
||||||
|
if (m === 'fs/write_text_file') return 'tool_fs_write'
|
||||||
|
if (m === 'session/request_permission') return 'tool_permission'
|
||||||
|
if (m === 'session/update') {
|
||||||
|
const params = payload.value?.params as Record<string, unknown> | undefined
|
||||||
|
const update = params?.update as Record<string, unknown> | undefined
|
||||||
|
const su = update?.sessionUpdate
|
||||||
|
if (su === 'agent_message_chunk') return 'agent_text'
|
||||||
|
if (su === 'agent_thought_chunk') return 'agent_thought'
|
||||||
|
if (su === 'tool_call') return 'tool_call_update'
|
||||||
|
return 'agent_update'
|
||||||
|
}
|
||||||
|
if (m === 'initialize' || m === 'session/new') return 'protocol'
|
||||||
|
return 'other'
|
||||||
|
})
|
||||||
|
|
||||||
|
const thoughtCollapsed = ref(true)
|
||||||
|
|
||||||
|
function chunkText(): string {
|
||||||
|
const params = payload.value?.params as Record<string, unknown> | undefined
|
||||||
|
const update = params?.update as Record<string, unknown> | undefined
|
||||||
|
const content = update?.content
|
||||||
|
if (Array.isArray(content)) {
|
||||||
|
return content.map((c: { text?: string }) => c.text ?? '').join('')
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function userPromptText(): string {
|
||||||
|
const params = payload.value?.params as Record<string, unknown> | undefined
|
||||||
|
const prompt = params?.prompt
|
||||||
|
if (Array.isArray(prompt)) {
|
||||||
|
return prompt.map((p: { text?: string }) => p.text ?? '').join('')
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function fsPath(): string {
|
||||||
|
const params = payload.value?.params as Record<string, unknown> | undefined
|
||||||
|
return (params?.path as string) ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function fsWriteSize(): number {
|
||||||
|
const params = payload.value?.params as Record<string, unknown> | undefined
|
||||||
|
const c = params?.content
|
||||||
|
return typeof c === 'string' ? c.length : 0
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="py-1">
|
||||||
|
<div v-if="eventType === 'user_prompt'" class="flex gap-2">
|
||||||
|
<span class="text-xs text-gray-500 mt-0.5 w-12">user</span>
|
||||||
|
<div class="flex-1 px-2 py-1 bg-blue-50 rounded text-sm">{{ userPromptText() }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="eventType === 'user_cancel'" class="flex gap-2 text-xs text-gray-500">
|
||||||
|
<span class="w-12">user</span>
|
||||||
|
<span>· cancel</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="eventType === 'agent_text'" class="flex gap-2">
|
||||||
|
<span class="text-xs text-gray-500 mt-0.5 w-12">agent</span>
|
||||||
|
<div class="flex-1 text-sm whitespace-pre-wrap">{{ chunkText() }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="eventType === 'agent_thought'" class="flex gap-2">
|
||||||
|
<span class="text-xs text-gray-500 mt-0.5 w-12">thought</span>
|
||||||
|
<div class="flex-1">
|
||||||
|
<button
|
||||||
|
class="text-xs text-gray-500 hover:text-gray-700"
|
||||||
|
@click="thoughtCollapsed = !thoughtCollapsed"
|
||||||
|
>
|
||||||
|
{{ thoughtCollapsed ? '▶' : '▼' }} thinking...
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-if="!thoughtCollapsed"
|
||||||
|
class="mt-1 px-2 py-1 bg-gray-50 text-sm text-gray-700 italic whitespace-pre-wrap"
|
||||||
|
>
|
||||||
|
{{ chunkText() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="eventType === 'tool_fs_read'" class="flex gap-2 text-sm">
|
||||||
|
<span class="text-xs text-gray-500 mt-0.5 w-12">tool</span>
|
||||||
|
<div class="flex-1 px-2 py-0.5 bg-yellow-50 rounded">
|
||||||
|
read <code class="text-xs">{{ fsPath() }}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="eventType === 'tool_fs_write'" class="flex gap-2 text-sm">
|
||||||
|
<span class="text-xs text-gray-500 mt-0.5 w-12">tool</span>
|
||||||
|
<div class="flex-1 px-2 py-0.5 bg-orange-50 rounded">
|
||||||
|
write <code class="text-xs">{{ fsPath() }}</code> ({{ fsWriteSize() }} bytes)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="eventType === 'tool_permission'" class="flex gap-2 text-sm">
|
||||||
|
<span class="text-xs text-gray-500 mt-0.5 w-12">perm</span>
|
||||||
|
<div class="flex-1 px-2 py-0.5 bg-purple-50 rounded">
|
||||||
|
permission requested (auto-rejected)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="eventType === 'session_terminated'"
|
||||||
|
class="text-center text-xs text-gray-500 py-2"
|
||||||
|
>
|
||||||
|
session terminated
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="eventType === 'client_error'" class="flex gap-2 text-sm text-red-600">
|
||||||
|
<span class="text-xs mt-0.5 w-12">error</span>
|
||||||
|
<div class="flex-1">{{ (payload as { message?: string }).message }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="flex gap-2 text-xs text-gray-400">
|
||||||
|
<span class="w-12">{{ eventType }}</span>
|
||||||
|
<code class="flex-1 truncate">{{ event.method }}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user