Skip to content

Commit 2a74c4b

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(canvas): show full Agent prompt tooltips
1 parent c16883e commit 2a74c4b

7 files changed

Lines changed: 131 additions & 3 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { getConditionRows, getRouterRows } from '@/lib/workflows/dynamic-handle-
5353
import { getDependsOnFields } from '@/lib/workflows/subblocks/dependencies'
5454
import {
5555
getDisplayValue,
56+
getTooltipDisplayValue,
5657
hasDisplayableRowValue,
5758
resolveDropdownLabel,
5859
resolveFilterFieldLabel,
@@ -595,11 +596,16 @@ const SubBlockRow = memo(function SubBlockRow({
595596
webhookUrlDisplayValue ||
596597
selectorDisplayName
597598
const displayValue = maskedValue || hydratedName || (isSelectorType && value ? '-' : value)
599+
const tooltipValue =
600+
subBlock?.type === 'messages-input' && !maskedValue && !hydratedName
601+
? getTooltipDisplayValue(rawValue)
602+
: displayValue
598603

599604
return (
600605
<SubBlockRowView
601606
title={title}
602607
displayValue={displayValue}
608+
tooltipValue={tooltipValue}
603609
isMonospace={isMonospaceField}
604610
variant={variant}
605611
icon={icon}

apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/block/block.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { resolveSelectedTriggerId } from '@/lib/workflows/blocks/canvas-trigger-sentence'
2020
import {
2121
getDisplayValue,
22+
getTooltipDisplayValue,
2223
hasDisplayableRowValue,
2324
resolveDropdownLabel,
2425
resolveSkillsLabel,
@@ -556,6 +557,16 @@ function WorkflowPreviewBlockInner({ data }: NodeProps<WorkflowPreviewBlockData>
556557
workflowMap,
557558
workflowLabelsReady
558559
)
560+
const tooltipValue =
561+
subBlock.type === 'messages-input'
562+
? resolvePreviewDisplayValue(
563+
getTooltipDisplayValue(rawValue),
564+
subBlock,
565+
rawValue,
566+
workflowMap,
567+
workflowLabelsReady
568+
)
569+
: displayValue
559570
/* The preview has no hooks, so a selector it cannot hydrate comes
560571
back as the `-` sentinel. That reads as noise mid-sentence, so
561572
hand the slot back and let its noun stand in instead. */
@@ -564,6 +575,7 @@ function WorkflowPreviewBlockInner({ data }: NodeProps<WorkflowPreviewBlockData>
564575
<SubBlockRowView
565576
title={subBlock.title ?? subBlock.id}
566577
displayValue={displayValue}
578+
tooltipValue={tooltipValue}
567579
variant='inline-value'
568580
/>
569581
)

apps/sim/lib/workflows/subblocks/display.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ vi.mock('@/blocks', () => ({
1313

1414
import {
1515
getDisplayValue,
16+
getTooltipDisplayValue,
1617
resolveDropdownLabel,
1718
resolveFilterFieldLabel,
1819
resolveSandboxLabel,
@@ -287,4 +288,22 @@ describe('getDisplayValue', () => {
287288
).toBe('one, two +1')
288289
expect(getDisplayValue(['a', 'b'])).toBe('a, b')
289290
})
291+
292+
it('keeps message previews compact while tooltips retain the full first message', () => {
293+
const content = `You are a research assistant. ${'Keep every instruction. '.repeat(4)}`.trim()
294+
const messages = [{ role: 'system', content }]
295+
const serializedMessages = JSON.stringify(messages)
296+
297+
expect(getDisplayValue(messages)).toBe(`${content.slice(0, 50)}...`)
298+
expect(getTooltipDisplayValue(messages)).toBe(content)
299+
expect(getDisplayValue(serializedMessages)).toBe(`${content.slice(0, 50)}...`)
300+
expect(getTooltipDisplayValue(serializedMessages)).toBe(content)
301+
})
302+
303+
it('keeps long plain strings complete for both display and tooltip use', () => {
304+
const code = `const result = ${'computeValue() + '.repeat(6)}0; return result;`
305+
306+
expect(getDisplayValue(code)).toBe(code)
307+
expect(getTooltipDisplayValue(code)).toBe(code)
308+
})
290309
})

apps/sim/lib/workflows/subblocks/display.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,26 @@ export const getDisplayValue = (value: unknown): string => {
311311
return stringValue.trim().length > 0 ? stringValue : '-'
312312
}
313313

314+
/**
315+
* Formats the full value shown by a collapsed-row tooltip.
316+
*
317+
* Message arrays keep a compact first-message preview in {@link getDisplayValue},
318+
* but their tooltip needs the complete first-message content. Other values keep
319+
* the same resolved display text so selector labels and structured summaries do
320+
* not change semantics.
321+
*/
322+
export const getTooltipDisplayValue = (value: unknown): string => {
323+
const parsedValue = tryParseJson(value)
324+
325+
if (isMessagesArray(parsedValue)) {
326+
const firstMessage = parsedValue[0]
327+
if (!firstMessage?.content || firstMessage.content.trim() === '') return '-'
328+
return firstMessage.content.trim()
329+
}
330+
331+
return getDisplayValue(value)
332+
}
333+
314334
/**
315335
* Whether a collapsed-node row has a meaningful value to display.
316336
* Rows whose value renders as the empty placeholder are hidden from the

packages/workflow-renderer/src/lib/overflow-span.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { FloatingTooltip, isTextClipped, useFloatingTooltip } from '@sim/emcn'
33

44
interface OverflowSpanProps {
55
value: string
6+
/** Full tooltip label when the visible value was shortened before rendering. */
7+
tooltipValue?: string
68
className: string
79
/**
810
* Decorated rendering of `value` — the same characters, wrapped. Used to mark
@@ -19,15 +21,18 @@ interface OverflowSpanProps {
1921
* attribute here: on the canvas it pops the browser's raw, unstyled tooltip
2022
* with the full untruncated value (including raw code/JSON) over the graph.
2123
*/
22-
export function OverflowSpan({ value, className, children }: OverflowSpanProps) {
23-
const { state, handlers } = useFloatingTooltip(isTextClipped)
24+
export function OverflowSpan({ value, tooltipValue, className, children }: OverflowSpanProps) {
25+
const resolvedTooltipValue = tooltipValue ?? value
26+
const { state, handlers } = useFloatingTooltip(
27+
(target) => resolvedTooltipValue !== value || isTextClipped(target)
28+
)
2429

2530
return (
2631
<>
2732
<span className={className} {...handlers}>
2833
{children ?? value}
2934
</span>
30-
<FloatingTooltip label={value} state={state} />
35+
<FloatingTooltip label={resolvedTooltipValue} state={state} />
3136
</>
3237
)
3338
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, describe, expect, it } from 'vitest'
7+
import { SubBlockRowView } from './sub-block-row-view'
8+
9+
let host: HTMLDivElement | null = null
10+
let root: Root | null = null
11+
12+
function mount(element: React.ReactElement): HTMLDivElement {
13+
host = document.createElement('div')
14+
document.body.appendChild(host)
15+
root = createRoot(host)
16+
act(() => root?.render(element))
17+
return host
18+
}
19+
20+
function hover(element: HTMLElement): void {
21+
act(() => {
22+
element.dispatchEvent(
23+
new MouseEvent('pointerover', { bubbles: true, clientX: 100, clientY: 100 })
24+
)
25+
})
26+
}
27+
28+
afterEach(() => {
29+
act(() => root?.unmount())
30+
host?.remove()
31+
document.body.querySelectorAll('[data-native-surface-overlay]').forEach((node) => node.remove())
32+
host = null
33+
root = null
34+
})
35+
36+
describe('SubBlockRowView tooltip values', () => {
37+
it('shows a full tooltip for an upstream-truncated inline value', () => {
38+
const compactValue = 'You are a research assistant. Keep every instruction...'
39+
const fullValue =
40+
'You are a research assistant. Keep every instruction, constraint, and output requirement.'
41+
const container = mount(
42+
<SubBlockRowView
43+
title='Messages'
44+
displayValue={compactValue}
45+
tooltipValue={fullValue}
46+
variant='inline-value'
47+
/>
48+
)
49+
50+
expect(container.textContent).toBe(compactValue)
51+
52+
const trigger = container.querySelector<HTMLElement>('.truncate')
53+
if (!trigger) throw new Error('inline tooltip trigger not found')
54+
hover(trigger)
55+
56+
expect(document.body.querySelector('[data-native-surface-overlay]')?.textContent).toBe(
57+
fullValue
58+
)
59+
})
60+
})

packages/workflow-renderer/src/workflow-block/sub-block-row-view.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface SubBlockRowViewProps {
1414
title: string
1515
/** Resolved display value on the right; `undefined` hides the value span. */
1616
displayValue?: string
17+
/** Full value for tooltip-only disclosure when `displayValue` is compact. */
18+
tooltipValue?: string
1719
/** Render the value in a monospace font (e.g. filter expressions). */
1820
isMonospace?: boolean
1921
/**
@@ -45,6 +47,7 @@ export interface SubBlockRowViewProps {
4547
export function SubBlockRowView({
4648
title,
4749
displayValue,
50+
tooltipValue,
4851
isMonospace,
4952
icon: Icon,
5053
variant = 'row',
@@ -54,6 +57,7 @@ export function SubBlockRowView({
5457
<InlineChip>
5558
<OverflowSpan
5659
value={displayValue ?? title}
60+
tooltipValue={tooltipValue}
5761
className={cn('min-w-0 truncate', isMonospace && 'font-mono')}
5862
/>
5963
</InlineChip>
@@ -64,6 +68,7 @@ export function SubBlockRowView({
6468
return (
6569
<OverflowSpan
6670
value={displayValue ?? title}
71+
tooltipValue={tooltipValue}
6772
className={cn(
6873
'min-w-0 truncate text-sm',
6974
variant === 'statement-primary'
@@ -80,6 +85,7 @@ export function SubBlockRowView({
8085
<Icon className='size-[14px] flex-shrink-0 text-[var(--text-icon)]' />
8186
<OverflowSpan
8287
value={displayValue ?? '-'}
88+
tooltipValue={tooltipValue}
8389
className={cn(
8490
'min-w-0 flex-1 truncate text-left text-[var(--text-primary)] text-sm',
8591
isMonospace && 'font-mono'

0 commit comments

Comments
 (0)