Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
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>
|
|
);
|
|
}
|