Add bot schedules (routines), open Activity, add Help and a Desktop computer view

Bots create, list, update and delete schedules (cron_jobs, Bun.cron in UTC);
a per-minute cron queues due runs as tasks. Routines, Activity (super admins)
and Help get their own rail aside. Help covers familiar problems, starting
with signing in to Google through Cookie-Editor over the new Desktop (noVNC)
view.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 10:52:32 +01:00
co-authored by Claude Opus 5.5
parent f24c0d7e82
commit c44048dbab
62 changed files with 1904 additions and 78 deletions
@@ -14,7 +14,7 @@ export default function ComposerAttachments({ pending, onRemove }: Props) {
if (!pending.length) return null;
return (
<Row className="w-full max-w-[760px] mx-auto gap-2! px-6 pb-3">
<Row className="w-full max-w-container mx-auto gap-2! px-6 pb-3">
{pending.map((item) => (
<ComposerAttachmentChip
key={item.key}
@@ -2,7 +2,10 @@ import { useState, type KeyboardEvent } from "react";
import Row from "@moduletrace/twui/layout/Row";
import Stack from "@moduletrace/twui/layout/Stack";
import Textarea from "@moduletrace/twui/form/Textarea";
import type { BotMessageWithAttachments, BotWithLastMessage } from "@/src/types";
import type {
BotMessageWithAttachments,
BotWithLastMessage,
} from "@/src/types";
import sendBotMessage from "../(functions)/send-bot-message";
import useComposerAttachments from "../(hooks)/use-composer-attachments";
import ComposerAttachButton from "./composer-attach-button";
@@ -30,7 +33,9 @@ export default function Composer({ bot, workspace_id, onSent }: Props) {
const files = useComposerAttachments();
const can_send =
Boolean(text.trim() || files.media_ids.length) && !sending && !files.uploading;
Boolean(text.trim() || files.media_ids.length) &&
!sending &&
!files.uploading;
async function send() {
if (!can_send || !bot.id) return;
@@ -57,7 +62,9 @@ export default function Composer({ bot, workspace_id, onSent }: Props) {
onSent({ message: res.singleRes });
}
function handleKeyDown(e: KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) {
function handleKeyDown(
e: KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>,
) {
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
e.preventDefault();
send();
@@ -77,10 +84,16 @@ export default function Composer({ bot, workspace_id, onSent }: Props) {
files.add({ files: [...e.dataTransfer.files] });
}}
>
<ComposerAttachments pending={files.pending} onRemove={files.remove} />
<ComposerAttachments
pending={files.pending}
onRemove={files.remove}
/>
<Stack className="shrink-0 gap-0! items-stretch border-0 border-t border-solid border-hairline">
<Row className="w-full max-w-[760px] mx-auto h-[88px] gap-4! flex-nowrap items-center px-6">
<ComposerAttachButton disabled={files.full} onFiles={files.add} />
<Row className="w-full max-w-container mx-auto h-[88px] gap-4! flex-nowrap items-center px-6">
<ComposerAttachButton
disabled={files.full}
onFiles={files.add}
/>
<Textarea
name="message"
aria-label={`Message ${bot.name}`}
@@ -106,7 +119,11 @@ export default function Composer({ bot, workspace_id, onSent }: Props) {
"p-0! outline-0! border-0! bg-transparent! dark:bg-transparent! rounded-none",
}}
/>
<ComposerSendButton disabled={!can_send} loading={sending || files.uploading} onClick={send} />
<ComposerSendButton
disabled={!can_send}
loading={sending || files.uploading}
onClick={send}
/>
</Row>
</Stack>
</Stack>
@@ -1,5 +1,8 @@
import Stack from "@moduletrace/twui/layout/Stack";
import type { BotMessageWithAttachments, BotWithLastMessage } from "@/src/types";
import type {
BotMessageWithAttachments,
BotWithLastMessage,
} from "@/src/types";
import groupMessages from "../(utils)/group-messages";
import BotThinkingRow from "./bot-thinking-row";
import MessageGroupBlock from "./message-group-block";
@@ -28,10 +31,14 @@ export default function MessageThread({ bot, messages, progress }: Props) {
role="log"
aria-live="polite"
aria-label={`Conversation with ${bot.name}`}
className="w-full max-w-[760px] mx-auto gap-4! items-stretch px-6 pt-6 pb-6"
className="w-full max-w-container mx-auto gap-4! items-stretch px-6 pt-6 pb-6"
>
{groups.map((group) => (
<MessageGroupBlock key={group.key} bot={bot} group={group} />
<MessageGroupBlock
key={group.key}
bot={bot}
group={group}
/>
))}
<BotThinkingRow bot={bot} progress={progress} />
</Stack>