Pipeline view groups phases by dependency depth with DAG visualization. Phase detail panel with Tiptap rich content editor and auto-save. Code review tab with diff viewer and comment threads (dummy data). Centralized live updates hook replaces scattered subscription boilerplate. Extract agent output parsing into shared utility. Inbox detail panel, account cards, and agent action components.
172 lines
4.6 KiB
TypeScript
172 lines
4.6 KiB
TypeScript
import { Link } from "@tanstack/react-router";
|
|
import { ChevronLeft } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { QuestionForm } from "@/components/QuestionForm";
|
|
import { formatRelativeTime } from "@/lib/utils";
|
|
|
|
interface InboxDetailPanelProps {
|
|
agent: {
|
|
id: string;
|
|
name: string;
|
|
status: string;
|
|
taskId: string | null;
|
|
updatedAt: string;
|
|
};
|
|
message: {
|
|
id: string;
|
|
content: string;
|
|
requiresResponse: boolean;
|
|
} | null;
|
|
questions:
|
|
| {
|
|
id: string;
|
|
question: string;
|
|
options: any;
|
|
multiSelect: boolean;
|
|
}[]
|
|
| null;
|
|
isLoadingQuestions: boolean;
|
|
questionsError: string | null;
|
|
onBack: () => void;
|
|
onSubmitAnswers: (answers: Record<string, string>) => void;
|
|
onDismissQuestions: () => void;
|
|
onDismissMessage: () => void;
|
|
isSubmitting: boolean;
|
|
isDismissingQuestions: boolean;
|
|
isDismissingMessage: boolean;
|
|
submitError: string | null;
|
|
dismissMessageError: string | null;
|
|
}
|
|
|
|
export function InboxDetailPanel({
|
|
agent,
|
|
message,
|
|
questions,
|
|
isLoadingQuestions,
|
|
questionsError,
|
|
onBack,
|
|
onSubmitAnswers,
|
|
onDismissQuestions,
|
|
onDismissMessage,
|
|
isSubmitting,
|
|
isDismissingQuestions,
|
|
isDismissingMessage,
|
|
submitError,
|
|
dismissMessageError,
|
|
}: InboxDetailPanelProps) {
|
|
return (
|
|
<div className="space-y-4 rounded-lg border border-border p-4">
|
|
{/* Mobile back button */}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="lg:hidden"
|
|
onClick={onBack}
|
|
>
|
|
<ChevronLeft className="mr-1 h-4 w-4" />
|
|
Back to list
|
|
</Button>
|
|
|
|
{/* Detail Header */}
|
|
<div className="border-b border-border pb-3">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-sm font-bold">
|
|
{agent.name}{" "}
|
|
<span className="font-normal text-muted-foreground">
|
|
→ You
|
|
</span>
|
|
</h3>
|
|
<span className="text-xs text-muted-foreground">
|
|
{formatRelativeTime(agent.updatedAt)}
|
|
</span>
|
|
</div>
|
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
Task:{" "}
|
|
{agent.taskId ? (
|
|
<Link
|
|
to="/initiatives"
|
|
className="text-primary hover:underline"
|
|
>
|
|
{agent.taskId}
|
|
</Link>
|
|
) : (
|
|
"\u2014"
|
|
)}
|
|
</p>
|
|
{agent.taskId && (
|
|
<Link
|
|
to="/initiatives"
|
|
className="mt-1 inline-block text-xs text-primary hover:underline"
|
|
>
|
|
View in context →
|
|
</Link>
|
|
)}
|
|
</div>
|
|
|
|
{/* Question Form or Notification Content */}
|
|
{isLoadingQuestions && (
|
|
<div className="py-4 text-center text-sm text-muted-foreground">
|
|
Loading questions...
|
|
</div>
|
|
)}
|
|
|
|
{questionsError && (
|
|
<div className="py-4 text-center text-sm text-destructive">
|
|
Failed to load questions: {questionsError}
|
|
</div>
|
|
)}
|
|
|
|
{questions && questions.length > 0 && (
|
|
<QuestionForm
|
|
questions={questions}
|
|
onSubmit={onSubmitAnswers}
|
|
onCancel={onBack}
|
|
onDismiss={onDismissQuestions}
|
|
isSubmitting={isSubmitting}
|
|
isDismissing={isDismissingQuestions}
|
|
/>
|
|
)}
|
|
|
|
{submitError && (
|
|
<p className="text-sm text-destructive">Error: {submitError}</p>
|
|
)}
|
|
|
|
{/* Notification message (no questions / requiresResponse=false) */}
|
|
{message && !message.requiresResponse && !isLoadingQuestions && (
|
|
<div className="space-y-3">
|
|
<p className="text-sm">{message.content}</p>
|
|
<div className="flex justify-end">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={onDismissMessage}
|
|
disabled={isDismissingMessage}
|
|
>
|
|
{isDismissingMessage ? "Dismissing..." : "Dismiss"}
|
|
</Button>
|
|
</div>
|
|
{dismissMessageError && (
|
|
<p className="text-sm text-destructive">
|
|
Error: {dismissMessageError}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* No questions and requires response -- message content only */}
|
|
{message &&
|
|
message.requiresResponse &&
|
|
questions &&
|
|
questions.length === 0 &&
|
|
!isLoadingQuestions && (
|
|
<div className="space-y-3">
|
|
<p className="text-sm">{message.content}</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
Waiting for structured questions...
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|