Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion libs/shared/src/components/file/file-code-editor.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -304,6 +313,35 @@ export const FileCodeEditor: React.FC<FileCodeEditorProps> = ({

return (
<div className="relative flex h-full w-full flex-col items-center bg-background [&_.quick-input-widget]:mx-0!">
{/* Toolbar */}
<div className="flex w-full shrink-0 items-center justify-end gap-1 border-border border-b px-1.5 py-0.5">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon-sm"
disabled={isLoading || getFile.status !== "SUCCESS"}
onClick={() => getFile.refresh()}
>
<RefreshCwIcon className="size-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Refresh</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon-sm"
disabled={isLoading || getFile.status !== "SUCCESS"}
onClick={() => saveFile()}
>
<SaveIcon className="size-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Save (Ctrl+S)</TooltipContent>
</Tooltip>
</div>
<Suspense
fallback={
<div className="flex h-full w-full items-center justify-center">
Expand Down
14 changes: 8 additions & 6 deletions libs/shared/src/components/file/file-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<FileEditorProps> = ({
Expand All @@ -37,10 +34,15 @@ export const FileEditor: React.FC<FileEditorProps> = ({

return (
<div className="relative flex h-full w-full flex-col overflow-hidden bg-background py-1">
{isImage && <FileImageViewer mode={mode} path={path} />}
{isPdf && <FilePdfViewer mode={mode} path={path} />}
{isImage && <FileImageViewer key={path} mode={mode} path={path} />}
{isPdf && <FilePdfViewer key={path} mode={mode} path={path} />}
{!isImage && !isPdf && (
<FileCodeEditor mode={mode} path={path} onChange={onChange} />
<FileCodeEditor
key={path}
mode={mode}
path={path}
onChange={onChange}
/>
)}
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions packages/playground/src/components/room/room-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export const RoomContent: React.FC<RoomContentProps> = 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;
};

Expand Down
50 changes: 50 additions & 0 deletions packages/playground/src/stores/room/room.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> => {
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
Expand Down
Loading