cartographer / ui /src /components /Message.jsx
umanggarg's picture
design: premium UI polish + agent reliability fixes
34ceb36
import { useState, useCallback, Suspense, lazy, forwardRef } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import "katex/dist/katex.min.css";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import SourceCard from "./SourceCard";
// Lazy-load MermaidBlock β€” deferred so mermaid.js doesn't bloat the initial bundle.
const MermaidBlock = lazy(() => import("./MermaidBlock"));
// ReactMarkdown renders fenced code blocks as <pre><code>...</code></pre>.
// If we override only `code`, ReactMarkdown wraps the whole thing in a <p>,
// giving <p><pre>...</pre></p> β€” invalid HTML (pre can't be inside p).
//
// Fix: override `pre` to render just its children (no wrapper), so
// SyntaxHighlighter's own <pre> is the only one. Then in `code` we check
// whether it has a language class (block code) or not (inline code).
const mdComponents = {
// Wrap tables in a scrollable container so wide tables don't wrap cells
table({ children }) {
return <div style={{ overflowX: "auto", margin: "12px 0" }}><table style={{ margin: 0 }}>{children}</table></div>;
},
// Strip the <pre> wrapper β€” SyntaxHighlighter adds its own
pre({ children }) {
return <>{children}</>;
},
code({ className, children, ...props }) {
const lang = /language-(\w+)/.exec(className || "")?.[1];
if (lang === "diagram" || lang === "mermaid") {
// Agent drew a diagram β€” render as SVG via mermaid.js.
// We intercept both "diagram" (our custom tag) and "mermaid" (model's natural tag).
return (
<Suspense fallback={
<div style={{ padding: "12px 0", color: "var(--muted)", fontSize: 12 }}>
<span className="spinner" style={{ marginRight: 8 }} /> Rendering diagram…
</div>
}>
<MermaidBlock mermaid={String(children).replace(/\n$/, "")} />
</Suspense>
);
}
if (lang) {
// Block code with a language tag β†’ syntax-highlighted
return (
<SyntaxHighlighter
language={lang}
style={oneDark}
customStyle={{ fontSize: 13, background: '#06060F', borderRadius: 8, border: '1px solid rgba(255,255,255,0.07)', borderLeft: '2px solid rgba(91,143,249,0.50)', margin: '10px 0' }}
>
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
);
}
// Inline code β†’ plain <code>
return <code className={className} {...props}>{children}</code>;
},
};
// Thought bubble β€” shows the LLM's reasoning before a tool call.
// isActive = this is the currently-streaming thought (last item while streaming).
// Past thoughts (agent already moved on) collapse to a one-liner β€” click to expand.
function AgentThought({ text, isActive }) {
const [expanded, setExpanded] = useState(false);
// Shared node β€” same β—‹ β†’ structure as tool steps so both rows align
const node = (
<div className="agent-step-node">
<span className="agent-step-dot thought-dot" />
<span className="agent-step-arrow">β†’</span>
</div>
);
// While streaming, show the full thought text (no collapse, no chevron)
if (isActive) {
return (
<div className="agent-thought">
{node}
{/* agent-thought-body mirrors agent-step-body margin so text aligns with tool step content */}
<div className="agent-thought-body">
<div className="agent-thought-text">{text}</div>
</div>
</div>
);
}
// Past thought β€” one-line collapsed by default, click to expand
const preview = text.length > 120 ? text.slice(0, 120) + "…" : text;
return (
<div
className={`agent-thought agent-thought-past${expanded ? " agent-thought-open" : ""}`}
onClick={() => setExpanded(v => !v)}
>
{node}
<div className="agent-thought-body">
<div className="agent-thought-header">
<div className="agent-thought-text">
{expanded ? text : preview}
</div>
<span className="agent-thought-chevron">
<svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
{expanded ? <path d="m4 6 4 4 4-4"/> : <path d="m6 4 4 4-4 4"/>}
</svg>
</span>
</div>
</div>
</div>
);
}
// Convert tool+input into a short human-readable label shown in the step header.
// Reads like a sentence fragment so the trace feels like watching the agent think,
// not like reading a JSON dump.
function formatStepQuery(tool, input) {
if (!input) return "";
switch (tool) {
case "search_code": return input.query || JSON.stringify(input);
case "search_symbol": return input.symbol_name || JSON.stringify(input);
case "list_files": return input.path ? `${input.repo}/${input.path}` : (input.repo || JSON.stringify(input));
case "find_callers": return input.function_name || JSON.stringify(input);
case "get_file_chunk": return input.filepath
? `${input.filepath} (L${input.start_line}–${input.end_line})`
: JSON.stringify(input);
case "read_file": return input.filepath || JSON.stringify(input);
case "note": return input.key ? `${input.key}: ${input.value}` : JSON.stringify(input);
case "recall_notes": return "checking notes";
case "trace_calls": return input.symbol_name || JSON.stringify(input);
default: return input.query || input.name || JSON.stringify(input);
}
}
// Individual agent step β€” renders as a node in the connected timeline chain.
//
// Collapsed by default once a step is no longer the active one.
// isActive = this step is currently executing (isLast && streaming).
// Clicking a completed (non-active) step toggles its output open/closed.
function AgentStep({ step, isLast, icon, streaming }) {
const isActive = isLast && streaming;
const isPending = !step.output && isActive;
// manualExpand lets users re-open a completed step; resets when step becomes active again
const [manualExpand, setManualExpand] = useState(false);
const showOutput = isActive || manualExpand;
const isLong = step.output && step.output.length > 300;
const [outputExpanded, setOutputExpanded] = useState(false);
const toggle = () => {
if (!isActive) setManualExpand(v => !v);
};
return (
<div className={`agent-step ${step.output ? "done" : "pending"}${isLast ? " last" : ""}${!showOutput && step.output ? " collapsed" : ""}`}>
{/* Node dot on the vertical line + arrow connector */}
<div className="agent-step-node">
<span className="agent-step-dot" />
<span className="agent-step-arrow">β†’</span>
</div>
{/* Step body */}
<div className="agent-step-body">
<div
className="agent-step-header"
onClick={toggle}
style={{ cursor: !isActive && step.output ? "pointer" : "default" }}
>
<span className="agent-step-icon">{icon}</span>
<span className="agent-step-tool">{step.tool}</span>
<span className="agent-step-query">
{formatStepQuery(step.tool, step.input)}
</span>
{isPending && <span className="spinner" style={{ marginLeft: "auto", flexShrink: 0, width: 10, height: 10 }} />}
{!isActive && step.output && (
<span className="agent-step-chevron" style={{ marginLeft: "auto", opacity: 0.4 }}>
<svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
{manualExpand ? <path d="m4 6 4 4 4-4"/> : <path d="m6 4 4 4-4 4"/>}
</svg>
</span>
)}
</div>
{showOutput && step.output && (
<>
<div
className={`agent-step-output${outputExpanded ? " expanded" : isLong ? " clipped" : ""}`}
onClick={() => isLong && !outputExpanded && setOutputExpanded(true)}
>
{step.output}
</div>
{isLong && !outputExpanded && (
<button className="agent-step-expand" onClick={() => setOutputExpanded(true)}>
Show full output ↓
</button>
)}
{isLong && outputExpanded && (
<button className="agent-step-expand" onClick={() => setOutputExpanded(false)}>
Collapse ↑
</button>
)}
</>
)}
</div>
</div>
);
}
// ToolCallTrace shows the agent's reasoning steps as a connected timeline β€”
// visually similar to how Claude Code shows "Agent β†’ Bash β†’ Read" with
// vertical lines connecting each step.
//
// DURING streaming: always expanded so user can watch the agent think live.
// AFTER completion: collapsible via the toggle header.
function ToolCallTrace({ steps, streaming, iterations, model }) {
const [expanded, setExpanded] = useState(true);
if (!steps || steps.length === 0) return null;
// Tool name β†’ icon SVG for clean visual scanning (no emoji)
const toolIcon = {
search_code: <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><circle cx="6.5" cy="6.5" r="4.5" stroke="currentColor" strokeWidth="1.5"/><path d="M10 10l3.5 3.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>,
search_symbol: <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M3 4h10M3 8h7M3 12h4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/><circle cx="12" cy="11" r="2.5" stroke="currentColor" strokeWidth="1.3"/><path d="M14 13l1.5 1.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg>,
list_files: <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><rect x="1" y="2" width="14" height="12" rx="1.5" stroke="currentColor" strokeWidth="1.5"/><path d="M4 6h8M4 9h5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg>,
get_file_chunk: <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><rect x="3" y="1" width="8" height="10" rx="1" stroke="currentColor" strokeWidth="1.5"/><path d="M5 5h4M5 7h3M9 1v3h3" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round"/></svg>,
read_file: <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><rect x="2" y="1" width="9" height="12" rx="1" stroke="currentColor" strokeWidth="1.5"/><path d="M4 5h5M4 7h5M4 9h3" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round"/><path d="M11 8l3 3-3 3" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/></svg>,
find_callers: <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><circle cx="4" cy="4" r="2" stroke="currentColor" strokeWidth="1.4"/><circle cx="12" cy="12" r="2" stroke="currentColor" strokeWidth="1.4"/><path d="M6 4h2a2 2 0 012 2v2" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>,
note: <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M3 2h10a1 1 0 011 1v8l-3 3H3a1 1 0 01-1-1V3a1 1 0 011-1z" stroke="currentColor" strokeWidth="1.4"/><path d="M11 11v3l3-3h-3z" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round"/><path d="M4 5h6M4 7h6M4 9h3" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round"/></svg>,
recall_notes: <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.4"/><path d="M8 5v3.5l2.5 1.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>,
trace_calls: <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><circle cx="3" cy="8" r="2" stroke="currentColor" strokeWidth="1.4"/><circle cx="13" cy="4" r="2" stroke="currentColor" strokeWidth="1.4"/><circle cx="13" cy="12" r="2" stroke="currentColor" strokeWidth="1.4"/><path d="M5 8h3M8 8L11 5M8 8l3 3" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg>,
};
const defaultIcon = <svg width="12" height="12" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="5" stroke="currentColor" strokeWidth="1.5"/><path d="M8 5v3l2 1" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>;
// Compute the index of the last non-thought step so we can pass isLast correctly
const lastToolIdx = steps.reduce((acc, s, i) => s.type !== "thought" ? i : acc, -1);
const stepsEl = (
<div className="agent-trace-steps">
{/* Vertical connector line running the full height */}
<div className="agent-trace-line" />
{steps.map((step, i) => {
if (step.type === "thought") {
// A thought is "active" (shown in full) only while it's the last item
// and the agent is still streaming β€” once the agent emits a tool call
// after it, the thought is "past" and collapses to a one-liner.
const isActiveThought = streaming && i === steps.length - 1;
return <AgentThought key={i} text={step.text} isActive={isActiveThought} />;
}
return (
<AgentStep
key={i}
step={step}
isLast={i === lastToolIdx}
icon={toolIcon[step.tool] || defaultIcon}
streaming={streaming}
/>
);
})}
</div>
);
return (
<div className={`agent-trace${streaming ? " live" : ""}`}>
<button
className="agent-trace-toggle"
onClick={() => !streaming && setExpanded(v => !v)}
aria-expanded={expanded}
style={{ cursor: streaming ? "default" : "pointer" }}
>
{/* "Agent" node β€” the root of the chain */}
<span className="agent-trace-root-dot" />
<span className="agent-trace-root-label">Agent</span>
{streaming
? <span className="spinner" style={{ marginLeft: 6, width: 10, height: 10 }} />
: (() => {
// Use backend's authoritative iteration count when available.
// steps.length = tool calls only; iterations = full ReAct turns
// (includes the final answer turn, so it's always >= steps.length).
const count = iterations ?? steps.length;
const label = iterations ? "iteration" : "tool call";
return (
<>
<span className="agent-trace-count">{count} {label}{count !== 1 ? "s" : ""}</span>
{model && (
<span style={{ opacity: 0.4, fontStyle: "italic", fontSize: 10.5, marginLeft: 6 }}
title={`Model: ${model}`}>
{model.split("/").pop()}
</span>
)}
</>
);
})()
}
{!streaming && (
<span className="agent-trace-chevron">
<svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
{expanded ? <path d="m4 6 4 4 4-4"/> : <path d="m6 4 4 4-4 4"/>}
</svg>
</span>
)}
</button>
{expanded && stepsEl}
{/* When collapsed, show the first thought as a one-line summary so users can still see the agent's reasoning intent */}
{!expanded && !streaming && (() => {
const firstThought = steps.find(s => s.type === "thought");
if (!firstThought) return null;
return (
<div className="agent-trace-thought-summary" title={firstThought.text}>
"{firstThought.text.length > 120 ? firstThought.text.slice(0, 120) + "…" : firstThought.text}"
</div>
);
})()}
</div>
);
}
// ConfidenceBadge β€” rendered after model-based grading completes.
// high = all claims confirmed in sources β†’ green check (shown in pipeline bar only)
// medium = mostly supported, minor extrapolation β†’ amber warning
// low = claims not backed by sources β†’ red warning
const CONFIDENCE_CONFIG = {
high: { color: "#10b981", bg: "rgba(16,185,129,0.10)", icon: "βœ“", label: "High confidence" },
medium: { color: "#f59e0b", bg: "rgba(245,158,11,0.10)", icon: "◐", label: "Medium confidence" },
low: { color: "#ef4444", bg: "rgba(239,68,68,0.10)", icon: "⚠", label: "Low confidence" },
};
function ConfidenceBadge({ grade }) {
const cfg = CONFIDENCE_CONFIG[grade.confidence] || CONFIDENCE_CONFIG.medium;
return (
<div style={{
display: "inline-flex", alignItems: "flex-start", gap: 6,
marginTop: 8,
padding: "5px 10px",
background: cfg.bg,
border: `1px solid ${cfg.color}33`,
borderRadius: 6,
fontSize: 11.5,
fontFamily: "var(--mono)",
color: cfg.color,
maxWidth: "100%",
}}>
<span style={{ flexShrink: 0, marginTop: 1 }}>{cfg.icon} {cfg.label}</span>
{grade.note && (
<span style={{ color: "var(--text-2)", fontFamily: "inherit", fontSize: 11 }}>
β€” {grade.note}
</span>
)}
</div>
);
}
// Copy-answer button β€” appears on hover over the assistant message.
// Copies the raw markdown text so developers can paste it into docs/code.
function CopyAnswerButton({ content }) {
const [copied, setCopied] = useState(false);
const handleCopy = useCallback(() => {
navigator.clipboard.writeText(content).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1800);
});
}, [content]);
return (
<button
className="copy-answer-btn"
onClick={handleCopy}
title={copied ? "Copied!" : "Copy answer"}
aria-label="Copy answer to clipboard"
>
{copied
? <svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/></svg>
: <svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25z"/></svg>
}
</button>
);
}
const Message = forwardRef(function Message({ msg, onDiagramThis, onRetry, showRepo = false }, ref) {
const isUser = msg.role === "user";
return (
<div ref={ref} className={`message ${msg.role}`}>
{isUser ? (
<div className="bubble">{msg.content}</div>
) : (
<>
{/* Assistant avatar β€” ✦ for agent responses, code icon for RAG.
This matches the ✦ badge on agent sessions in the sidebar,
making the visual language consistent: ✦ = agent mode. */}
<div className="message-avatar assistant" aria-hidden="true">
{msg.mode === "agent"
? /* Agent mode β€” ✦ sparkle signals autonomous reasoning / ReAct loop */
<span style={{ fontSize: 14, lineHeight: 1, color: "white", opacity: 0.9 }}>✦</span>
: /* RAG mode β€” compass brand mark, signals "Cartographer navigating the code" */
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M12 2 L14.5 7 L12 12 L9.5 7 Z" fill="white" opacity="0.95"/>
<path d="M12 22 L13.5 17 L12 12 L10.5 17 Z" fill="white" opacity="0.45"/>
<path d="M22 12 L17 10.5 L12 12 L17 13.5 Z" fill="white" opacity="0.45"/>
<path d="M2 12 L7 10.5 L12 12 L7 13.5 Z" fill="white" opacity="0.45"/>
<circle cx="12" cy="12" r="1.5" fill="white"/>
</svg>
}
</div>
{/* All assistant content in a column wrapper */}
<div className="message-content">
{/* Mode tag β€” always shown on assistant responses so chat history is scannable.
Uses msg.mode (set explicitly at creation, never mutated) so async callbacks
updating queryType/phase/model can never flip the label to the wrong mode. */}
{/* Mode tag β€” RAG only. Agent already has the ToolCallTrace header showing
"Agent Β· N iterations Β· model", so a second label would be redundant. */}
{msg.mode === "rag" && (
<div className="msg-mode-tag">
<span className="msg-mode-icon">β—Ž</span>
<span className="msg-mode-label">RAG</span>
{msg.queryType && (
<span className="msg-mode-detail">Β· {msg.queryType}</span>
)}
{msg.model && (
<span className="msg-mode-model" title={`Model: ${msg.model}`}>
Β· {msg.model.split("/").pop()}
</span>
)}
</div>
)}
{/* Agent reasoning trace */}
{msg.toolCalls && msg.toolCalls.length > 0 && (
<ToolCallTrace steps={msg.toolCalls} streaming={msg.streaming} iterations={msg.iterations} model={msg.model} />
)}
{/* "Thinking…" shown before first tool call in agent mode */}
{msg.streaming && msg.currentTool === null && !msg.content && (!msg.toolCalls || msg.toolCalls.length === 0) && !msg.phase && (
<div className="agent-thinking">
<span className="spinner" role="status" aria-label="Thinking" />
Thinking…
</div>
)}
{/* RAG retrieval phase indicator β€” makes the invisible retrieval step visible.
"searching" = waiting for vector search to return sources.
"generating" = sources received, LLM is now streaming the answer. */}
{msg.streaming && msg.phase && (
<div className={`stream-phase stream-phase--${msg.phase}`}>
<span className="stream-phase-dot" />
{msg.phase === "searching"
? "Searching code…"
: `Found ${msg.sourceCount ?? "?"} source${msg.sourceCount !== 1 ? "s" : ""} Β· Generating answer…`
}
</div>
)}
{/* Rate-limit countdown banner β€” shown instead of a hard error */}
{msg.rateLimited && (
<div className="rate-limit-banner">
<span className="rate-limit-spinner" aria-hidden="true" />
<span className="rate-limit-text">{msg.content}</span>
{onRetry && msg.retryQuestion && (
<button
className="rate-limit-retry-btn"
onClick={() => onRetry(msg.retryQuestion)}
>
Retry now
</button>
)}
</div>
)}
{/* Answer bubble */}
<div className="bubble" style={{ position: "relative", display: msg.rateLimited ? "none" : undefined }}>
<ReactMarkdown components={mdComponents} remarkPlugins={[remarkGfm, remarkMath]} rehypePlugins={[rehypeKatex]}>
{msg.content || " "}
</ReactMarkdown>
{/* Show cursor whenever streaming, not just when no tool active */}
{msg.streaming && <span className="cursor" aria-hidden="true" />}
{/* Copy-answer button β€” visible on hover; lets devs paste the answer */}
{!msg.streaming && msg.content && <CopyAnswerButton content={msg.content} />}
</div>
{/* Pipeline provenance β€” shows every retrieval stage that fired for this answer.
Positioned HERE (before sources) so it's immediately visible after the answer,
not buried below N source cards. Quality features only shown when they ran. */}
{!msg.streaming && msg.queryType && !msg.iterations && (
<div className="pipeline-provenance">
{msg.pipeline?.hyde && (
<>
<span className="pipeline-stage pipeline-quality" title="Hypothetical Document Embeddings: a code snippet was generated from your question and used for retrieval instead of the raw query text">
HyDE
</span>
<span className="pipeline-sep">β†’</span>
</>
)}
{msg.pipeline?.expanded > 0 && (
<>
<span className="pipeline-stage pipeline-quality" title={`Query Expansion: ${msg.pipeline.expanded} alternative phrasings were searched and merged with RRF`}>
+{msg.pipeline.expanded} expansions
</span>
<span className="pipeline-sep">β†’</span>
</>
)}
<span className="pipeline-stage" title={`${msg.queryType === "hybrid" ? "Hybrid: dense semantic vectors + BM25 keyword search, fused with Reciprocal Rank Fusion" : msg.queryType === "semantic" ? "Dense semantic search: nearest-neighbour lookup in 768-dim embedding space" : "BM25 keyword search: exact and fuzzy term matching"}`}>
<svg width="9" height="9" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"><circle cx="6.5" cy="6.5" r="4.5"/><path d="M10 10l3.5 3.5"/></svg>
{msg.queryType} search
</span>
<span className="pipeline-sep">β†’</span>
<span
className="pipeline-stage"
title={msg.pipeline?.reranker === "cohere" ? "Cohere rerank-v3.5: API cross-encoder re-scores every candidate against your question for maximum precision" : "Local ms-marco cross-encoder: re-scores candidates locally without an API call"}
>
{msg.pipeline?.reranker === "cohere" ? "cohere re-ranked" : "re-ranked"}
</span>
<span className="pipeline-sep">β†’</span>
{msg.pipeline?.parent_docs > 0 && (
<>
<span className="pipeline-stage pipeline-quality" title={`Parent-document retrieval: ${msg.pipeline.parent_docs} function chunk${msg.pipeline.parent_docs !== 1 ? "s" : ""} expanded to enclosing class for richer LLM context`}>
↕ {msg.pipeline.parent_docs} expanded
</span>
<span className="pipeline-sep">β†’</span>
</>
)}
<span className="pipeline-stage" title={`${msg.sources?.length ?? 0} code chunk${(msg.sources?.length ?? 0) !== 1 ? "s" : ""} were retrieved and passed as context to the LLM`}>{msg.sources?.length ?? 0} source{(msg.sources?.length ?? 0) !== 1 ? "s" : ""}</span>
<span className="pipeline-sep">β†’</span>
<span className="pipeline-stage" title="The LLM generated this answer using only the retrieved sources as context β€” it cannot see code outside these chunks">generated</span>
{msg.model && (
<>
<span className="pipeline-sep">Β·</span>
<span
className="pipeline-stage"
style={{ opacity: 0.45, fontStyle: "italic" }}
title={`Model: ${msg.model}`}
>
{msg.model.split("/").pop()}
</span>
</>
)}
{msg.grade && msg.grade.confidence !== "unknown" && (
<>
<span className="pipeline-sep">β†’</span>
<span className={`pipeline-stage pipeline-grade-${msg.grade.confidence}`}>
{msg.grade.confidence === "high" ? "βœ“" : msg.grade.confidence === "low" ? "⚠" : "◐"} {msg.grade.confidence}
</span>
</>
)}
</div>
)}
{/* Badges + Sources β€” query type shown as sources header for context */}
{/* (agent iteration count is shown in the ToolCallTrace header above) */}
{msg.sources && msg.sources.length > 0 && !msg.streaming && (
<div className="sources">
<div className="sources-header">
{msg.sources.length} source{msg.sources.length > 1 ? "s" : ""}
{msg.queryType && !msg.iterations && (
<span className="query-type-badge" style={{ marginLeft: 8 }}>{msg.queryType}</span>
)}
</div>
{msg.sources.map((s, i) => (
<SourceCard key={i} source={s} index={i + 1} showRepo={showRepo} />
))}
{/* "Diagram this β†’" button β€” switches to diagram tab with focused-files context */}
{onDiagramThis && (
<button
className="diagram-this-btn"
onClick={() => onDiagramThis(msg.sources)}
title="Switch to Diagram tab and highlight the files cited in this answer"
>
Diagram this β†’
</button>
)}
</div>
)}
{/* Query type badge when no sources (e.g. factual answer with no retrieved chunks) */}
{!msg.streaming && !msg.iterations && msg.queryType && !(msg.sources?.length > 0) && (
<span className="query-type-badge">{msg.queryType}</span>
)}
{/* Standalone confidence badge for medium/low β€” shows the note text */}
{msg.grade && msg.grade.confidence !== "unknown" && msg.grade.confidence !== "high" && (
<ConfidenceBadge grade={msg.grade} />
)}
</div>
</>
)}
</div>
);
});
export default Message;