- {options.map((option) => {
- const active = option.id === value;
- return (
-
- );
- })}
-
- );
-}
-
export function TypingIndicator() {
return (
diff --git a/ui/src/app/workflow/[workflowId]/components/workflow-tester/types.ts b/ui/src/app/workflow/[workflowId]/components/workflow-tester/types.ts
index 8f59b8d..43328c7 100644
--- a/ui/src/app/workflow/[workflowId]/components/workflow-tester/types.ts
+++ b/ui/src/app/workflow/[workflowId]/components/workflow-tester/types.ts
@@ -47,8 +47,6 @@ export interface TurnActionState {
type: "rewind" | "edit";
}
-export type WorkflowRuntimeFocusMode = "pulse" | "follow";
-
export type WorkflowRuntimeNodeTransition = ConversationNodeTransitionItem;
export const EMPTY_TEXT_CHAT_TURNS: TextChatTurn[] = [];
diff --git a/ui/src/components/CallTypeCell.tsx b/ui/src/components/CallTypeCell.tsx
new file mode 100644
index 0000000..f9e0076
--- /dev/null
+++ b/ui/src/components/CallTypeCell.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import { ArrowDownLeft, ArrowUpRight, Globe, MessageSquare, Phone } from "lucide-react";
+
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+
+const WEB_CALL_MODES = new Set(["webrtc", "smallwebrtc"]);
+const TEXT_CHAT_MODES = new Set(["textchat"]);
+
+const getCallChannel = (mode?: string | null): "phone" | "web" | "chat" => {
+ if (mode && TEXT_CHAT_MODES.has(mode)) return "chat";
+ if (mode && WEB_CALL_MODES.has(mode)) return "web";
+ return "phone";
+};
+
+export function CallTypeCell({
+ mode,
+ callType,
+}: {
+ mode?: string | null;
+ callType?: string | null;
+}) {
+ if (!mode && !callType) {
+ return
-;
+ }
+
+ const channel = getCallChannel(mode);
+ const ChannelIcon = channel === "chat" ? MessageSquare : channel === "web" ? Globe : Phone;
+ const channelLabel = channel === "chat" ? "Text chat" : channel === "web" ? "Web call" : "Phone call";
+
+ const isInbound = callType === "inbound";
+ const DirectionIcon = isInbound ? ArrowDownLeft : ArrowUpRight;
+ const directionLabel = isInbound ? "Inbound" : "Outbound";
+
+ return (
+
+
+
+
+
+
+
+
+ {directionLabel} · {channelLabel}
+
+
+ );
+}
diff --git a/ui/src/components/flow/nodes/BaseNode.tsx b/ui/src/components/flow/nodes/BaseNode.tsx
index 2b3a26c..134c79f 100644
--- a/ui/src/components/flow/nodes/BaseNode.tsx
+++ b/ui/src/components/flow/nodes/BaseNode.tsx
@@ -10,9 +10,8 @@ export const BaseNode = forwardRef<
selected_through_edge?: boolean;
hovered_through_edge?: boolean;
runtimeActive?: boolean;
- runtimePulseNonce?: number;
}
->(({ children, className, selected, invalid, selected_through_edge, hovered_through_edge, runtimeActive, runtimePulseNonce, ...props }, ref) => (
+>(({ children, className, selected, invalid, selected_through_edge, hovered_through_edge, runtimeActive, ...props }, ref) => (
- {runtimeActive ? (
-
- ) : null}
{children}
));
diff --git a/ui/src/components/flow/nodes/GenericNode.tsx b/ui/src/components/flow/nodes/GenericNode.tsx
index 824f255..5fb6a48 100644
--- a/ui/src/components/flow/nodes/GenericNode.tsx
+++ b/ui/src/components/flow/nodes/GenericNode.tsx
@@ -609,7 +609,6 @@ export const GenericNode = memo(({ data, selected, id, type }: GenericNodeProps)
selected_through_edge={data.selected_through_edge}
hovered_through_edge={data.hovered_through_edge}
runtimeActive={data.runtime_active}
- runtimePulseNonce={data.runtime_pulse_nonce}
title={data.name || fallbackTitle}
icon={
}
badgeLabel={badge.label}
diff --git a/ui/src/components/flow/nodes/common/NodeContent.tsx b/ui/src/components/flow/nodes/common/NodeContent.tsx
index eaff3d1..358a2c8 100644
--- a/ui/src/components/flow/nodes/common/NodeContent.tsx
+++ b/ui/src/components/flow/nodes/common/NodeContent.tsx
@@ -11,7 +11,6 @@ interface NodeContentProps {
selected_through_edge?: boolean;
hovered_through_edge?: boolean;
runtimeActive?: boolean;
- runtimePulseNonce?: number;
title: string;
icon: ReactNode;
badgeLabel?: string;
@@ -34,7 +33,6 @@ export const NodeContent = ({
selected_through_edge,
hovered_through_edge,
runtimeActive,
- runtimePulseNonce,
title,
icon,
badgeLabel,
@@ -59,7 +57,6 @@ export const NodeContent = ({
selected_through_edge={selected_through_edge}
hovered_through_edge={hovered_through_edge}
runtimeActive={runtimeActive}
- runtimePulseNonce={runtimePulseNonce}
className={`p-0 ${className}`}
onDoubleClick={onDoubleClick}
>
diff --git a/ui/src/components/flow/types.ts b/ui/src/components/flow/types.ts
index e3a0a0f..4a0ef9f 100644
--- a/ui/src/components/flow/types.ts
+++ b/ui/src/components/flow/types.ts
@@ -18,7 +18,6 @@ export type FlowNodeData = {
selected_through_edge?: boolean;
hovered_through_edge?: boolean;
runtime_active?: boolean;
- runtime_pulse_nonce?: number;
allow_interrupt?: boolean;
extraction_enabled?: boolean;
extraction_prompt?: string;
diff --git a/ui/src/components/workflow-runs/WorkflowRunsTable.tsx b/ui/src/components/workflow-runs/WorkflowRunsTable.tsx
index ef51e68..9c6672e 100644
--- a/ui/src/components/workflow-runs/WorkflowRunsTable.tsx
+++ b/ui/src/components/workflow-runs/WorkflowRunsTable.tsx
@@ -4,6 +4,7 @@ import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, ExternalLin
import { useState } from "react";
import { WorkflowRunResponseSchema } from "@/client/types.gen";
+import { CallTypeCell } from "@/components/CallTypeCell";
import { FilterBuilder } from "@/components/filters/FilterBuilder";
import { MediaPreviewButton, MediaPreviewDialog } from "@/components/MediaPreviewDialog";
import { Badge } from "@/components/ui/badge";
@@ -189,9 +190,7 @@ export function WorkflowRunsTable({
{formatDate(run.created_at)}
-
- {run.call_type === 'inbound' ? 'Inbound' : 'Outbound'}
-
+
{typeof run.cost_info?.call_duration_seconds === 'number'
diff --git a/ui/src/components/workflow/conversation/ConversationItemView.tsx b/ui/src/components/workflow/conversation/ConversationItemView.tsx
index 42e1780..351584d 100644
--- a/ui/src/components/workflow/conversation/ConversationItemView.tsx
+++ b/ui/src/components/workflow/conversation/ConversationItemView.tsx
@@ -15,15 +15,34 @@ interface ConversationItemViewProps {
export function ConversationItemView({ item, actions }: ConversationItemViewProps) {
if (item.kind === "message") {
+ const isUser = item.role === "user";
+ const bubble = (
+
+ );
+
+ if (isUser && actions) {
+ return (
+
+
+
+ {actions}
+
+ {bubble}
+
+
+ );
+ }
+
return (
-
+ {bubble}
{actions ? (
{actions}
diff --git a/ui/src/components/workflow/conversation/MessageBubble.tsx b/ui/src/components/workflow/conversation/MessageBubble.tsx
index 64f5a1f..fdd322f 100644
--- a/ui/src/components/workflow/conversation/MessageBubble.tsx
+++ b/ui/src/components/workflow/conversation/MessageBubble.tsx
@@ -10,6 +10,7 @@ interface MessageBubbleProps {
final?: boolean;
tone?: "default" | "muted";
reasoningDurationMs?: number;
+ containerClassName?: string;
}
export function MessageBubble({
@@ -18,12 +19,13 @@ export function MessageBubble({
final = true,
tone = "default",
reasoningDurationMs,
+ containerClassName,
}: MessageBubbleProps) {
const isUser = role === "user";
const isMuted = tone === "muted";
return (
-
+
{!isUser && reasoningDurationMs !== undefined ? (