Spaces:
Running
Running
| import React, { useState } from "react"; | |
| import { Activity, FolderTree, Bot, X } from "lucide-react"; | |
| import FileExplorer from "./FileExplorer"; | |
| import AgentSettingsPanel from "./AgentSettingsPanel"; | |
| import BrowserPanel from "./BrowserPanel"; | |
| const TABS = [ | |
| { id: "activity", label: "Activité", icon: Activity }, | |
| { id: "files", label: "Fichiers", icon: FolderTree }, | |
| { id: "agent", label: "Agent", icon: Bot }, | |
| ]; | |
| export default function RightPanel({ | |
| tools, | |
| agentOnline, | |
| agentToolsOnline, | |
| onRefreshStatus, | |
| onClose, | |
| filePreview = null, | |
| activeTab, | |
| onTabChange, | |
| browserFrames = [], | |
| reflectNotes = [], | |
| onBrowserFrameUpdate, | |
| mobileSheet = false, | |
| }) { | |
| const [fallbackTab, setFallbackTab] = useState("activity"); | |
| const tab = activeTab ?? fallbackTab; | |
| const selectTab = (id) => { | |
| if (activeTab == null) setFallbackTab(id); | |
| onTabChange?.(id); | |
| }; | |
| return ( | |
| <aside | |
| data-testid="right-panel" | |
| className={ | |
| mobileSheet | |
| ? "flex w-full min-w-0 flex-1 flex-col emo-panel-flat emo-right-panel-sheet overflow-hidden" | |
| : "hidden md:flex emo-right-panel flex-shrink-0 h-full flex-col emo-panel-flat" | |
| } | |
| style={{ | |
| borderLeft: mobileSheet ? "none" : "1px solid var(--emo-border)", | |
| background: "var(--emo-surface)", | |
| }} | |
| > | |
| <div className="emo-panel-tabs flex-shrink-0"> | |
| <div className="flex items-center gap-1 flex-1 min-w-0 overflow-x-auto scrollbar-thin"> | |
| {TABS.map((t) => { | |
| const Icon = t.icon; | |
| const active = t.id === tab; | |
| return ( | |
| <button | |
| key={t.id} | |
| type="button" | |
| data-testid={`right-tab-${t.id}`} | |
| onClick={() => selectTab(t.id)} | |
| className="emo-panel-tab flex-shrink-0" | |
| data-active={active ? "true" : "false"} | |
| aria-selected={active} | |
| > | |
| <Icon size={13} style={{ color: active ? "var(--mode-color)" : "currentColor" }} /> | |
| {t.label} | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| {onClose && ( | |
| <button type="button" onClick={onClose} className="emo-icon-btn ml-1 flex-shrink-0" data-testid="right-panel-close"> | |
| <X size={14} /> | |
| </button> | |
| )} | |
| </div> | |
| <div className="emo-panel-tab-stack"> | |
| <div | |
| className="emo-panel-tab-pane" | |
| data-active={tab === "activity" ? "true" : "false"} | |
| aria-hidden={tab !== "activity"} | |
| > | |
| <ActivityTab | |
| tools={tools} | |
| agentOnline={agentOnline} | |
| agentToolsOnline={agentToolsOnline} | |
| browserFrames={browserFrames} | |
| reflectNotes={reflectNotes} | |
| onBrowserFrameUpdate={onBrowserFrameUpdate} | |
| /> | |
| </div> | |
| <div | |
| className="emo-panel-tab-pane" | |
| data-active={tab === "files" ? "true" : "false"} | |
| aria-hidden={tab !== "files"} | |
| > | |
| <FileExplorer agentOnline={agentToolsOnline} externalPreview={filePreview} /> | |
| </div> | |
| <div | |
| className="emo-panel-tab-pane" | |
| data-active={tab === "agent" ? "true" : "false"} | |
| aria-hidden={tab !== "agent"} | |
| > | |
| <div className="p-4 overflow-y-auto h-full scrollbar-thin"> | |
| <AgentSettingsPanel agentOnline={agentOnline} onRefreshStatus={onRefreshStatus} /> | |
| </div> | |
| </div> | |
| </div> | |
| </aside> | |
| ); | |
| } | |
| const ActivityTab = ({ tools, agentOnline, agentToolsOnline, browserFrames, reflectNotes, onBrowserFrameUpdate }) => ( | |
| <div className="h-full flex flex-col overflow-hidden" data-testid="activity-tab"> | |
| <div | |
| className="flex-shrink-0 flex items-center gap-2 text-[10px] uppercase tracking-wide font-medium text-muted-em px-4 py-2.5" | |
| style={{ borderBottom: "1px solid var(--emo-border)", background: "var(--emo-bg-subtle)" }} | |
| > | |
| <div | |
| className={`w-2 h-2 rounded-full ${agentOnline ? "emo-status-dot-online" : "emo-status-dot-offline"}`} | |
| /> | |
| Desktop {agentOnline ? "connecté" : "hors ligne"} | |
| {agentToolsOnline ? ( | |
| <span className="normal-case tracking-normal opacity-70 font-normal">· outils actifs</span> | |
| ) : null} | |
| {tools.length > 0 && ( | |
| <span className="normal-case tracking-normal opacity-70 font-normal"> | |
| · {tools.length} action{tools.length !== 1 ? "s" : ""} | |
| </span> | |
| )} | |
| </div> | |
| <div className="flex-1 min-h-0"> | |
| <BrowserPanel frames={browserFrames} reflectNotes={reflectNotes} onFrameUpdate={onBrowserFrameUpdate} /> | |
| </div> | |
| {tools.length > 0 && ( | |
| <div | |
| className="flex-shrink-0 max-h-32 overflow-y-auto scrollbar-thin p-2.5 space-y-1.5" | |
| style={{ borderTop: "1px solid var(--emo-border)", background: "var(--emo-bg-subtle)" }} | |
| > | |
| {tools.slice(-8).map((t) => ( | |
| <div | |
| key={t.id} | |
| className="text-xs px-3 py-2 rounded-xl flex items-center justify-between" | |
| style={{ background: "var(--emo-surface)", border: "1px solid var(--emo-border)" }} | |
| > | |
| <span className="font-code text-[11px] font-medium" style={{ color: "var(--mode-color)" }}>{t.tool}</span> | |
| <span className="text-muted-em text-[10px]"> | |
| {t.state === "executing" ? "en cours…" : t.state === "error" ? "erreur" : "ok"} | |
| </span> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ); | |