Tidy the bot chat: icon header controls, status in the rail, narrower thread

Drop the working-on panel. Rail rows show the bot's status line first.
The header's computer and model controls are ghost icons opening modals
that explain them, and the bot's text model, provider and computer show
under its name. The thread and composer are held to 760px.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 05:02:28 +01:00
co-authored by Claude Opus 5.5
parent 7b580d782e
commit 02ed47dd86
21 changed files with 263 additions and 379 deletions
@@ -1,17 +1,18 @@
import { useContext, useState } from "react";
import Row from "@moduletrace/twui/layout/Row";
import Stack from "@moduletrace/twui/layout/Stack";
import H1 from "@moduletrace/twui/layout/H1";
import Span from "@moduletrace/twui/layout/Span";
import { CpuAi, Monitor } from "lucide-react";
import BotStone from "@/src/components/general/bot-stone";
import type { BotFace, BotHue } from "@/src/dict/bot-dict";
import type { BotWithLastMessage, HarnessOverview } from "@/src/types";
import { crewStatusSentence } from "@/src/utils/crew-status";
import BotComputerSelect from "./bot-computer-select";
import { BotComputersContext } from "../(data)/bot-computers-context";
import BotComputersModal from "./bot-computers-modal";
import BotModelsModal from "./bot-models-modal";
import HeaderMetaItem from "./header-meta-item";
type Props = {
bot: BotWithLastMessage;
bots: BotWithLastMessage[];
/**
* The bot's models (the defaults for the coordinator).
*/
@@ -19,16 +20,17 @@ type Props = {
};
/**
* 64px header. The coordinator's status is the crew sentence. On the
* right, the computer the bot is on (`BotComputerSelect`), then the model
* button that opens the bot's models (the defaults on the coordinator,
* for super admins).
* 64px header. Under the name, what the bot runs on: its text model and
* provider, and the computer it's on. On the right, ghost icon buttons
* that open the computers and models modals. Kept here so a save or a
* switch shows under the name at once.
*/
export default function ConversationHeader({ bot, bots, harnesses }: Props) {
const status =
bot.kind === "coordinator"
? crewStatusSentence({ bots })
: bot.status_line || "";
export default function ConversationHeader({ bot, harnesses: initial }: Props) {
const [harnesses, setHarnesses] = useState(initial);
const { computers } = useContext(BotComputersContext);
const model = harnesses?.find((h) => h.harness == "text")?.current;
const computer = computers[0];
return (
<Row className="h-16 shrink-0 gap-3! flex-nowrap items-center px-6 border-0 border-b border-solid border-hairline">
@@ -38,16 +40,36 @@ export default function ConversationHeader({ bot, bots, harnesses }: Props) {
size={40}
/>
<Stack className="min-w-0 gap-0!">
<H1 className="text-lg! leading-6! tracking-normal! font-bold text-ink truncate">
<H1 className="text-lg! leading-6! tracking-normal! font-bold text-ink truncate w-full">
{bot.name}
</H1>
<Span className="text-[13px] leading-[18px] text-ink-secondary truncate">
{status}
</Span>
<Row className="min-w-0 w-full gap-x-3! gap-y-0! flex-nowrap">
<HeaderMetaItem
icon={CpuAi}
label="Model"
text={
model
? `${model.name} · ${model.provider_name}`
: "No model set up"
}
/>
{computer?.id ? (
<HeaderMetaItem
icon={Monitor}
label="Computer"
text={computer.name || `Computer ${computer.id}`}
/>
) : null}
</Row>
</Stack>
<Row className="ml-auto min-w-0 shrink gap-2! flex-nowrap justify-end">
<BotComputerSelect bot={bot} />
<BotModelsModal key={bot.id} bot={bot} harnesses={harnesses} />
<Row className="ml-auto shrink-0 gap-1! flex-nowrap justify-end">
<BotComputersModal bot={bot} />
<BotModelsModal
key={bot.id}
bot={bot}
harnesses={harnesses}
onSaved={setHarnesses}
/>
</Row>
</Row>
);