workspace management progress

This commit is contained in:
Rob Colbert 2026-05-02 16:38:49 -04:00
parent 4ec31764d5
commit 8b5df3827b
4 changed files with 66 additions and 63 deletions

View File

@ -19,62 +19,17 @@ export default function LogPanel({ logs, expanded, workspaceMode, onToggleExpand
const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (scrollRef.current && !expanded) {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [logs, expanded]);
if (expanded) {
return (
<div className="absolute inset-0 z-10 bg-bg-primary flex flex-col border-t border-border-subtle">
<div className="flex items-center justify-between px-4 py-2 bg-bg-tertiary shrink-0 border-b border-border-subtle">
<h3 className="text-sm font-semibold text-text-secondary uppercase tracking-wider">
Log
</h3>
<button
onClick={onToggleExpand}
className="w-6 h-6 flex items-center justify-center text-text-muted hover:text-text-secondary transition-colors"
title="Collapse"
>
</button>
</div>
<div
ref={scrollRef}
className="flex-1 overflow-y-auto p-2 font-mono text-xs"
>
{logs.length === 0 ? (
<div className="text-text-muted">No log entries</div>
) : (
logs.map((entry) => (
<div key={entry.id} className="flex gap-2 py-0.5">
<span className="text-text-muted shrink-0">
{entry.timestamp.toLocaleTimeString()}
</span>
<span
className={`shrink-0 ${
entry.level === 'error'
? 'text-red-500'
: entry.level === 'warn'
? 'text-yellow-500'
: entry.level === 'info'
? 'text-blue-400'
: 'text-text-secondary'
}`}
>
[{entry.level.toUpperCase()}]
</span>
<span className="text-text-primary">{entry.message}</span>
</div>
))
)}
</div>
</div>
);
}
}, [logs]);
return (
<div className="h-48 border-t border-border-subtle bg-bg-secondary flex flex-col shrink-0">
<div
className={`border-t border-border-subtle bg-bg-secondary flex flex-col ${
expanded ? 'flex-1' : 'h-48'
}`}
>
<div className="flex items-center justify-between px-4 py-2 bg-bg-tertiary shrink-0">
<h3 className="text-sm font-semibold text-text-secondary uppercase tracking-wider">
Log
@ -82,9 +37,9 @@ export default function LogPanel({ logs, expanded, workspaceMode, onToggleExpand
<button
onClick={onToggleExpand}
className="w-6 h-6 flex items-center justify-center text-text-muted hover:text-text-secondary transition-colors"
title="Expand"
title={expanded ? 'Collapse' : 'Expand'}
>
{expanded ? '▾' : '▴'}
</button>
</div>
<div

View File

@ -223,15 +223,24 @@ export default function ChatSessionView() {
const handleWorkspaceModeChange = async (mode: WorkspaceMode) => {
if (!session || !project) return;
const registration = session.drone as any;
const success = await socketClient.requestWorkspaceMode(
registration,
project,
session,
mode,
);
if (!success) {
showToast(`Cannot switch to ${mode} mode: workspace is not idle`);
try {
const droneJson = localStorage.getItem('dtp_drone_registration');
if (!droneJson) {
showToast('No drone registration found');
return;
}
const registration = JSON.parse(droneJson);
const success = await socketClient.requestWorkspaceMode(
registration,
project,
session,
mode,
);
if (!success) {
showToast(`Cannot switch to ${mode} mode: workspace is not idle`);
}
} catch (err) {
showToast(`Failed to change workspace mode: ${err instanceof Error ? err.message : 'Unknown error'}`);
}
};

View File

@ -703,6 +703,7 @@ export default function ProjectManager({ user }: ProjectManagerProps) {
console.error("Failed to lock drone session");
return;
}
localStorage.setItem('dtp_drone_registration', JSON.stringify(selectedDrone));
navigate(`/projects/${selectedProject._id}/chat-session/${sessionId}`);
} catch (err) {
console.error("Failed to open chat session", err);

View File

@ -37,6 +37,7 @@ export class CodeSession extends SocketSession {
super.register();
this.socket.on("requestSessionLock", this.onRequestSessionLock.bind(this));
this.socket.on("requestWorkspaceMode", this.onRequestWorkspaceMode.bind(this));
this.socket.on("submitPrompt", this.onSubmitPrompt.bind(this));
}
@ -90,6 +91,43 @@ export class CodeSession extends SocketSession {
);
}
/**
* Called when the IDE sends a requestWorkspaceMode event to change the drone's
* workspace mode.
* @param registration the gadget-drone registration
* @param project the project
* @param chatSession the chat session
* @param mode the requested workspace mode
* @param cb response callback with success and current mode
*/
onRequestWorkspaceMode(
registration: IDroneRegistration,
project: IProject,
chatSession: IChatSession,
mode: WorkspaceMode,
cb: (success: boolean, currentMode: WorkspaceMode) => void,
) {
if (!this.selectedDrone) {
this.log.warn("workspace mode request rejected: no drone selected");
return cb(false, this.workspaceMode);
}
const droneSession = SocketService.getDroneSession(this.selectedDrone);
droneSession.socket.emit(
"requestWorkspaceMode",
registration,
project,
chatSession,
mode,
(success: boolean, currentMode: WorkspaceMode) => {
if (success) {
this.workspaceMode = currentMode;
}
cb(success, currentMode);
},
);
}
/**
* Called when the IDE submits a prompt to be processed by the agent.
* Creates a ChatTurn document and sends a work order to the selected drone.