From 3da439207c8ce82b2c544fbc9a6028d832b7925f Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 21 May 2026 17:20:21 +0530 Subject: [PATCH] chore: minor UI fixes --- .../src/dograh_sdk/_generated_models.py | 4 +- ui/src/app/usage/page.tsx | 52 +------------------ .../workflow/[workflowId]/RenderWorkflow.tsx | 16 ++---- .../components/WorkflowTesterPanel.tsx | 15 +----- .../components/workflow-tester/shared.tsx | 36 ------------- .../components/workflow-tester/types.ts | 2 - ui/src/components/CallTypeCell.tsx | 50 ++++++++++++++++++ ui/src/components/flow/nodes/BaseNode.tsx | 11 +--- ui/src/components/flow/nodes/GenericNode.tsx | 1 - .../flow/nodes/common/NodeContent.tsx | 3 -- ui/src/components/flow/types.ts | 1 - .../workflow-runs/WorkflowRunsTable.tsx | 5 +- .../conversation/ConversationItemView.tsx | 33 +++++++++--- .../workflow/conversation/MessageBubble.tsx | 4 +- 14 files changed, 92 insertions(+), 141 deletions(-) create mode 100644 ui/src/components/CallTypeCell.tsx diff --git a/sdk/python/src/dograh_sdk/_generated_models.py b/sdk/python/src/dograh_sdk/_generated_models.py index 775fcc2..cda0551 100644 --- a/sdk/python/src/dograh_sdk/_generated_models.py +++ b/sdk/python/src/dograh_sdk/_generated_models.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: -# filename: dograh-openapi-XXXXXX.json.4p15PiTCyh -# timestamp: 2026-05-21T07:00:16+00:00 +# filename: dograh-openapi-XXXXXX.json.kmLQzhjuKC +# timestamp: 2026-05-21T11:50:06+00:00 from __future__ import annotations diff --git a/ui/src/app/usage/page.tsx b/ui/src/app/usage/page.tsx index 7fb5c64..e66355e 100644 --- a/ui/src/app/usage/page.tsx +++ b/ui/src/app/usage/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { ArrowDownLeft, ArrowUpRight, ChevronLeft, ChevronRight, Download, Globe, MessageSquare, Phone } from 'lucide-react'; +import { ChevronLeft, ChevronRight, Download, Globe } from 'lucide-react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useId, useState } from 'react'; import TimezoneSelect, { type ITimezoneOption } from 'react-timezone-select'; @@ -8,6 +8,7 @@ import { toast } from 'sonner'; import { downloadUsageRunsReportApiV1OrganizationsUsageRunsReportGet, getDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGet, getMpsCreditsApiV1OrganizationsUsageMpsCreditsGet, getUsageHistoryApiV1OrganizationsUsageRunsGet } from '@/client/sdk.gen'; import type { DailyUsageBreakdownResponse, MpsCreditsResponse, UsageHistoryResponse, WorkflowRunUsageResponse } from '@/client/types.gen'; +import { CallTypeCell } from '@/components/CallTypeCell'; import { DailyUsageTable } from '@/components/DailyUsageTable'; import { FilterBuilder } from '@/components/filters/FilterBuilder'; import { MediaPreviewButton, MediaPreviewDialog } from '@/components/MediaPreviewDialog'; @@ -23,7 +24,6 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { useUserConfig } from '@/context/UserConfigContext'; import { useAuth } from '@/lib/auth'; import { usageFilterAttributes } from '@/lib/filterAttributes'; @@ -33,53 +33,6 @@ import { ActiveFilter, DateRangeValue } from '@/types/filters'; // Get local timezone const getLocalTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone; -// Collapse a run's `mode` (from WorkflowRunMode in api/enums.py) into a coarse -// channel. Telephony providers (twilio, plivo, telnyx, vonage, vobiz, cloudonix, -// ari, ...) are phone calls; webrtc/smallwebrtc are browser web calls; textchat -// is a text conversation. Anything unknown falls back to "phone". -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'; -}; - -// Render the call's channel (mode) and direction (call_type) as two compact -// icons in a single cell, with a tooltip spelling out the full label. The -// channel icon shows medium/how (phone / web / chat); the colored arrow shows -// direction (inbound = incoming/emerald, outbound = outgoing/blue). -const 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} - - - ); -}; - export default function UsagePage() { const router = useRouter(); const searchParams = useSearchParams(); @@ -677,4 +630,3 @@ export default function UsagePage() { ); } - diff --git a/ui/src/app/workflow/[workflowId]/RenderWorkflow.tsx b/ui/src/app/workflow/[workflowId]/RenderWorkflow.tsx index 19b7ada..a9cdc72 100644 --- a/ui/src/app/workflow/[workflowId]/RenderWorkflow.tsx +++ b/ui/src/app/workflow/[workflowId]/RenderWorkflow.tsx @@ -25,7 +25,7 @@ import CustomEdge from "../../../components/flow/edges/CustomEdge"; import { GenericNode } from "../../../components/flow/nodes/GenericNode"; import { PhoneCallDialog } from './components/PhoneCallDialog'; import { VersionHistoryPanel, WorkflowVersion } from './components/VersionHistoryPanel'; -import type { WorkflowRuntimeFocusMode, WorkflowRuntimeNodeTransition } from './components/workflow-tester/types'; +import type { WorkflowRuntimeNodeTransition } from './components/workflow-tester/types'; import { WorkflowEditorHeader } from "./components/WorkflowEditorHeader"; import { WorkflowTesterPanel } from './components/WorkflowTesterPanel'; import { WorkflowProvider } from "./contexts/WorkflowContext"; @@ -94,9 +94,7 @@ function RenderWorkflow({ const [documents, setDocuments] = useState(undefined); const [tools, setTools] = useState(undefined); const [recordings, setRecordings] = useState([]); - const [runtimeFocusMode, setRuntimeFocusMode] = useState("follow"); const [activeRuntimeNodeId, setActiveRuntimeNodeId] = useState(null); - const [runtimePulseNonce, setRuntimePulseNonce] = useState(0); const { rfInstance, @@ -387,12 +385,11 @@ function RenderWorkflow({ data: { ...node.data, runtime_active: true, - runtime_pulse_nonce: runtimePulseNonce, }, } : node, ), - [activeRuntimeNodeId, nodes, runtimePulseNonce], + [activeRuntimeNodeId, nodes], ); const handleRuntimeNodeTransition = useCallback( @@ -404,9 +401,8 @@ function RenderWorkflow({ } setActiveRuntimeNodeId(nodeId); - setRuntimePulseNonce((value) => value + 1); - if (runtimeFocusMode !== "follow" || !instance.viewportInitialized) { + if (!instance.viewportInitialized) { return; } @@ -417,7 +413,7 @@ function RenderWorkflow({ maxZoom: 0.9, }); }, - [rfInstance, runtimeFocusMode], + [rfInstance], ); // Guard saveWorkflow so it's a no-op when viewing a historical version. @@ -669,8 +665,6 @@ function RenderWorkflow({ showWebCallOnboarding={shouldShowWebCallOnboarding} isVisible={isDesktopViewport} onClose={() => setIsTesterRailOpen(false)} - runtimeFocusMode={runtimeFocusMode} - onRuntimeFocusModeChange={setRuntimeFocusMode} onRuntimeNodeTransition={handleRuntimeNodeTransition} /> @@ -686,8 +680,6 @@ function RenderWorkflow({ disabledReason={testerDisabledReason} showWebCallOnboarding={shouldShowWebCallOnboarding} isVisible={isTesterSheetOpen} - runtimeFocusMode={runtimeFocusMode} - onRuntimeFocusModeChange={setRuntimeFocusMode} onRuntimeNodeTransition={handleRuntimeNodeTransition} /> diff --git a/ui/src/app/workflow/[workflowId]/components/WorkflowTesterPanel.tsx b/ui/src/app/workflow/[workflowId]/components/WorkflowTesterPanel.tsx index 97a53f3..61648dd 100644 --- a/ui/src/app/workflow/[workflowId]/components/WorkflowTesterPanel.tsx +++ b/ui/src/app/workflow/[workflowId]/components/WorkflowTesterPanel.tsx @@ -19,8 +19,8 @@ import { cn, getRandomId } from "@/lib/utils"; import { AiSimulatorPlaceholder } from "./workflow-tester/AiSimulatorPlaceholder"; import { EmbeddedVoiceTester } from "./workflow-tester/EmbeddedVoiceTester"; import { ManualTextChatPanel } from "./workflow-tester/ManualTextChatPanel"; -import { ChatModeToggle, DisabledNotice, EmptyState, RuntimeFocusToggle } from "./workflow-tester/shared"; -import type { WorkflowRuntimeFocusMode, WorkflowRuntimeNodeTransition } from "./workflow-tester/types"; +import { ChatModeToggle, DisabledNotice, EmptyState } from "./workflow-tester/shared"; +import type { WorkflowRuntimeNodeTransition } from "./workflow-tester/types"; import { extractSdkErrorMessage, getErrorMessage } from "./workflow-tester/utils"; interface WorkflowTesterPanelProps { @@ -32,8 +32,6 @@ interface WorkflowTesterPanelProps { isVisible?: boolean; className?: string; onClose?: () => void; - runtimeFocusMode: WorkflowRuntimeFocusMode; - onRuntimeFocusModeChange: (mode: WorkflowRuntimeFocusMode) => void; onRuntimeNodeTransition?: (transition: WorkflowRuntimeNodeTransition) => void; } @@ -46,8 +44,6 @@ export function WorkflowTesterPanel({ isVisible = true, className, onClose, - runtimeFocusMode, - onRuntimeFocusModeChange, onRuntimeNodeTransition, }: WorkflowTesterPanelProps) { const auth = useAuth(); @@ -178,13 +174,6 @@ export function WorkflowTesterPanel({ ) : null} -
-

Canvas sync

- -
diff --git a/ui/src/app/workflow/[workflowId]/components/workflow-tester/shared.tsx b/ui/src/app/workflow/[workflowId]/components/workflow-tester/shared.tsx index 12c1038..f953899 100644 --- a/ui/src/app/workflow/[workflowId]/components/workflow-tester/shared.tsx +++ b/ui/src/app/workflow/[workflowId]/components/workflow-tester/shared.tsx @@ -81,42 +81,6 @@ export function ChatModeToggle({ ); } -export function RuntimeFocusToggle({ - value, - onChange, -}: { - value: "pulse" | "follow"; - onChange: (next: "pulse" | "follow") => void; -}) { - const options: Array<{ id: "pulse" | "follow"; label: string }> = [ - { id: "pulse", label: "Pulse" }, - { id: "follow", label: "Follow" }, - ]; - - return ( -
- {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 ? ( -
)); 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 ? (