ui: memoize issue timeline rendering in comment thread
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useRef, useState, type ChangeEvent } from "react";
|
import { memo, useEffect, useMemo, useRef, useState, type ChangeEvent } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import type { IssueComment, Agent } from "@paperclipai/shared";
|
import type { IssueComment, Agent } from "@paperclipai/shared";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -95,6 +95,91 @@ type TimelineItem =
|
|||||||
| { kind: "comment"; id: string; createdAtMs: number; comment: CommentWithRunMeta }
|
| { kind: "comment"; id: string; createdAtMs: number; comment: CommentWithRunMeta }
|
||||||
| { kind: "run"; id: string; createdAtMs: number; run: LinkedRunItem };
|
| { kind: "run"; id: string; createdAtMs: number; run: LinkedRunItem };
|
||||||
|
|
||||||
|
const TimelineList = memo(function TimelineList({
|
||||||
|
timeline,
|
||||||
|
agentMap,
|
||||||
|
}: {
|
||||||
|
timeline: TimelineItem[];
|
||||||
|
agentMap?: Map<string, Agent>;
|
||||||
|
}) {
|
||||||
|
if (timeline.length === 0) {
|
||||||
|
return <p className="text-sm text-muted-foreground">No comments or runs yet.</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{timeline.map((item) => {
|
||||||
|
if (item.kind === "run") {
|
||||||
|
const run = item.run;
|
||||||
|
return (
|
||||||
|
<div key={`run:${run.runId}`} className="border border-border bg-accent/20 p-3 overflow-hidden min-w-0 rounded-sm">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<Link to={`/agents/${run.agentId}`} className="hover:underline">
|
||||||
|
<Identity
|
||||||
|
name={agentMap?.get(run.agentId)?.name ?? run.agentId.slice(0, 8)}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{formatDateTime(run.startedAt ?? run.createdAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-xs">
|
||||||
|
<span className="text-muted-foreground">Run</span>
|
||||||
|
<Link
|
||||||
|
to={`/agents/${run.agentId}/runs/${run.runId}`}
|
||||||
|
className="inline-flex items-center rounded-md border border-border bg-accent/40 px-2 py-1 font-mono text-muted-foreground hover:text-foreground hover:bg-accent/60 transition-colors"
|
||||||
|
>
|
||||||
|
{run.runId.slice(0, 8)}
|
||||||
|
</Link>
|
||||||
|
<StatusBadge status={run.status} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const comment = item.comment;
|
||||||
|
return (
|
||||||
|
<div key={comment.id} className="border border-border p-3 overflow-hidden min-w-0 rounded-sm">
|
||||||
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
{comment.authorAgentId ? (
|
||||||
|
<Link to={`/agents/${comment.authorAgentId}`} className="hover:underline">
|
||||||
|
<Identity
|
||||||
|
name={agentMap?.get(comment.authorAgentId)?.name ?? comment.authorAgentId.slice(0, 8)}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Identity name="You" size="sm" />
|
||||||
|
)}
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{formatDateTime(comment.createdAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<MarkdownBody className="text-sm">{comment.body}</MarkdownBody>
|
||||||
|
{comment.runId && (
|
||||||
|
<div className="mt-2 pt-2 border-t border-border/60">
|
||||||
|
{comment.runAgentId ? (
|
||||||
|
<Link
|
||||||
|
to={`/agents/${comment.runAgentId}/runs/${comment.runId}`}
|
||||||
|
className="inline-flex items-center rounded-md border border-border bg-accent/30 px-2 py-1 text-[10px] font-mono text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
||||||
|
>
|
||||||
|
run {comment.runId.slice(0, 8)}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span className="inline-flex items-center rounded-md border border-border bg-accent/30 px-2 py-1 text-[10px] font-mono text-muted-foreground">
|
||||||
|
run {comment.runId.slice(0, 8)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export function CommentThread({
|
export function CommentThread({
|
||||||
comments,
|
comments,
|
||||||
linkedRuns = [],
|
linkedRuns = [],
|
||||||
@@ -212,80 +297,7 @@ export function CommentThread({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-sm font-semibold">Comments & Runs ({timeline.length})</h3>
|
<h3 className="text-sm font-semibold">Comments & Runs ({timeline.length})</h3>
|
||||||
|
|
||||||
{timeline.length === 0 && (
|
<TimelineList timeline={timeline} agentMap={agentMap} />
|
||||||
<p className="text-sm text-muted-foreground">No comments or runs yet.</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
{timeline.map((item) => {
|
|
||||||
if (item.kind === "run") {
|
|
||||||
const run = item.run;
|
|
||||||
return (
|
|
||||||
<div key={`run:${run.runId}`} className="border border-border bg-accent/20 p-3 overflow-hidden min-w-0 rounded-sm">
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<Link to={`/agents/${run.agentId}`} className="hover:underline">
|
|
||||||
<Identity
|
|
||||||
name={agentMap?.get(run.agentId)?.name ?? run.agentId.slice(0, 8)}
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{formatDateTime(run.startedAt ?? run.createdAt)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 text-xs">
|
|
||||||
<span className="text-muted-foreground">Run</span>
|
|
||||||
<Link
|
|
||||||
to={`/agents/${run.agentId}/runs/${run.runId}`}
|
|
||||||
className="inline-flex items-center rounded-md border border-border bg-accent/40 px-2 py-1 font-mono text-muted-foreground hover:text-foreground hover:bg-accent/60 transition-colors"
|
|
||||||
>
|
|
||||||
{run.runId.slice(0, 8)}
|
|
||||||
</Link>
|
|
||||||
<StatusBadge status={run.status} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const comment = item.comment;
|
|
||||||
return (
|
|
||||||
<div key={comment.id} className="border border-border p-3 overflow-hidden min-w-0 rounded-sm">
|
|
||||||
<div className="flex items-center justify-between mb-1">
|
|
||||||
{comment.authorAgentId ? (
|
|
||||||
<Link to={`/agents/${comment.authorAgentId}`} className="hover:underline">
|
|
||||||
<Identity
|
|
||||||
name={agentMap?.get(comment.authorAgentId)?.name ?? comment.authorAgentId.slice(0, 8)}
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Identity name="You" size="sm" />
|
|
||||||
)}
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{formatDateTime(comment.createdAt)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<MarkdownBody className="text-sm">{comment.body}</MarkdownBody>
|
|
||||||
{comment.runId && (
|
|
||||||
<div className="mt-2 pt-2 border-t border-border/60">
|
|
||||||
{comment.runAgentId ? (
|
|
||||||
<Link
|
|
||||||
to={`/agents/${comment.runAgentId}/runs/${comment.runId}`}
|
|
||||||
className="inline-flex items-center rounded-md border border-border bg-accent/30 px-2 py-1 text-[10px] font-mono text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
|
||||||
>
|
|
||||||
run {comment.runId.slice(0, 8)}
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<span className="inline-flex items-center rounded-md border border-border bg-accent/30 px-2 py-1 text-[10px] font-mono text-muted-foreground">
|
|
||||||
run {comment.runId.slice(0, 8)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{liveRunSlot}
|
{liveRunSlot}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user