import { ARTIFACT_PREVIEW, ARTIFACT_ROW, CARD_GAP, FILE_HEADER, FILE_HEADER_PAD_Y, FILE_PREVIEW, FILE_ROW, SHOW_MORE_HEIGHT, } from "./layout"; import type { Artifact, EditedFile } from "../../lib/timeline"; import { cn } from "../../lib/utils"; import { Button } from "../../ui/button"; import { buttonVariants } from "../../lib/button-variants"; import { IconChevronDownMicro, IconExternal, IconFileDiff, IconFileText, IconHash, IconIssue, IconMessage, IconPullRequest, } from "../../icons"; const VM_PROJECT_PREFIX = "/workspace/project/"; type TurnSummaryProps = { id: string; artifacts: Artifact[]; files: EditedFile[]; artifactsExpanded: boolean; filesExpanded: boolean; onToggleArtifacts: () => void; onToggleFiles: () => void; onFileOpen?: (file: string) => void; onEditedFileOpen?: (file: string) => void; onLink?: (href: string) => void; onReviewChanges?: () => void; }; function TurnSummary({ id, artifacts, files, artifactsExpanded, filesExpanded, onToggleArtifacts, onToggleFiles, onFileOpen, onEditedFileOpen, onLink, onReviewChanges, }: TurnSummaryProps) { return (
{artifacts.length > 0 && ( )} {files.length > 0 && ( )}
); } function ArtifactCard({ id, artifacts, expanded, onToggle, onFileOpen, onLink, }: { id: string; artifacts: Artifact[]; expanded: boolean; onToggle: () => void; onFileOpen?: (file: string) => void; onLink?: (href: string) => void; }) { let visible = expanded ? artifacts : artifacts.slice(0, ARTIFACT_PREVIEW); let extra = artifacts.length - ARTIFACT_PREVIEW; return (
{visible.map((artifact, index) => ( 0} height={ARTIFACT_ROW} onFileOpen={onFileOpen} onLink={onLink} /> ))} {extra > 0 && ( )}
); } function ArtifactRow({ artifact, separated, height, onFileOpen, onLink, }: { artifact: Artifact; separated: boolean; height: number; onFileOpen?: (file: string) => void; onLink?: (href: string) => void; }) { let Icon = artifactIcon(artifact); let actionable = Boolean(artifact.url || artifact.path && onFileOpen); let actionLabel = "Open"; let content = ( <> {artifact.meta} {artifact.title} {actionable && ( {actionLabel} {artifact.url && } )} ); let classes = cn( "flex w-full min-w-0 items-center gap-3 px-3 text-left transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-ring/50 motion-reduce:transition-none", "hover:bg-muted/50", separated && "border-t border-border", ); let style = { blockSize: height, boxSizing: "border-box" as const }; if (artifact.url) { if (onLink) { return ( ); } return ( {content} ); } if (artifact.path && onFileOpen) { return ( ); } return (
{content}
); } function artifactIcon(artifact: Artifact) { switch (artifact.kind) { case "issue": return IconIssue; case "pull-request": return IconPullRequest; case "comment": return IconMessage; case "label": return IconHash; case "document": return IconFileText; } } function EditedFilesCard({ id, files, expanded, onToggle, onFileOpen, onReviewChanges, }: { id: string; files: EditedFile[]; expanded: boolean; onToggle: () => void; onFileOpen?: (file: string) => void; onReviewChanges?: () => void; }) { let visible = expanded ? files : files.slice(0, FILE_PREVIEW); let extra = files.length - FILE_PREVIEW; let adds = sum(files, "adds"); let dels = sum(files, "dels"); let hasStats = adds > 0 || dels > 0; return (
Edited {files.length} {files.length === 1 ? "file" : "files"} {hasStats && ( +{adds}{" "} -{dels} )} {onReviewChanges && ( )}
{visible.map(file => ( ))}
{extra > 0 && ( )}
); } function FileRow({ file, height, onFileOpen, }: { file: EditedFile; height: number; onFileOpen?: (file: string) => void; }) { let stats = file.adds != null || file.dels != null; let label = displayPath(file.path); let content = ( <> {label} {stats && ( +{file.adds ?? 0}{" "} -{file.dels ?? 0} )} ); let classes = cn( "flex w-full min-w-0 items-center gap-3 px-[14px] text-left transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-ring/50 motion-reduce:transition-none", "hover:bg-muted/50", ); let style = { blockSize: height, boxSizing: "border-box" as const }; if (!onFileOpen) return
{content}
; return ( ); } function displayPath(path: string): string { return path.startsWith(VM_PROJECT_PREFIX) ? path.slice(VM_PROJECT_PREFIX.length) : path; } function ShowMore({ id, count, label, expanded, onToggle, }: { id: string; count: number; label: string; expanded: boolean; onToggle: () => void; }) { let text = expanded ? "Show less" : `Show ${count} more ${label}${count === 1 ? "" : "s"}`; return ( ); } function sum(files: EditedFile[], key: "adds" | "dels"): number { let value = 0; for (let file of files) value += file[key] ?? 0; return value; } export { TurnSummary };