Skip to content

feat(ai-conversations): use conversations endpoint#106367

Merged
obostjancic merged 10 commits intomasterfrom
ogi/tet-1720-call-new-conversation-endpoint
Jan 20, 2026
Merged

feat(ai-conversations): use conversations endpoint#106367
obostjancic merged 10 commits intomasterfrom
ogi/tet-1720-call-new-conversation-endpoint

Conversation

@obostjancic
Copy link
Member

@obostjancic obostjancic commented Jan 15, 2026

closes TET-1720: Call new conversation endpoint

  • calls new conversation details endpoint instead of fetching multiple traces
  • moves AI span list behind a tab
  • aligns messages to resemble a chat
CleanShot 2026-01-15 at 15 15 42

@obostjancic obostjancic requested review from a team as code owners January 15, 2026 14:17
@linear
Copy link

linear bot commented Jan 15, 2026

@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Jan 15, 2026
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Comment on lines 294 to 306

interface CompressedTimeBounds extends TraceBounds {
getCompressedTimestamp: (timestamp: number) => number;
}

const MAX_GAP_SECONDS = 30;
const COMPRESSED_GAP_SECONDS = 1;

/**
* Compresses large time gaps between spans to make the timeline more readable.
* Gaps larger than MAX_GAP_SECONDS are compressed to COMPRESSED_GAP_SECONDS.
*/
function getCompressedTimeBounds(nodes: AITraceSpanNode[]): CompressedTimeBounds {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a few problems with this function:

  1. Too complex and costly for what it does as it has to iterate the segments on each call of getCompressedTimestamp. Instead we could just loop over it once and store the new timestamp for each span in a map.
  2. It ignores nested spans, simply summing up all durations and inflating the total time span. This results in the last span not ending the timeline (see screenshot).
  3. A few more comments on such a complex logic would help me figure out what it does 😅
Image

Something like this could better suit your needs:

function getCompressedTimeBounds(nodes: AITraceSpanNode[]): CompressedTimeBounds {
  const emptyResult: CompressedTimeBounds = {
    startTime: 0,
    endTime: 0,
    duration: 0,
    compressedStartByNodeId: new Map(),
  };

  if (nodes.length === 0) {
    return emptyResult;
  }

  const sortedNodes = [...nodes]
    .filter(n => n.startTimestamp && n.endTimestamp)
    .sort((a, b) => (a.startTimestamp ?? 0) - (b.startTimestamp ?? 0));

  if (sortedNodes.length === 0) {
    return emptyResult;
  }

  const compressedStartByNodeId = new Map<string, number>();

  // Track current segment bounds
  const firstNode = sortedNodes[0]!;
  let segmentRealStart = firstNode.startTimestamp!;
  let segmentRealEnd = firstNode.endTimestamp!;
  let segmentCompressedStart = 0;

  compressedStartByNodeId.set(firstNode.id, 0);

  for (let i = 1; i < sortedNodes.length; i++) {
    const node = sortedNodes[i]!;
    const nodeStart = node.startTimestamp!;
    const nodeEnd = node.endTimestamp!;

    if (nodeStart > segmentRealEnd) {
      // Gap detected - finish current segment and start new one
      const gap = nodeStart - segmentRealEnd;
      const compressedGap = gap > MAX_GAP_SECONDS ? COMPRESSED_GAP_SECONDS : gap;
      const segmentDuration = segmentRealEnd - segmentRealStart;

      // Advance compressed time by segment duration + gap
      segmentCompressedStart += segmentDuration + compressedGap;

      // Start new segment
      segmentRealStart = nodeStart;
      segmentRealEnd = nodeEnd;
    } else {
      // Overlapping - extend current segment
      segmentRealEnd = Math.max(segmentRealEnd, nodeEnd);
    }

    // Calculate this node's compressed start
    const offsetInSegment = nodeStart - segmentRealStart;
    compressedStartByNodeId.set(node.id, segmentCompressedStart + offsetInSegment);
  }

  // Total duration is the compressed start of last segment + its duration
  const totalDuration = segmentCompressedStart + (segmentRealEnd - segmentRealStart);

  return {
    startTime: 0,
    endTime: totalDuration,
    duration: totalDuration,
    compressedStartByNodeId,
  };
}

Comment on lines 260 to 268
const TabContent = styled('div')`
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
`;

const TabContentPadded = styled(TabContent)`
padding: ${p => p.theme.space.md} ${p => p.theme.space.lg};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should rather use the design system primitives like Flex.
Same goes for the other styled components.

@obostjancic obostjancic merged commit a434070 into master Jan 20, 2026
53 checks passed
@obostjancic obostjancic deleted the ogi/tet-1720-call-new-conversation-endpoint branch January 20, 2026 15:39
@github-actions github-actions bot locked and limited conversation to collaborators Feb 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants