mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-13 17:52:38 +02:00
chat-messages: add timeline module with builder, grouping, items, and rendering.
This commit is contained in:
parent
9e451a5907
commit
48c4df822a
12 changed files with 879 additions and 0 deletions
|
|
@ -0,0 +1,3 @@
|
|||
export { ItemHeader } from "./item-header";
|
||||
export { ReasoningItem } from "./reasoning-item";
|
||||
export { ToolCallItem } from "./tool-call-item";
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import type { FC } from "react";
|
||||
import { ChainOfThoughtItem } from "@/components/prompt-kit/chain-of-thought";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { ItemStatus } from "../types";
|
||||
|
||||
/**
|
||||
* The title row + sub-bullets shared by every timeline item kind. The
|
||||
* timeline's chrome (status dot, indent, vertical line) renders to the
|
||||
* left; this fills the right column.
|
||||
*
|
||||
* Status-aware text styling matches the legacy ``StepBody`` semantics:
|
||||
* running → emphasised (font-medium foreground)
|
||||
* completed → muted
|
||||
* pending → muted/60
|
||||
* error → destructive
|
||||
* cancelled → strikethrough muted
|
||||
*
|
||||
* Sub-bullets render via ``ChainOfThoughtItem`` (reused from
|
||||
* ``components/prompt-kit/chain-of-thought``) — same component the
|
||||
* legacy ``StepBody`` used.
|
||||
*/
|
||||
export const ItemHeader: FC<{
|
||||
title: string;
|
||||
status: ItemStatus;
|
||||
items?: readonly string[];
|
||||
itemKey: string;
|
||||
}> = ({ title, status, items, itemKey }) => (
|
||||
<div className="min-w-0">
|
||||
<div
|
||||
className={cn(
|
||||
"text-sm leading-5",
|
||||
status === "running" && "text-foreground font-medium",
|
||||
status === "completed" && "text-muted-foreground",
|
||||
status === "pending" && "text-muted-foreground/60",
|
||||
status === "error" && "text-destructive",
|
||||
status === "cancelled" && "text-muted-foreground line-through"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
|
||||
{items && items.length > 0 && (
|
||||
<div className="mt-1 space-y-0.5">
|
||||
{items.map((item) => (
|
||||
<ChainOfThoughtItem key={`${itemKey}-${item}`} className="text-xs">
|
||||
{item}
|
||||
</ChainOfThoughtItem>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import type { FC } from "react";
|
||||
import type { ReasoningItem as ReasoningItemModel } from "../types";
|
||||
import { ItemHeader } from "./item-header";
|
||||
|
||||
/**
|
||||
* Renders a ``kind: "reasoning"`` row — pure agent narration with no
|
||||
* tool component beneath it. Just the shared header.
|
||||
*
|
||||
* Native ``<think>`` blocks (model-level reasoning) are NOT rendered
|
||||
* here — they live in the body via assistant-ui's ``Reasoning``
|
||||
* component.
|
||||
*/
|
||||
export const ReasoningItem: FC<{ item: ReasoningItemModel }> = ({ item }) => (
|
||||
<ItemHeader title={item.title} status={item.status} items={item.items} itemKey={item.id} />
|
||||
);
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import { getToolDisplayName } from "@/contracts/enums/toolIcons";
|
||||
import { ToolCallIdProvider, useHitlBundle } from "@/features/chat-messages/hitl";
|
||||
import { resolveItemTitle } from "../subagent-rename";
|
||||
import { adaptItemToProps, FallbackToolBody, getToolComponent } from "../tool-registry";
|
||||
import type { ToolCallItem as ToolCallItemModel } from "../types";
|
||||
import { ItemHeader } from "./item-header";
|
||||
|
||||
/**
|
||||
* Renders a ``kind: "tool-call"`` row: ``ItemHeader`` (title + items)
|
||||
* plus the resolved tool body underneath.
|
||||
*
|
||||
* Tool body is selected from the registry; unknown names fall through
|
||||
* to ``FallbackToolBody`` (which itself dispatches between HITL
|
||||
* approval cards and the default visual card based on result shape).
|
||||
*
|
||||
* Multi-approval bundle behaviour: when the HITL bundle is active, all
|
||||
* cards EXCEPT the current step are hidden so the user is paged
|
||||
* through them one at a time. Hiding is local to this row — the header
|
||||
* and the timeline chrome around it are unaffected (the row collapses
|
||||
* to its header only). The bundle's ``PagerChrome`` is mounted once
|
||||
* at the end of the timeline by ``timeline.tsx``.
|
||||
*
|
||||
* Every tool body is wrapped in ``ToolCallIdProvider`` so
|
||||
* ``useHitlDecision`` (called inside HITL approval cards) can read the
|
||||
* tool-call id from context and stage decisions in the bundle.
|
||||
*/
|
||||
export const ToolCallItem: FC<{ item: ToolCallItemModel }> = ({ item }) => {
|
||||
const bundle = useHitlBundle();
|
||||
const hideForBundle =
|
||||
bundle?.isInBundle(item.toolCallId) === true && !bundle.isCurrentStep(item.toolCallId);
|
||||
|
||||
const title = resolveItemTitle(item, getToolDisplayName);
|
||||
|
||||
const Body = getToolComponent(item.toolName) ?? FallbackToolBody;
|
||||
const props = adaptItemToProps(item);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ItemHeader title={title} status={item.status} items={item.items} itemKey={item.id} />
|
||||
{!hideForBundle && (
|
||||
<ToolCallIdProvider toolCallId={item.toolCallId}>
|
||||
<Body {...props} />
|
||||
</ToolCallIdProvider>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue