feat: api playground, other tweaks

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-07-06 16:45:04 -07:00
parent 7bf559cd5b
commit 50f2d095aa
104 changed files with 5482 additions and 244 deletions

View file

@ -5,10 +5,11 @@ import type { TimelineToolProps } from "./types";
* Lossless mapping ``ToolCallItem TimelineToolProps``. Pure;
* extracts only the fields tool components actually consume.
*
* ``id``, ``kind``, ``items``, ``spanId``, ``thinkingStepId`` are
* intentionally dropped they're timeline-internal concerns (React
* key, dispatch, indentation, back-correlation) that tool components
* have no reason to see.
* ``id``, ``kind``, ``spanId``, ``thinkingStepId`` are intentionally
* dropped they're timeline-internal concerns (React key, dispatch,
* indentation, back-correlation) that tool components have no reason to
* see. ``items`` is forwarded as ``progress`` so a tool body can show its
* live activity (e.g. streamed scraper progress) inside its own card.
*/
export function adaptItemToProps(item: ToolCallItem): TimelineToolProps {
return {
@ -19,5 +20,6 @@ export function adaptItemToProps(item: ToolCallItem): TimelineToolProps {
result: item.result,
langchainToolCallId: item.langchainToolCallId,
status: item.status,
progress: item.items,
};
}

View file

@ -56,10 +56,12 @@ export const DefaultFallbackCard: TimelineToolComponent = ({
result,
status,
langchainToolCallId,
progress,
}) => {
const isCancelled = status === "cancelled";
const isError = status === "error";
const isRunning = status === "running";
const liveProgress = isRunning ? (progress ?? []) : [];
const [isExpanded, setIsExpanded] = useState(isRunning);
useEffect(() => {
@ -72,10 +74,13 @@ export const DefaultFallbackCard: TimelineToolComponent = ({
[result]
);
const subtitle = useMemo(
() => (isError || isCancelled ? deriveResultMessage(result) : null),
[isError, isCancelled, result]
);
const subtitle = useMemo(() => {
if (isError || isCancelled) return deriveResultMessage(result);
// While running, surface the latest streamed activity line so progress
// is visible even when the card is collapsed.
if (isRunning && liveProgress.length > 0) return liveProgress[liveProgress.length - 1];
return null;
}, [isError, isCancelled, isRunning, liveProgress, result]);
const displayName = getToolDisplayName(toolName);
@ -196,6 +201,25 @@ export const DefaultFallbackCard: TimelineToolComponent = ({
</NestedScroll>
</div>
)}
{isRunning && liveProgress.length > 0 && (
<>
<Separator />
<div className="flex flex-col gap-1 min-w-0">
<p className="text-xs font-medium text-muted-foreground">Progress</p>
<div className="flex flex-col gap-1.5 rounded-md bg-muted/40 px-3 py-2">
{liveProgress.map((line) => (
<div
key={line}
className="flex items-center gap-2 text-xs text-foreground/80"
>
<Spinner size="sm" className="shrink-0 text-primary" />
<span className="min-w-0 wrap-break-word">{line}</span>
</div>
))}
</div>
</div>
</>
)}
{!isCancelled && result !== undefined && (
<>
<Separator />

View file

@ -22,6 +22,13 @@ export interface TimelineToolProps {
result?: unknown;
langchainToolCallId?: string;
status: ItemStatus;
/**
* Live activity lines for this tool (the joined thinking-step items).
* While ``status === "running"`` these are the streamed progress updates
* (e.g. scraper ``scraper_progress`` events) so the fallback card can show
* output inside its dropdown instead of a static "input only" view.
*/
progress?: readonly string[];
}
/**