Update ag-ui-example UI with STATE_DELTA instead of ACTIVITY_SNAPSHOT since these are not handled by copilotkit

This commit is contained in:
Yiorgis Gozadinos 2025-12-03 15:32:48 +02:00
parent 84f25f0627
commit 0f5b9ae208
No known key found for this signature in database
3 changed files with 117 additions and 108 deletions

View file

@ -108,9 +108,35 @@ async def stream_research_agent(request: Request) -> StreamingResponse:
async for event in emitter: async for event in emitter:
# Log events for debugging # Log events for debugging
logger.info(f"AG-UI Event: {event}") logger.info(f"AG-UI Event: {event}")
# Filter out ACTIVITY_SNAPSHOT - not supported by CopilotKit event_type = event.get("type")
if event.get("type") == "ACTIVITY_SNAPSHOT":
# Convert ACTIVITY_SNAPSHOT to STATE_DELTA for CopilotKit
# As CopilotKit does not handle ACTIVITY_SNAPSHOT events
if event_type == "ACTIVITY_SNAPSHOT":
activity_type = event.get("activityType", "")
content = event.get("content", {})
message = content.get("message", "")
# Emit STATE_DELTA to patch activity info into state
# Use "add" op which creates or replaces the value
delta_event = {
"type": "STATE_DELTA",
"delta": [
{
"op": "add",
"path": "/current_activity",
"value": activity_type,
},
{
"op": "add",
"path": "/current_activity_message",
"value": message,
},
],
}
await send_stream.send(format_sse_event(delta_event))
continue continue
await send_stream.send(format_sse_event(event)) await send_stream.send(format_sse_event(event))
# Run agent and event forwarding concurrently # Run agent and event forwarding concurrently

View file

@ -70,6 +70,8 @@ interface ResearchState {
gaps_identified: GapRecord[]; gaps_identified: GapRecord[];
} | null; } | null;
result?: ResearchReport; result?: ResearchReport;
current_activity?: string;
current_activity_message?: string;
} }
function AgentContent() { function AgentContent() {

View file

@ -86,6 +86,8 @@ interface ResearchState {
gaps_identified: GapRecord[]; gaps_identified: GapRecord[];
} | null; } | null;
result?: ResearchReport; result?: ResearchReport;
current_activity?: string;
current_activity_message?: string;
} }
interface StateDisplayProps { interface StateDisplayProps {
@ -187,7 +189,8 @@ export default function StateDisplay({ state }: StateDisplayProps) {
gap: "1rem", gap: "1rem",
}} }}
> >
{/* Current Status */} {/* Question */}
{state.context.original_question && (
<div <div
style={{ style={{
background: "white", background: "white",
@ -200,52 +203,60 @@ export default function StateDisplay({ state }: StateDisplayProps) {
style={{ style={{
fontSize: "0.875rem", fontSize: "0.875rem",
color: "#718096", color: "#718096",
marginBottom: "0.5rem", marginBottom: "0.25rem",
}} }}
> >
Research Progress Question
</div> </div>
<div style={{ display: "flex", gap: "0.75rem", alignItems: "center" }}>
<div <div
style={{ style={{
padding: "0.5rem 1rem", fontSize: "1.125rem",
background: fontWeight: "bold",
state.iterations === 0 color: "#2d3748",
? "#e2e8f0" }}
: state.result >
? "#d1fae5" {state.context.original_question}
: "#dbeafe", </div>
color: </div>
state.iterations === 0 )}
? "#718096"
: state.result {/* Research Progress - only show when research has started */}
? "#065f46" {(state.iterations > 0 || (state.current_activity && !state.result)) && (
: "#1e40af", <div
style={{
background: "white",
borderRadius: "8px",
padding: "1.5rem",
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
}}
>
{/* Current Activity - hide when complete */}
{state.current_activity && !state.result && (
<div
style={{
padding: "0.75rem",
background: "#ebf8ff",
borderRadius: "6px", borderRadius: "6px",
fontSize: "1rem", border: "1px solid #90cdf4",
fontWeight: "700", marginBottom: state.iterations > 0 ? "1rem" : 0,
}} }}
> >
{state.iterations === 0 <div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
? "Ready" <span></span>
: state.result <span style={{ fontWeight: "600", color: "#2b6cb0" }}>
? "Complete" {state.current_activity.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}
: "Researching"} </span>
</div> </div>
{state.iterations > 0 && ( {state.current_activity_message && (
<div <div style={{ fontSize: "0.875rem", color: "#4299e1", marginTop: "0.25rem", marginLeft: "1.5rem" }}>
style={{ {state.current_activity_message}
fontSize: "0.875rem",
color: "#4a5568",
}}
>
Iteration {state.iterations} of {state.max_iterations}
</div> </div>
)} )}
</div> </div>
{/* Research Progress Bar */} )}
{/* Iteration Progress Bar */}
{state.iterations > 0 && ( {state.iterations > 0 && (
<div style={{ marginTop: "1rem" }}> <div>
<div <div
style={{ style={{
display: "flex", display: "flex",
@ -284,7 +295,7 @@ export default function StateDisplay({ state }: StateDisplayProps) {
style={{ style={{
width: `${researchProgress}%`, width: `${researchProgress}%`,
height: "100%", height: "100%",
background: "#48bb78", background: state.result ? "#48bb78" : "#4299e1",
transition: "width 0.3s ease", transition: "width 0.3s ease",
}} }}
/> />
@ -292,36 +303,6 @@ export default function StateDisplay({ state }: StateDisplayProps) {
</div> </div>
)} )}
</div> </div>
{/* Question */}
{state.context.original_question && (
<div
style={{
background: "white",
borderRadius: "8px",
padding: "1.5rem",
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
}}
>
<div
style={{
fontSize: "0.875rem",
color: "#718096",
marginBottom: "0.25rem",
}}
>
Question
</div>
<div
style={{
fontSize: "1.125rem",
fontWeight: "bold",
color: "#2d3748",
}}
>
{state.context.original_question}
</div>
</div>
)} )}
{/* Confidence Meter */} {/* Confidence Meter */}