Add computer switching, crew reporting, job steps and markdown chat
- Bots have a current computer (bots.computer_id), switched from the chat header beside the model button or by clicking a computer the bot names ([computer:ID]); tasks run on it by default - search_computers runs one read-only command on all of a bot's computers; chat replies know their computers (compose-computers-note) - The coordinator can use every computer and grants or revokes crew access with assign_computer - Delegated jobs record requested_by_bot_id; the worker's result or failure is reported back in the coordinator's conversation - bots_queue_updates records each model turn of a job, pushed live and shown in the working-on panel - Live relay: every process pushes new messages, job steps and bot changes from the database to its own sockets, so work done elsewhere shows live - Bot messages render as markdown (react-markdown + remark-gfm, no raw HTML) with twui components; rail previews are plain text - Fix header button overflow in the model and computer selects - Bump @moduletrace/twui to 0.1.2 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -5,12 +5,16 @@ import { AppContext } from "@/src/pages/__root";
|
||||
import fetchApi from "@moduletrace/twui/utils/fetch/fetchApi";
|
||||
import useLiveBots from "@/src/hooks/use-live-bots";
|
||||
import useServerEvent from "@/src/hooks/use-server-event";
|
||||
import { BotComputersContext } from "../(data)/bot-computers-context";
|
||||
import useBotComputers from "../(hooks)/use-bot-computers";
|
||||
import ConversationHeader from "../(partials)/conversation-header";
|
||||
import MessageThread from "../(partials)/message-thread";
|
||||
import Composer from "../(partials)/composer";
|
||||
|
||||
/**
|
||||
* The 900px conversation column: header, bottom-anchored thread, composer.
|
||||
* The bot's computers are shared with the header's select and the computer
|
||||
* buttons in the thread, so a switch from either shows in both.
|
||||
*/
|
||||
export default function Conversation() {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
@@ -22,6 +26,10 @@ export default function Conversation() {
|
||||
|
||||
const bots = useLiveBots({ bots: pageProps?.bots || [], open_bot_id: bot?.id || undefined });
|
||||
const live_bot = bots.find((b) => b.id == bot?.id) || bot;
|
||||
const bot_computers = useBotComputers({
|
||||
bot_id: bot?.id || 0,
|
||||
computers: pageProps?.bot_computers || [],
|
||||
});
|
||||
|
||||
useServerEvent({
|
||||
event: "server:bot-message",
|
||||
@@ -44,21 +52,23 @@ export default function Conversation() {
|
||||
if (!bot?.id || !live_bot) return null;
|
||||
|
||||
return (
|
||||
<Stack className="grow min-w-0 h-full gap-0! items-stretch">
|
||||
<ConversationHeader
|
||||
bot={live_bot}
|
||||
bots={bots}
|
||||
harnesses={pageProps?.bot_harness_overview?.harnesses}
|
||||
/>
|
||||
<MessageThread bot={live_bot} messages={messages} />
|
||||
<Composer
|
||||
bot={bot}
|
||||
onSent={({ message }) =>
|
||||
setMessages((prev) =>
|
||||
prev.some((m) => m.id == message.id) ? prev : [...prev, message],
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<BotComputersContext.Provider value={bot_computers}>
|
||||
<Stack className="grow min-w-0 h-full gap-0! items-stretch">
|
||||
<ConversationHeader
|
||||
bot={live_bot}
|
||||
bots={bots}
|
||||
harnesses={pageProps?.bot_harness_overview?.harnesses}
|
||||
/>
|
||||
<MessageThread bot={live_bot} messages={messages} />
|
||||
<Composer
|
||||
bot={bot}
|
||||
onSent={({ message }) =>
|
||||
setMessages((prev) =>
|
||||
prev.some((m) => m.id == message.id) ? prev : [...prev, message],
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
</BotComputersContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,17 @@ import Span from "@moduletrace/twui/layout/Span";
|
||||
import H2 from "@moduletrace/twui/layout/H2";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import useServerEvent from "@/src/hooks/use-server-event";
|
||||
import type { BotUpdatedData } from "@/src/types";
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
import type { BotJobUpdateData, BotUpdatedData } from "@/src/types";
|
||||
import TaskStepRow from "../(partials)/task-step-row";
|
||||
import OpenComputerButton from "../(partials)/open-computer-button";
|
||||
|
||||
/**
|
||||
* 280px panel listing the steps the bot is taking, kept current by the
|
||||
* active task each `server:bot-updated` push carries. Hidden below 1280px.
|
||||
* active task each `server:bot-updated` push carries. Without planned
|
||||
* steps it shows what the task's job has done (`bots_queue_updates`), the
|
||||
* newest as in progress, appended live from `server:bot-job-update`.
|
||||
* Hidden below 1280px.
|
||||
*/
|
||||
export default function WorkingOnPanel() {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
@@ -24,6 +28,17 @@ export default function WorkingOnPanel() {
|
||||
},
|
||||
});
|
||||
|
||||
useServerEvent({
|
||||
event: "server:bot-job-update",
|
||||
onData: (data: BotJobUpdateData) => {
|
||||
setTask((prev) =>
|
||||
prev?.id && prev.id == data.task_id && !prev.updates.some((u) => u.id == data.id)
|
||||
? { ...prev, updates: [...prev.updates, data].slice(-AppData.BotJobUpdatesShown) }
|
||||
: prev,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className="hidden xl:flex w-[280px] shrink-0 h-full gap-0! items-stretch px-6 border-0 border-l border-solid border-hairline"
|
||||
@@ -38,13 +53,21 @@ export default function WorkingOnPanel() {
|
||||
{task.title}
|
||||
</H2>
|
||||
<Stack className="mt-4 gap-0! items-stretch">
|
||||
{task.steps.map((step) => (
|
||||
<TaskStepRow
|
||||
key={step.id}
|
||||
label={step.label || ""}
|
||||
state={step.state || "pending"}
|
||||
/>
|
||||
))}
|
||||
{task.steps.length
|
||||
? task.steps.map((step) => (
|
||||
<TaskStepRow
|
||||
key={step.id}
|
||||
label={step.label || ""}
|
||||
state={step.state || "pending"}
|
||||
/>
|
||||
))
|
||||
: task.updates.map((update, index) => (
|
||||
<TaskStepRow
|
||||
key={update.id}
|
||||
label={update.text || ""}
|
||||
state={index == task.updates.length - 1 ? "active" : "done"}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user