From c72e903bece39c2097598cc1513d223da2bd424f Mon Sep 17 00:00:00 2001 From: Kyle Zifrony Date: Fri, 13 Mar 2026 14:32:07 -0400 Subject: [PATCH] fix(playground): add save and refresh to file explorer. get roomoptions after each message --- .../src/components/file/file-code-editor.tsx | 40 ++++++++++++++- .../src/components/file/file-editor.tsx | 14 +++--- .../src/components/room/room-content.tsx | 4 ++ .../playground/src/stores/room/room.store.ts | 50 +++++++++++++++++++ 4 files changed, 101 insertions(+), 7 deletions(-) diff --git a/libs/shared/src/components/file/file-code-editor.tsx b/libs/shared/src/components/file/file-code-editor.tsx index 1cea59fc68..e480bcd6b5 100644 --- a/libs/shared/src/components/file/file-code-editor.tsx +++ b/libs/shared/src/components/file/file-code-editor.tsx @@ -1,8 +1,17 @@ import type { OnMount } from "@monaco-editor/react"; +import { RefreshCwIcon, SaveIcon } from "lucide-react"; import type * as monaco from "monaco-editor"; import { Suspense, useRef, useState } from "react"; import { download, runPixel, useInsight, usePixel } from "@semoss/sdk/react"; -import { Muted, Spinner, toast } from "@semoss/ui/next"; +import { + Button, + Muted, + Spinner, + Tooltip, + TooltipContent, + TooltipTrigger, + toast, +} from "@semoss/ui/next"; import { MONACO_CONFIG, MONACO_EXT_LANGUAGE_MAPPING, @@ -304,6 +313,35 @@ export const FileCodeEditor: React.FC = ({ return (
+ {/* Toolbar */} +
+ + + + + Refresh + + + + + + Save (Ctrl+S) + +
diff --git a/libs/shared/src/components/file/file-editor.tsx b/libs/shared/src/components/file/file-editor.tsx index 3fa0b829a7..620996f977 100644 --- a/libs/shared/src/components/file/file-editor.tsx +++ b/libs/shared/src/components/file/file-editor.tsx @@ -12,9 +12,6 @@ interface FileEditorProps { /** Callback when the file is changed */ onChange?: (content: string, isModified: boolean) => void; - - /** Callback when the file is saved */ - onSave?: () => void; } export const FileEditor: React.FC = ({ @@ -37,10 +34,15 @@ export const FileEditor: React.FC = ({ return (
- {isImage && } - {isPdf && } + {isImage && } + {isPdf && } {!isImage && !isPdf && ( - + )}
); diff --git a/packages/playground/src/components/room/room-content.tsx b/packages/playground/src/components/room/room-content.tsx index aa5b616340..3b47275070 100644 --- a/packages/playground/src/components/room/room-content.tsx +++ b/packages/playground/src/components/room/room-content.tsx @@ -69,6 +69,10 @@ export const RoomContent: React.FC = observer(({ room }) => { // ask the room await room.askMessage(prompt, files); + // re-sync room options from backend after message completes, + // preserving workspace MCPs that are only held in memory + await room.syncRoomOptions(); + return true; }; diff --git a/packages/playground/src/stores/room/room.store.ts b/packages/playground/src/stores/room/room.store.ts index b06d03d909..2c60cad8bd 100644 --- a/packages/playground/src/stores/room/room.store.ts +++ b/packages/playground/src/stores/room/room.store.ts @@ -612,6 +612,56 @@ export class RoomStore { } }; + /** + * Fetch the latest room options from the backend and sync local state, + * preserving any workspace MCPs that are currently loaded in memory. + */ + syncRoomOptions = async (): Promise => { + try { + const response = await this.runRoomPixel< + [{ OPTIONS?: RoomStoreInterface["options"] }] + >( + `GetRoomOptions(roomId=${JSON.stringify(this._store.roomId)});`, + false, + false, + ); + + const fetched = response.pixelReturn[0].output as { + OPTIONS?: RoomStoreInterface["options"]; + }; + + if (!fetched?.OPTIONS) { + return; + } + + // Preserve workspace MCPs that are already in local state + const workspaceMCPs = this._store.options.mcp.filter( + (mcp) => mcp?.fromWorkspace, + ); + + const freshRoomMCPs = (fetched.OPTIONS.mcp ?? []).filter( + (mcp) => !mcp?.fromWorkspace, + ); + + // Deduplicate: workspace MCPs take precedence + const workspaceIds = new Set(workspaceMCPs.map((m) => m.id)); + const merged = [ + ...workspaceMCPs, + ...freshRoomMCPs.filter((m) => !workspaceIds.has(m.id)), + ]; + + runInAction(() => { + this.setOptions({ + ...fetched.OPTIONS, + mcp: merged, + }); + }); + } catch (e) { + // non-critical — swallow errors so the chat isn't disrupted + console.warn("Failed to sync room options:", e); + } + }; + /** * UpdateRoomOptions * @param options - full set of new options