Skip to content

Commit

Permalink
fix: Display event updates in the UI (#242)
Browse files Browse the repository at this point in the history
* fix: Replace user attention level with timeline activity retrieval

* fix: Adding playground components and rename events

* fix: Minor updates to the UI
  • Loading branch information
nadeesha authored Dec 8, 2024
1 parent 5061564 commit 84043d3
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 158 deletions.
152 changes: 110 additions & 42 deletions app/components/debug-event.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,45 @@ import {
import { useAuth } from "@clerk/nextjs";
import { ClientInferResponseBody } from "@ts-rest/core";
import { startCase } from "lodash";
import { Info } from "lucide-react";
import { Info, Blocks } from "lucide-react";
import { useState } from "react";
import { ReadOnlyJSON } from "./read-only-json";

const sanitizedKey: { [key: string]: string } = {
targetFn: "function",
targetFn: "Function",
workflowId: "Run ID",
clusterId: "Cluster ID",
};

function formatDateTime(date: string | Date) {
return new Date(date).toLocaleString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
}

export function DebugEvent({
event,
clusterId,
msSincePreviousEvent,
}: {
event: ClientInferResponseBody<
typeof contract.getRunTimeline,
200
>["activity"][number];
clusterId: string;
msSincePreviousEvent: number;
}) {
const [eventMeta, setEventMeta] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false);

const { getToken } = useAuth();

const fetchEventMeta = async () => {
if (eventMeta) return; // Don't fetch if we already have the data
if (eventMeta) return;
setIsLoading(true);
try {
const response = await client.getEventMeta({
Expand All @@ -52,17 +66,26 @@ export function DebugEvent({
console.error("Error fetching event metadata:", error);
} finally {
setIsLoading(false);
``;
}
};

return (
<div className="ml-8 py-2">
<div className="flex items-center space-x-2 mb-1">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<p className="text-sm font-medium text-gray-700">
{startCase(event.type)}
</p>
<div className="ml-8 py-2 relative">
<div className="absolute left-1 top-0 bottom-0 w-[2px] bg-gray-200 -top-2 -bottom-2"></div>

<div className="flex items-center space-x-2 mb-1 relative">
<div className="w-2 h-2 bg-blue-500 rounded-full relative z-10 ring-4 ring-white"></div>
<div className="flex items-center gap-2">
<p className="text-sm font-medium text-gray-700">
{startCase(event.type)}
</p>
<span className="text-xs text-muted-foreground font-mono">
{formatDateTime(event.createdAt)}
<span className="text-muted-foreground font-mono text-blue-400">
{msSincePreviousEvent ? ` +${msSincePreviousEvent / 1000}s` : ""}
</span>
</span>
</div>
<Sheet>
<SheetTrigger asChild>
<Info
Expand All @@ -71,44 +94,89 @@ export function DebugEvent({
/>
</SheetTrigger>
<SheetContent
style={{ minWidth: "60vw" }}
className="overflow-scroll"
side="right"
style={{ minWidth: "80%" }}
className="overflow-y-auto h-screen"
>
<SheetHeader>
<SheetTitle>Event Metadata</SheetTitle>
<SheetTitle>
<div className="flex items-center gap-3">
<div className="h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center">
<Info className="w-4 h-4 text-primary" />
</div>
<div>
<div className="font-mono">{startCase(event.type)}</div>
<div className="text-xs text-muted-foreground">
{formatDateTime(event.createdAt)}
</div>
</div>
</div>
</SheetTitle>
</SheetHeader>
<div className="mt-4">
{isLoading ? (
<p>Loading metadata...</p>
) : eventMeta ? (
<ReadOnlyJSON key={event.id} json={eventMeta} />
) : (
<p>No metadata available</p>
)}

<div className="mt-6 space-y-6">
<div className="rounded-xl bg-secondary/30 p-4 shadow-sm border border-border/50">
<div className="flex items-center gap-3 mb-4 pb-3 border-b border-border/50">
<div className="h-6 w-6 rounded-full bg-gray-100 flex items-center justify-center">
<Info className="w-3 h-3 text-gray-600" />
</div>
<div>
<div className="text-sm font-medium">Event Details</div>
<div className="text-xs text-muted-foreground">
Event ID: {event.id}
</div>
</div>
</div>

<div className="grid grid-cols-2 gap-4">
{Object.entries(event).map(([key, value]) => (
<div key={key} className="space-y-1">
<dt className="text-xs font-medium text-muted-foreground">
{sanitizedKey[key] ?? startCase(key)}
</dt>
<dd className="text-sm">
{value instanceof Date
? formatDateTime(value)
: String(value) || "—"}
</dd>
</div>
))}
</div>
</div>

<div className="rounded-xl bg-secondary/30 p-4 shadow-sm border border-border/50">
<div className="flex items-center gap-3 mb-4 pb-3 border-b border-border/50">
<div className="h-6 w-6 rounded-full bg-gray-100 flex items-center justify-center">
<Blocks className="w-3 h-3 text-gray-600" />
</div>
<div>
<div className="text-sm font-medium">Metadata</div>
<div className="text-xs text-muted-foreground">
Additional event information
</div>
</div>
</div>

{isLoading ? (
<div className="flex items-center justify-center h-24">
<p className="text-sm text-muted-foreground">
Loading metadata...
</p>
</div>
) : eventMeta ? (
<ReadOnlyJSON key={event.id} json={eventMeta} />
) : (
<div className="flex items-center justify-center h-24">
<p className="text-sm text-muted-foreground">
No metadata available
</p>
</div>
)}
</div>
</div>
</SheetContent>
</Sheet>
</div>
<div className="flex flex-wrap gap-2 ml-4">
{Object.entries(event)
.filter(
([key, value]) =>
key !== "id" && key !== "createdAt" && key !== "type" && !!value,
)
.map(([key, value]) => (
<span
key={key}
className="px-2 py-1 bg-gray-100 rounded-md text-xs text-gray-600 flex items-center"
>
<span className="font-medium mr-1">
{sanitizedKey[key] ?? startCase(key)}:
</span>
<span>
{value instanceof Date ? value.toISOString() : String(value)}
</span>
</span>
))}
</div>
</div>
);
}
48 changes: 21 additions & 27 deletions app/components/events-overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,17 @@ const typeToText: { [key: string]: string } = {
jobCreated: "Job was created.",
jobStatusRequest: `Caller asked for the status of the job.`,
jobReceived: `Function was received by the machine for execution.`,
jobResulted: `Function execution concluded.`,
functionResulted: `Function execution concluded.`,
jobStalled: `Function execution did not complete within the expected time frame. The function is marked as stalled.`,
jobRecovered: `Function execution was recovered after being marked as stalled.`,
jobStalledTooManyTimes: `Function execution did not complete within the expected time frame too many times. The execution has resulted in a failure.`,
agentMessage: `Agent message produced.`,
agentEnd: `Agent workflow concluded.`,
jobAcknowledged: `Job was acknowledged by the machine.`,
agentTool: `Agent is invoking a tool.`,
callingFunction: `Agent is invoking a tool.`,
humanMessage: `Human sent a message.`,
machineRegistered: `Machine registered with the control plane.`,
agentToolError: `Invoked tool produced an error.`,
functionErrored: `Invoked tool produced an error.`,
modelInvocation: `A call was made to the model.`,
};

Expand Down Expand Up @@ -129,7 +129,7 @@ const chartConfig: ChartConfig = {
label: "Job Received",
color: "hsl(210, 100%, 70%)", // Blue
},
jobResulted: {
functionResulted: {
label: "Job Resulted",
color: "hsl(210, 100%, 70%)", // Blue
},
Expand Down Expand Up @@ -157,7 +157,7 @@ const chartConfig: ChartConfig = {
label: "Job Acknowledged",
color: "hsl(210, 100%, 70%)", // Blue
},
agentTool: {
callingFunction: {
label: "Agent Tool",
color: "hsl(210, 100%, 70%)", // Blue
},
Expand All @@ -169,7 +169,7 @@ const chartConfig: ChartConfig = {
label: "Machine Registered",
color: "hsl(210, 100%, 70%)", // Blue
},
agentToolError: {
functionErrored: {
label: "Agent Tool Error",
color: "hsl(0, 100%, 70%)", // Red
},
Expand All @@ -183,7 +183,7 @@ const getEventCountsByTime = (events: Event[]) => {
if (events.length === 0) return [];

const earliestEventTime = new Date(
Math.min(...events.map((event) => new Date(event.createdAt).getTime())),
Math.min(...events.map((event) => new Date(event.createdAt).getTime()))
);

const timeNow = new Date();
Expand All @@ -195,7 +195,7 @@ const getEventCountsByTime = (events: Event[]) => {
const bucketStartTimes = Array.from({ length: bucketCount }, (_, i) => {
const time = new Date(earliestEventTime);
time.setMilliseconds(
time.getMilliseconds() + i * (differenceInMs / bucketCount),
time.getMilliseconds() + i * (differenceInMs / bucketCount)
);
return time;
});
Expand All @@ -208,13 +208,10 @@ const getEventCountsByTime = (events: Event[]) => {
});
return {
date: t.toISOString(),
...eventsInBucket.reduce(
(acc, event) => {
acc[event.type] = (acc[event.type] || 0) + 1;
return acc;
},
{} as Record<string, number>,
),
...eventsInBucket.reduce((acc, event) => {
acc[event.type] = (acc[event.type] || 0) + 1;
return acc;
}, {} as Record<string, number>),
};
});
};
Expand Down Expand Up @@ -252,7 +249,7 @@ function EventsOverlay({
Object.entries(query).map(([key, value]) => ({
key: key as FilterKey,
value: value as string,
})),
}))
);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<any>(null);
Expand All @@ -269,13 +266,10 @@ function EventsOverlay({
params: {
clusterId,
},
query: filters.reduce(
(acc, filter) => {
acc[filter.key] = filter.value;
return acc;
},
{} as Partial<Record<FilterKey, string>>,
),
query: filters.reduce((acc, filter) => {
acc[filter.key] = filter.value;
return acc;
}, {} as Partial<Record<FilterKey, string>>),
});

if (response.status === 200) {
Expand All @@ -289,7 +283,7 @@ function EventsOverlay({
setLoading(false);
}
},
[clusterId, getToken],
[clusterId, getToken]
);

useEffect(() => {
Expand Down Expand Up @@ -317,7 +311,7 @@ function EventsOverlay({
const updated = [...prev];
newFilters.forEach((newFilter) => {
const existingIndex = updated.findIndex(
(f) => f.key === newFilter.key && f.value === newFilter.value,
(f) => f.key === newFilter.key && f.value === newFilter.value
);
if (existingIndex === -1) {
updated.push(newFilter);
Expand All @@ -331,8 +325,8 @@ function EventsOverlay({
setFilters((prev) =>
prev.filter(
(f) =>
!(f.key === filterToRemove.key && f.value === filterToRemove.value),
),
!(f.key === filterToRemove.key && f.value === filterToRemove.value)
)
);
};

Expand Down
29 changes: 21 additions & 8 deletions app/components/workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,14 +308,27 @@ export function Run({
})) || [];

const activityElements =
runTimeline?.activity.map((a) => ({
element: (
<ElementWrapper id={a.id} key={a.id} mutableId={mutableId}>
<DebugEvent clusterId={clusterId} event={a} />
</ElementWrapper>
),
timestamp: new Date(a.createdAt).getTime(),
})) || [];
runTimeline?.activity
.sort((a, b) => (a.createdAt > b.createdAt ? 1 : -1))
.map((a, index) => ({
element: (
<ElementWrapper id={a.id} key={a.id} mutableId={mutableId}>
<DebugEvent
clusterId={clusterId}
event={a}
msSincePreviousEvent={
index > 0
? new Date(a.createdAt).getTime() -
new Date(
runTimeline?.activity[index - 1]?.createdAt ?? 0
).getTime()
: 0
}
/>
</ElementWrapper>
),
timestamp: new Date(a.createdAt).getTime(),
})) || [];

const testHeader =
runTimeline?.run.test === true
Expand Down
6 changes: 6 additions & 0 deletions control-plane/src/modules/calls/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ export const callsRouter = initServer().router(
sdkLanguage: request.headers["x-machine-sdk-language"],
xForwardedFor: request.headers["x-forwarded-for"],
ip: request.request.ip,
}).catch((e) => {
// don't fail the request if the machine upsert fails

logger.error("Failed to upsert machine", {
error: e,
});
}),
jobs.persistJobResult({
owner: machine,
Expand Down
Loading

0 comments on commit 84043d3

Please sign in to comment.