Add workspaces, conversation sessions, app-managed computers and pictures
- Rail: Add icon with stacked form modals (bot, workspace, computer), coordinator, recent bots with "See all bots", Workspaces section, and a workspace mode listing only its crew - Workspaces: page for context, bots (per-workspace instructions and memory), computers (default, link, unlink), models and delete; conversations scoped per bot per workspace - Sessions: conversations split into searchable, reopenable sessions with carry-over summaries; sessions and new-session header icons - Computers: created and provisioned over the docker socket, home dir kept at DATA_DIR/computers/<id> after delete; super admins only - Models: workspace settings win over a bot's own, then the defaults - Pictures for bots and workspaces; bots editable from the header - Coordinator tools: update_workspace, set_bot_workspace_instructions Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -33,6 +33,10 @@ export type BotComputers = {
|
||||
|
||||
type Params = {
|
||||
bot_id: number;
|
||||
/**
|
||||
* In a workspace, switching changes the workspace's default computer.
|
||||
*/
|
||||
workspace_id?: number | null;
|
||||
computers: BUN_SQLITE_COBBLE_COMPUTERS[];
|
||||
};
|
||||
|
||||
@@ -42,7 +46,7 @@ type Params = {
|
||||
* arrives as `server:bot-updated` with the bot's `computer_id`. Also holds
|
||||
* the computer open in the viewer (`ComputerViewerModal`).
|
||||
*/
|
||||
export default function useBotComputers({ bot_id, computers: initial }: Params): BotComputers {
|
||||
export default function useBotComputers({ bot_id, workspace_id, computers: initial }: Params): BotComputers {
|
||||
const [computers, setComputers] = useState(initial);
|
||||
const [switching_id, setSwitchingId] = useState<number>();
|
||||
const [error, setError] = useState<string>();
|
||||
@@ -71,7 +75,7 @@ export default function useBotComputers({ bot_id, computers: initial }: Params):
|
||||
setSwitchingId(computer_id);
|
||||
setError(undefined);
|
||||
|
||||
const res = await setBotComputer({ bot_id, computer_id }).catch(() => null);
|
||||
const res = await setBotComputer({ bot_id, computer_id, workspace_id }).catch(() => null);
|
||||
|
||||
if (res?.success && res.payload?.length) {
|
||||
setComputers(res.payload);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { BotSessionListItem } from "@/src/types";
|
||||
import postSessionApi from "../(functions)/post-session-api";
|
||||
|
||||
type Params = {
|
||||
bot_id: number;
|
||||
workspace_id?: number | null;
|
||||
/**
|
||||
* Loads only while the modal is open.
|
||||
*/
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A conversation's sessions, latest first, searched as the user types
|
||||
* (debounced). Starting or reopening a session reloads the page onto it.
|
||||
*/
|
||||
export default function useBotSessions({ bot_id, workspace_id, open }: Params) {
|
||||
const [query, setQuery] = useState("");
|
||||
const [sessions, setSessions] = useState<BotSessionListItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [busy_id, setBusyId] = useState<number | "new">();
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
let stale = false;
|
||||
const timer = setTimeout(async () => {
|
||||
setLoading(true);
|
||||
|
||||
const res = await postSessionApi<BotSessionListItem>({
|
||||
path: "list-sessions",
|
||||
body: { bot_id, workspace_id, query: query.trim() || undefined },
|
||||
}).catch(() => null);
|
||||
|
||||
if (stale) return;
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (res?.success) {
|
||||
setSessions(res.payload || []);
|
||||
setError(undefined);
|
||||
} else {
|
||||
setError(res?.msg || "Couldn't load the sessions.");
|
||||
}
|
||||
}, query ? 250 : 0);
|
||||
|
||||
return () => {
|
||||
stale = true;
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [open, query, bot_id, workspace_id]);
|
||||
|
||||
const act = async ({ id, path, body }: { id: number | "new"; path: "new-session" | "reopen-session"; body: Record<string, unknown> }) => {
|
||||
if (busy_id) return;
|
||||
|
||||
setBusyId(id);
|
||||
setError(undefined);
|
||||
|
||||
const res = await postSessionApi({ path, body }).catch(() => null);
|
||||
|
||||
if (res?.success) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
setBusyId(undefined);
|
||||
setError(res?.msg || "That didn't work.");
|
||||
};
|
||||
|
||||
return {
|
||||
query,
|
||||
setQuery,
|
||||
sessions,
|
||||
loading,
|
||||
busy_id,
|
||||
error,
|
||||
startSession: () => act({ id: "new", path: "new-session", body: { bot_id, workspace_id } }),
|
||||
reopenSession: ({ session_id }: { session_id: number }) =>
|
||||
act({ id: session_id, path: "reopen-session", body: { session_id } }),
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,10 @@ import type { BotJobUpdateData, BotTaskWithSteps, BotUpdatedData } from "@/src/t
|
||||
|
||||
type Params = {
|
||||
bot_id: number;
|
||||
/**
|
||||
* The open session: steps of jobs from other sessions are left out.
|
||||
*/
|
||||
session_id?: number | null;
|
||||
/**
|
||||
* The bot's active task when the page loaded.
|
||||
*/
|
||||
@@ -27,13 +31,14 @@ function fromTask({ task }: { task: BotTaskWithSteps | null }): Progress {
|
||||
* row's text), kept current from `server:bot-job-update`. Starts over when
|
||||
* `server:bot-updated` brings a different task, or none.
|
||||
*/
|
||||
export default function useTaskProgress({ bot_id, task }: Params) {
|
||||
export default function useTaskProgress({ bot_id, session_id, task }: Params) {
|
||||
const [progress, setProgress] = useState<Progress>(() => fromTask({ task }));
|
||||
|
||||
useServerEvent({
|
||||
event: "server:bot-job-update",
|
||||
onData: (update: BotJobUpdateData) => {
|
||||
if (update.bot_id != bot_id || !update.task_id || !update.text) return;
|
||||
if (session_id && update.session_id && update.session_id != session_id) return;
|
||||
|
||||
setProgress({ task_id: update.task_id, text: update.text });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user