feat(chat): prompt navigator list preview, prompt filtering, shell status fix (#2211)
* feat(chat): prompt navigator list preview with prompt filtering The hover preview is now an interactive scrolling mini-list of prompts: rows render as bordered two-line cards, the highlighted row stays inside a center dead zone and the list glides only near the window edges, wheel steps the highlight, and the panel stays open when the pointer moves into it so a click can be corrected inside the list. Rail entries are filtered to real prompts: previews are built from normalized user display parts (synthetic context stripped), fully synthetic user messages are excluded, and shell-mode messages show their extracted command via the shared shell bridge helpers. * fix(chat): render shell command status transitions The injected /shell text part carries live state in shellAction, which the render-relevant part comparator ignored — a running→completed update reached the store but never re-rendered the message row until the next send. Compare shellAction command/output/status for text parts. * fix(sync): stream shell bridge part updates while running Streaming suspension keeps part updates out of the static message records while an assistant message streams, relying on the live streaming-tail path to render it. Shell-mode bridge messages are hidden from the timeline and rendered inside the user row, so they have no live path — suspension froze their output chunks and left the card without a Show output action until the run finished. Exempt shell bridges (single bash tool part parented to a synthetic shell-marker user message) from suspension; their updates arrive at command-output pace, not delta pace. * feat(chat): syntax-highlight shell command card Render the shell-mode command and its output through the shared WorkerHighlightedCode (Shiki) with bash grammar, matching the bash tool part presentation, instead of plain pre blocks.
This commit is contained in:
committed by
GitHub
parent
68f1c1efe3
commit
a1badccddd
@@ -11,6 +11,7 @@ import type { ToolPart as ToolPartType } from '@opencode-ai/sdk/v2';
|
||||
import type { StreamPhase, ToolPopupContent, AgentMentionInfo } from './types';
|
||||
import type { TurnChangedFile, TurnGroupingContext } from '../lib/turns/types';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { WorkerHighlightedCode } from '@/components/code/WorkerHighlightedCode';
|
||||
import { isEmptyTextPart, extractTextContent } from './partUtils';
|
||||
import { FadeInOnReveal } from './FadeInOnReveal';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -278,6 +279,8 @@ const UserSubtaskPart: React.FC<{ part: SubtaskPartLike }> = ({ part }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const SHELL_CODE_TAG_STYLE: React.CSSProperties = { background: 'transparent', backgroundColor: 'transparent' };
|
||||
|
||||
const UserShellActionPart: React.FC<{ part: ShellActionPartLike }> = ({ part }) => {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const [copiedOutput, setCopiedOutput] = React.useState(false);
|
||||
@@ -335,9 +338,14 @@ const UserShellActionPart: React.FC<{ part: ShellActionPartLike }> = ({ part })
|
||||
</div>
|
||||
|
||||
{command ? (
|
||||
<pre className="typography-meta mt-1.5 overflow-x-auto whitespace-pre-wrap break-words text-foreground/90 font-mono">
|
||||
{command}
|
||||
</pre>
|
||||
<div className="typography-meta mt-1.5 overflow-x-auto font-mono">
|
||||
<WorkerHighlightedCode
|
||||
language="bash"
|
||||
code={command}
|
||||
codeStyle={SHELL_CODE_TAG_STYLE}
|
||||
wrap
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{hasOutput ? (
|
||||
@@ -363,9 +371,14 @@ const UserShellActionPart: React.FC<{ part: ShellActionPartLike }> = ({ part })
|
||||
</button>
|
||||
</div>
|
||||
{expanded ? (
|
||||
<pre className="typography-meta mt-1.5 max-h-56 overflow-auto whitespace-pre-wrap break-words text-foreground/85 font-mono">
|
||||
{output}
|
||||
</pre>
|
||||
<div className="typography-meta mt-1.5 max-h-56 overflow-auto font-mono text-foreground/85">
|
||||
<WorkerHighlightedCode
|
||||
language="bash"
|
||||
code={output}
|
||||
codeStyle={SHELL_CODE_TAG_STYLE}
|
||||
wrap
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -100,6 +100,18 @@ export const areRenderRelevantPartsEqual = (left: Part[], right: Part[]): boolea
|
||||
if (readPartText(leftPart) !== readPartText(rightPart)) {
|
||||
return false;
|
||||
}
|
||||
// Shell-mode user messages carry their live command state in an
|
||||
// injected `shellAction` payload on a synthetic text part; without
|
||||
// comparing it, a running→completed transition never re-renders.
|
||||
const leftShell = (leftPart as { shellAction?: { command?: unknown; output?: unknown; status?: unknown } }).shellAction;
|
||||
const rightShell = (rightPart as { shellAction?: { command?: unknown; output?: unknown; status?: unknown } }).shellAction;
|
||||
if (leftShell || rightShell) {
|
||||
if (leftShell?.command !== rightShell?.command
|
||||
|| leftShell?.output !== rightShell?.output
|
||||
|| leftShell?.status !== rightShell?.status) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user