feat(web): MS23-P2-004 panel operator controls
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { BadgeVariant } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { PanelControls } from "@/components/mission-control/PanelControls";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
useSessionStream,
|
||||
useSessions,
|
||||
type MissionControlConnectionStatus,
|
||||
type MissionControlMessageRole,
|
||||
} from "@/hooks/useMissionControl";
|
||||
@@ -46,12 +48,21 @@ function formatRelativeTimestamp(timestamp: string): string {
|
||||
|
||||
export function OrchestratorPanel({ sessionId }: OrchestratorPanelProps): React.JSX.Element {
|
||||
const { messages, status, error } = useSessionStream(sessionId ?? "");
|
||||
const { sessions } = useSessions();
|
||||
const bottomAnchorRef = useRef<HTMLDivElement | null>(null);
|
||||
const [optimisticStatus, setOptimisticStatus] = useState<string | null>(null);
|
||||
|
||||
const selectedSessionStatus = sessions.find((session) => session.id === sessionId)?.status;
|
||||
const controlsStatus = optimisticStatus ?? selectedSessionStatus ?? "unknown";
|
||||
|
||||
useEffect(() => {
|
||||
bottomAnchorRef.current?.scrollIntoView({ block: "end" });
|
||||
}, [messages.length]);
|
||||
|
||||
useEffect(() => {
|
||||
setOptimisticStatus(null);
|
||||
}, [sessionId, selectedSessionStatus]);
|
||||
|
||||
if (!sessionId) {
|
||||
return (
|
||||
<Card className="flex h-full min-h-[220px] flex-col">
|
||||
@@ -68,16 +79,23 @@ export function OrchestratorPanel({ sessionId }: OrchestratorPanelProps): React.
|
||||
return (
|
||||
<Card className="flex h-full min-h-[220px] flex-col">
|
||||
<CardHeader className="space-y-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
||||
<CardTitle className="text-base">Orchestrator Panel</CardTitle>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span
|
||||
className={`h-2.5 w-2.5 rounded-full ${CONNECTION_DOT_CLASS[status]} ${
|
||||
status === "connecting" ? "animate-pulse" : ""
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
<div className="flex flex-col items-start gap-2 sm:items-end">
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span
|
||||
className={`h-2.5 w-2.5 rounded-full ${CONNECTION_DOT_CLASS[status]} ${
|
||||
status === "connecting" ? "animate-pulse" : ""
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{CONNECTION_TEXT[status]}</span>
|
||||
</div>
|
||||
<PanelControls
|
||||
sessionId={sessionId}
|
||||
status={controlsStatus}
|
||||
onStatusChange={setOptimisticStatus}
|
||||
/>
|
||||
<span>{CONNECTION_TEXT[status]}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="truncate text-xs text-muted-foreground">Session: {sessionId}</p>
|
||||
|
||||
Reference in New Issue
Block a user