From 53cf054d705b6c513f33d46742b5ce41ef7de969 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Mon, 19 Oct 2026 17:27:57 +0100 Subject: [PATCH] List the open bot's recent sessions in the rail Show the latest five sessions under the active bot, with All sessions opening the existing modal once there are more. Share the session modal, row and hook outside the conversation page, and mark the current session with a muted row. --- AGENTS.md | 2 +- .../general}/bot-session-row.tsx | 12 +++- .../general}/bot-sessions-modal.tsx | 48 ++++++++++----- src/data/app-data.ts | 5 ++ .../db/bot-sessions/grab-bot-sessions.ts | 11 +++- .../frontend}/post-session-api.ts | 0 .../(hooks) => hooks}/use-bot-sessions.ts | 14 +++-- .../bots/(partials)/rail-bot-entry.tsx | 22 +++++++ .../bots/(partials)/rail-bot-session-row.tsx | 54 ++++++++++++++++ .../bots/(partials)/rail-bot-sessions.tsx | 61 +++++++++++++++++++ .../bots/(partials)/rail-bots-section.tsx | 16 ++++- .../bots/(partials)/rail-crew-list.tsx | 16 +++-- .../bots/(partials)/rail-workspace-list.tsx | 1 + .../[bot_id]/(hooks)/use-reply-catch-up.ts | 2 +- .../(partials)/conversation-header.tsx | 2 +- .../(partials)/new-session-button.tsx | 2 +- src/pages/api/admin/bots/list-sessions.ts | 3 +- src/schemas/bot-schema.ts | 4 ++ 18 files changed, 236 insertions(+), 39 deletions(-) rename src/{pages/admin/bots/[bot_id]/(partials) => components/general}/bot-session-row.tsx (82%) rename src/{pages/admin/bots/[bot_id]/(partials) => components/general}/bot-sessions-modal.tsx (69%) rename src/{pages/admin/bots/[bot_id]/(functions) => functions/frontend}/post-session-api.ts (100%) rename src/{pages/admin/bots/[bot_id]/(hooks) => hooks}/use-bot-sessions.ts (87%) create mode 100644 src/layouts/bots/(partials)/rail-bot-entry.tsx create mode 100644 src/layouts/bots/(partials)/rail-bot-session-row.tsx create mode 100644 src/layouts/bots/(partials)/rail-bot-sessions.tsx diff --git a/AGENTS.md b/AGENTS.md index caa8073..1a30648 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -87,7 +87,7 @@ Inspect live data with `bun llm-code-assistant/query-db.ts ""` - a read-onl - **Access (the crew is shared):** bots, workspaces and computers belong to the install, not to a user (their `user_id` is only who created them, not a foreign key). Nobody reaches a bot until a super admin or admin grants it: `bot_users` (a worker assigned to a user, for its own chat) and `workspace_users` (a user added to a workspace, to chat with every worker in it there). `crewRole` (`src/utils/rbac/crew-role.ts`): `super_admin` reaches everything and is the only one who ever sees, chats with or edits the coordinator; `admin` reaches every worker and workspace; anyone else (`member`) only their grants. `canManageCrew` (super admins and admins) creates, edits and deletes workers and workspaces, sets their models and pictures, and hands out access (the Bots and Workspaces groups on a user's settings page, `user-access-groups.tsx`, each added or taken away at once by `api/admin/users/add-access` and `remove-access` through `setUserGrant`); the structural DB helpers check it themselves (`assertCrewManager`, `src/functions/backend/db/access/`). The SQL conditions live in `src/utils/crew-access/` (`chatBotsSql`, `workspacesSql`, `workspaceBotsSql`, `computersSql`, tested against SQLite) and back `grabChatBot` (its own chat), `grabUserBot` (anywhere: its own chat or a workspace), `grabUserWorkspace(s)`, `grabWorkspaceBot`, `grabBots` and `grabUserComputer(s)`. Conversations are per user (`bot_sessions.user_id`): a job runs for the user whose session it's in (`runBotJob` reads it), with that user's memory and only what they reach. A bot's face and status line are per user too (`bot_user_faces`, set by `setBotFace` for the job's user, `grabJobUserId`; no row is idle), so needs-you or working only shows to the user the job is for; every bot read for a page overlays the viewer's (`botFaceSql`, `src/utils/bot-face-sql.ts`). Live updates go to the session's user (`pushToSessionUser`), a face change to its user, and a bot change (its computer) to everyone who sees it (`pushBotUpdated`, `grabBotAudience`, each with their own face and active task); its status line never names a task (`BotTaskStatusLine`). Bot and workspace pictures are served to whoever sees them (`grabSharedPictureMedia`). A member with nothing granted lands on an empty `/admin/bots`; a workspace's dashboard is open to everyone in it, its settings link and computers' settings links only to `canManageCrew`. `src/scripts/share-crew.ts` (safe to rerun) moved an install from one crew per user to this. - **Tables:** `bots` (`kind` worker|coordinator, `hue`, `role`, `system_prompt` (the bot's core instructions, markdown)), `bot_user_faces` (`face`, `status_line` per bot and user), `bot_sessions`, `bot_messages`, `bot_tasks`, `bot_task_steps`. Enums in `src/dict/bot-dict.ts`. -- **Threads and sessions:** each user has one conversation with a bot in its own chat and one per workspace it's in (the coordinator in every workspace). Each is split into `bot_sessions` (`user_id`, `bot_id`, `workspace_id` null for its own chat, `status` active|closed, `title` from the first user message, `summary`, `previous_session_id`, `unread`, `last_activity_at`); `bot_messages`, `bot_tasks` and `bots_queue` carry `session_id` and `workspace_id`. Helpers in `src/functions/backend/db/bot-sessions/`: `ensureBotSession` (the active one, else a new one; `rollover` starts the next once it has `AppData.BotSessionMaxMessages`), `startBotSession`, `reopenBotSession`, `grabBotSessions` (search over title, summary and message text), `grabUserConversation` (access and membership), `grabConversationProps` (the conversation pages' props). A new session's first reply gets the previous one's summary (`grab-session-carry-over.ts`, written once with the text harness). The header's sessions icon (`(partials)/bot-sessions-modal.tsx`) lists, searches, starts and reopens sessions, and the new-session icon beside it (`(partials)/new-session-button.tsx`) starts one (`api/admin/bots/list-sessions`, `new-session`, `reopen-session`). Unread, the rail's last message and activity are per thread, from its sessions. Job results go into the job's own session, even after it closes. +- **Threads and sessions:** each user has one conversation with a bot in its own chat and one per workspace it's in (the coordinator in every workspace). Each is split into `bot_sessions` (`user_id`, `bot_id`, `workspace_id` null for its own chat, `status` active|closed, `title` from the first user message, `summary`, `previous_session_id`, `unread`, `last_activity_at`); `bot_messages`, `bot_tasks` and `bots_queue` carry `session_id` and `workspace_id`. Helpers in `src/functions/backend/db/bot-sessions/`: `ensureBotSession` (the active one, else a new one; `rollover` starts the next once it has `AppData.BotSessionMaxMessages`), `startBotSession`, `reopenBotSession`, `grabBotSessions` (search over title, summary and message text), `grabUserConversation` (access and membership), `grabConversationProps` (the conversation pages' props). A new session's first reply gets the previous one's summary (`grab-session-carry-over.ts`, written once with the text harness). The header's sessions icon (`src/components/general/bot-sessions-modal.tsx`) lists, searches, starts and reopens sessions, and the new-session icon beside it (`(partials)/new-session-button.tsx`) starts one (`api/admin/bots/list-sessions`, `new-session`, `reopen-session`). The rail lists the open bot's latest `AppData.RailRecentSessions` sessions under its row (`rail-bot-sessions.tsx`), and once there are more, "All sessions" opens the same modal. Unread, the rail's last message and activity are per thread, from its sessions. Job results go into the job's own session, even after it closes. - **Coordinator:** the install has exactly one `kind: coordinator` bot, the super admin's alone, that coordinates the crew and delegates to every worker. It is created at signup (`bun run seed`) and, as a fallback, by the super admin's `/admin/bots`, all via `ensureCoordinatorBot` (`src/functions/backend/db/bots/ensure-coordinator-bot.ts`, idempotent). Name in `CoordinatorBotName` (`src/dict/bot-dict.ts`). Its default prompt is `CoordinatorSystemPrompt` (`src/data/coordinator-system-prompt.ts`): attached at creation, and backfilled onto a coordinator whose prompt is empty, never over an edited one. - **Routes:** `/admin/bots` ensures the coordinator and redirects the super admin to its chat (`/admin/bots/1` on a fresh install), anyone else to their latest bot, else their first workspace, else an empty page; `/admin/bots/[bot_id]` renders the conversation (header, thread and composer, the last two held to 760px; there is no right panel); `/admin/bots/[bot_id]/models` is a worker bot's own model settings, for super admins and admins (the coordinator's redirects to its chat); `/admin/workspaces/[workspace_id]` is a workspace's dashboard (`(sections)/workspace-dashboard.tsx`: read-only picture, name, description and context, latest activity, bots with their per-workspace instructions and memory, computers; its header links into `/admin/settings/workspaces/[workspace_id]`, whose `workspace-overview.tsx` has the editable groups) and `/admin/workspaces/[workspace_id]/bots/[bot_id]` is the same `Conversation` section scoped to the workspace (`pageProps.workspace`; only the coordinator and member bots). All render in the full-bleed `src/layouts/bots/` layout (rail of conversations + user popover), which `__root.tsx` uses for every `/admin/*` page. - **Server:** `src/functions/backend/db/bots/` (`grab-bots`, `grab-bot-messages`, `grab-active-bot-task`, `grab-user-bot`, `create-bot`, `insert-user-message`). `api/admin/bots/send-message.ts` posts a user message to a bot the session user chats with (zod: `src/schemas/bot-schema.ts`), queues the bot's reply (`enqueueBotReply`) and wakes the queue (`src/bots-queue/wake-bots-queue.ts`; never import `src/bots-queue/index.ts` from a page or API route, it pulls the bunext runtime into the page bundles). `upload-attachment.ts` stores one file for the next message (private `media` row, paradigm `bot-attachment`, type from `attachmentFileKind`, documents get `text_content`, limits in `AppData.BotAttachment*`) and `send-message` takes their `media_ids` (checked to be the user's own by `grabAttachableMedia`), linked in `bot_message_media`; messages carry `attachments` (`BotMessageWithAttachments`). `mark-read.ts` clears a session's unread when a message arrives in the open conversation; `session-messages.ts` returns a session's latest messages, which the open conversation polls every `AppData.BotReplyCatchUpMs` while a reply is due (`use-reply-catch-up`), so a reply whose live push was missed (socket down) still lands without a reload (replayed as a `server:bot-message`, so the rail and unread take it too); `create-bot.ts` adds a worker (and puts it in a workspace when given one); `save-harness-settings.ts` saves a worker bot's models. diff --git a/src/pages/admin/bots/[bot_id]/(partials)/bot-session-row.tsx b/src/components/general/bot-session-row.tsx similarity index 82% rename from src/pages/admin/bots/[bot_id]/(partials)/bot-session-row.tsx rename to src/components/general/bot-session-row.tsx index d2c8cac..00a70ea 100644 --- a/src/pages/admin/bots/[bot_id]/(partials)/bot-session-row.tsx +++ b/src/components/general/bot-session-row.tsx @@ -33,14 +33,20 @@ export default function BotSessionRow({ session, loading, disabled, onOpen }: Pr loading={loading} disabled={active || disabled} onClick={onOpen} + buttonContentProps={{ className: "w-full min-w-0 flex-nowrap" }} className={twMerge( - "w-full justify-start! items-start! gap-0! px-3! py-2.5! rounded-default text-left text-ink dark:text-ink hover:bg-sunken hover:dark:bg-sunken focus-visible:ring-2 focus-visible:ring-ink", - active && "bg-ink/5 opacity-100!", + "w-full justify-start! items-start! gap-0! px-3! py-2.5! rounded-default text-left text-ink dark:text-ink hover:bg-ink/5 hover:dark:bg-ink/5 focus-visible:ring-2 focus-visible:ring-ink", + active && "bg-sunken hover:bg-sunken hover:dark:bg-sunken opacity-100!", )} > - + {session.title || "Untitled session"} {session.unread && !active ? ( diff --git a/src/pages/admin/bots/[bot_id]/(partials)/bot-sessions-modal.tsx b/src/components/general/bot-sessions-modal.tsx similarity index 69% rename from src/pages/admin/bots/[bot_id]/(partials)/bot-sessions-modal.tsx rename to src/components/general/bot-sessions-modal.tsx index de90611..5e4c678 100644 --- a/src/pages/admin/bots/[bot_id]/(partials)/bot-sessions-modal.tsx +++ b/src/components/general/bot-sessions-modal.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, type ComponentProps, type ReactNode } from "react"; import Input from "@moduletrace/twui/form/Input"; import Modal from "@moduletrace/twui/elements/Modal"; import Button from "@moduletrace/twui/layout/Button"; @@ -6,12 +6,11 @@ import H2 from "@moduletrace/twui/layout/H2"; import Row from "@moduletrace/twui/layout/Row"; import Span from "@moduletrace/twui/layout/Span"; import Stack from "@moduletrace/twui/layout/Stack"; -import { History, Plus } from "lucide-react"; +import { History, Plus, CircleAlert } from "lucide-react"; import type { BotWithLastMessage } from "@/src/types"; -import SettingsStatus from "@/src/pages/admin/settings/(partials)/settings-status"; -import useBotSessions from "../(hooks)/use-bot-sessions"; +import useBotSessions from "@/src/hooks/use-bot-sessions"; import BotSessionRow from "./bot-session-row"; -import HeaderIconButton from "@/src/components/general/header-icon-button"; +import HeaderIconButton from "./header-icon-button"; type Props = { bot: BotWithLastMessage; @@ -20,14 +19,26 @@ type Props = { * A bot message arrived in another session of this conversation. */ has_other_unread?: boolean; + /** + * What opens it; the conversation header's sessions icon by default. + */ + target?: ReactNode; + target_wrapper_props?: ComponentProps["targetWrapperProps"]; }; /** - * The header's sessions icon. A conversation is split into sessions so a - * bot's context never grows without bound; this lists them, searches - * them, starts a fresh one, or reopens an old one to carry on. + * The sessions icon (or another target) that opens a conversation's + * sessions. A conversation is split into sessions so a bot's context never + * grows without bound; this lists them, searches them, starts a fresh one, + * or reopens an old one to carry on. */ -export default function BotSessionsModal({ bot, workspace_id, has_other_unread }: Props) { +export default function BotSessionsModal({ + bot, + workspace_id, + has_other_unread, + target, + target_wrapper_props, +}: Props) { const [open, setOpen] = useState(false); const sessions = useBotSessions({ bot_id: Number(bot.id), workspace_id, open }); const name = bot.name || "the bot"; @@ -35,12 +46,14 @@ export default function BotSessionsModal({ bot, workspace_id, has_other_unread } return ( + target ?? ( + + ) } - targetWrapperProps={{ className: "shrink-0" }} + targetWrapperProps={target_wrapper_props ?? { className: target ? "w-full" : "shrink-0" }} open={open} setOpen={setOpen} className="max-w-lg bg-paper p-6" @@ -74,7 +87,12 @@ export default function BotSessionsModal({ bot, workspace_id, has_other_unread } onChange={(e) => sessions.setQuery(e.target.value)} wrapperWrapperProps={{ className: "mt-4" }} /> - + {sessions.error ? ( + + + {sessions.error} + + ) : null} {sessions.sessions.map((session) => ( { const search = query?.trim(); const like = search ? `%${search.replace(/[\\%_]/g, (c) => `\\${c}`)}%` : null; - const limit = AppData.BotSessionsPageSize; + const page_size = limit || AppData.BotSessionsPageSize; let sql = ``; @@ -56,8 +61,8 @@ export default async function grabBotSessions({ bot_id, workspace_id ? Number(workspace_id) : null, ...(like ? [like, like, like] : []), - limit, - (Math.max(1, page) - 1) * limit, + page_size, + (Math.max(1, page) - 1) * page_size, ], }); diff --git a/src/pages/admin/bots/[bot_id]/(functions)/post-session-api.ts b/src/functions/frontend/post-session-api.ts similarity index 100% rename from src/pages/admin/bots/[bot_id]/(functions)/post-session-api.ts rename to src/functions/frontend/post-session-api.ts diff --git a/src/pages/admin/bots/[bot_id]/(hooks)/use-bot-sessions.ts b/src/hooks/use-bot-sessions.ts similarity index 87% rename from src/pages/admin/bots/[bot_id]/(hooks)/use-bot-sessions.ts rename to src/hooks/use-bot-sessions.ts index 5080dbe..2381715 100644 --- a/src/pages/admin/bots/[bot_id]/(hooks)/use-bot-sessions.ts +++ b/src/hooks/use-bot-sessions.ts @@ -1,21 +1,25 @@ import { useEffect, useState } from "react"; import type { BotSessionListItem } from "@/src/types"; -import postSessionApi from "../(functions)/post-session-api"; +import postSessionApi from "@/src/functions/frontend/post-session-api"; type Params = { bot_id: number; workspace_id?: number | null; /** - * Loads only while the modal is open. + * Loads only while the caller needs the sessions. */ open: boolean; + /** + * How many to load, latest first; the modal leaves it out for a page. + */ + limit?: number; }; /** * 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) { +export default function useBotSessions({ bot_id, workspace_id, open, limit }: Params) { const [query, setQuery] = useState(""); const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(false); @@ -31,7 +35,7 @@ export default function useBotSessions({ bot_id, workspace_id, open }: Params) { const res = await postSessionApi({ path: "list-sessions", - body: { bot_id, workspace_id, query: query.trim() || undefined }, + body: { bot_id, workspace_id, query: query.trim() || undefined, limit }, }).catch(() => null); if (stale) return; @@ -50,7 +54,7 @@ export default function useBotSessions({ bot_id, workspace_id, open }: Params) { stale = true; clearTimeout(timer); }; - }, [open, query, bot_id, workspace_id]); + }, [open, query, bot_id, workspace_id, limit]); const act = async ({ id, path, body }: { id: number | "new"; path: "new-session" | "reopen-session"; body: Record }) => { if (busy_id) return; diff --git a/src/layouts/bots/(partials)/rail-bot-entry.tsx b/src/layouts/bots/(partials)/rail-bot-entry.tsx new file mode 100644 index 0000000..4548a75 --- /dev/null +++ b/src/layouts/bots/(partials)/rail-bot-entry.tsx @@ -0,0 +1,22 @@ +import type { BotWithLastMessage } from "@/src/types"; +import BotRow from "./bot-row"; +import RailBotSessions from "./rail-bot-sessions"; + +type Props = { + bot: BotWithLastMessage; + href: string; + selected?: boolean; + workspace_id?: number | null; +}; + +/** + * A worker in the rail, and its sessions while it is the open conversation. + */ +export default function RailBotEntry({ bot, href, selected, workspace_id }: Props) { + return ( + <> + + {selected ? : null} + + ); +} diff --git a/src/layouts/bots/(partials)/rail-bot-session-row.tsx b/src/layouts/bots/(partials)/rail-bot-session-row.tsx new file mode 100644 index 0000000..4fd708a --- /dev/null +++ b/src/layouts/bots/(partials)/rail-bot-session-row.tsx @@ -0,0 +1,54 @@ +import Button from "@moduletrace/twui/layout/Button"; +import Span from "@moduletrace/twui/layout/Span"; +import { twMerge } from "tailwind-merge"; +import type { BotSessionListItem } from "@/src/types"; +import relativeTime from "@/src/utils/relative-time"; + +type Props = { + session: BotSessionListItem; + loading?: boolean; + disabled?: boolean; + onOpen: () => void; +}; + +/** + * One session in the rail's sub-list under the open bot: aligned with the + * bot's name, its title and how recent. The active one says so; any other + * reopens on click. + */ +export default function RailBotSessionRow({ session, loading, disabled, onOpen }: Props) { + const active = session.status == "active"; + const at = session.last_activity_at || session.created_at; + + return ( + + ); +} diff --git a/src/layouts/bots/(partials)/rail-bot-sessions.tsx b/src/layouts/bots/(partials)/rail-bot-sessions.tsx new file mode 100644 index 0000000..9079fee --- /dev/null +++ b/src/layouts/bots/(partials)/rail-bot-sessions.tsx @@ -0,0 +1,61 @@ +import Button from "@moduletrace/twui/layout/Button"; +import Span from "@moduletrace/twui/layout/Span"; +import Stack from "@moduletrace/twui/layout/Stack"; +import BotSessionsModal from "@/src/components/general/bot-sessions-modal"; +import { AppData } from "@/src/data/app-data"; +import useBotSessions from "@/src/hooks/use-bot-sessions"; +import type { BotWithLastMessage } from "@/src/types"; +import RailBotSessionRow from "./rail-bot-session-row"; + +type Props = { + bot: BotWithLastMessage; + workspace_id?: number | null; +}; + +/** + * Under the open bot in the rail: its latest sessions, latest first, and, + * once there are more than the ones listed, "All sessions" which opens the + * conversation's sessions modal. Hidden when the rail collapses to icons. + */ +export default function RailBotSessions({ bot, workspace_id }: Props) { + const sessions = useBotSessions({ + bot_id: Number(bot.id), + workspace_id, + open: true, + limit: AppData.RailRecentSessions + 1, + }); + const shown = sessions.sessions.slice(0, AppData.RailRecentSessions); + + if (!shown.length) return null; + + return ( + + {shown.map((session) => ( + sessions.reopenSession({ session_id: Number(session.id) })} + /> + ))} + {sessions.sessions.length > AppData.RailRecentSessions ? ( + + All sessions + + } + /> + ) : null} + + ); +} diff --git a/src/layouts/bots/(partials)/rail-bots-section.tsx b/src/layouts/bots/(partials)/rail-bots-section.tsx index 1b258ee..fcf8bb8 100644 --- a/src/layouts/bots/(partials)/rail-bots-section.tsx +++ b/src/layouts/bots/(partials)/rail-bots-section.tsx @@ -4,7 +4,7 @@ import { AppData } from "@/src/data/app-data"; import type { BotWithLastMessage } from "@/src/types"; import pickRailBots from "../(utils)/pick-rail-bots"; import AllBotsModal from "./all-bots-modal"; -import BotRow from "./bot-row"; +import RailBotEntry from "./rail-bot-entry"; import RailSectionLabel from "./rail-section-label"; import RailSeeAllRow from "./rail-see-all-row"; @@ -15,6 +15,10 @@ type Props = { */ workers: BotWithLastMessage[]; selected_id?: number; + /** + * Set inside a workspace, whose conversations have their own sessions. + */ + workspace_id?: number | null; hrefFor: (bot: BotWithLastMessage) => string; /** * Shown when there are no bots yet. @@ -28,7 +32,7 @@ type Props = { * active (and any that want attention), then "See all bots" for the rest, * which lists them all in a modal. */ -export default function RailBotsSection({ title, workers, selected_id, hrefFor, empty, action }: Props) { +export default function RailBotsSection({ title, workers, selected_id, workspace_id, hrefFor, empty, action }: Props) { const [open, setOpen] = useState(false); const { shown, hidden } = pickRailBots({ workers, selected_id, limit: AppData.RailRecentBots }); @@ -36,7 +40,13 @@ export default function RailBotsSection({ title, workers, selected_id, hrefFor, <> {shown.map((bot) => ( - + ))} {workers.length ? null : ( diff --git a/src/layouts/bots/(partials)/rail-crew-list.tsx b/src/layouts/bots/(partials)/rail-crew-list.tsx index 497638b..465c526 100644 --- a/src/layouts/bots/(partials)/rail-crew-list.tsx +++ b/src/layouts/bots/(partials)/rail-crew-list.tsx @@ -4,6 +4,7 @@ import { AppContext } from "@/src/pages/__root"; import canManageCrew from "@/src/utils/rbac/can-manage-crew"; import type { BotWithLastMessage, WorkspaceWithActivity } from "@/src/types"; import CoordinatorRow from "./coordinator-row"; +import RailBotSessions from "./rail-bot-sessions"; import RailBotsSection from "./rail-bots-section"; import RailSectionAddButton from "./rail-section-add-button"; import RailWorkspacesSection from "./rail-workspaces-section"; @@ -27,11 +28,16 @@ export default function RailCrewList({ bots, workspaces, selected_id }: Props) { return ( <> {coordinator ? ( - + <> + + {coordinator.id === selected_id ? ( + + ) : null} + ) : null} openForm({ kind: "bot" })} />} diff --git a/src/layouts/bots/(partials)/rail-workspace-list.tsx b/src/layouts/bots/(partials)/rail-workspace-list.tsx index 27eaeb7..1310efb 100644 --- a/src/layouts/bots/(partials)/rail-workspace-list.tsx +++ b/src/layouts/bots/(partials)/rail-workspace-list.tsx @@ -44,6 +44,7 @@ export default function RailWorkspaceList({ workspace, bots, selected_id, on_wor title="Workspace bots" workers={workers} selected_id={on_workspace_page ? undefined : selected_id} + workspace_id={workspace.id ? Number(workspace.id) : null} hrefFor={(bot) => `${base}/bots/${bot.id}`} empty="No bots here yet." /> diff --git a/src/pages/admin/bots/[bot_id]/(hooks)/use-reply-catch-up.ts b/src/pages/admin/bots/[bot_id]/(hooks)/use-reply-catch-up.ts index 8fd67d7..e9fb2ab 100644 --- a/src/pages/admin/bots/[bot_id]/(hooks)/use-reply-catch-up.ts +++ b/src/pages/admin/bots/[bot_id]/(hooks)/use-reply-catch-up.ts @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; import { AppData } from "@/src/data/app-data"; import type { BotMessageWithAttachments, WebSocketDataType } from "@/src/types"; -import postSessionApi from "../(functions)/post-session-api"; +import postSessionApi from "@/src/functions/frontend/post-session-api"; type Params = { session_id?: number | null; diff --git a/src/pages/admin/bots/[bot_id]/(partials)/conversation-header.tsx b/src/pages/admin/bots/[bot_id]/(partials)/conversation-header.tsx index 1355a4b..61c3853 100644 --- a/src/pages/admin/bots/[bot_id]/(partials)/conversation-header.tsx +++ b/src/pages/admin/bots/[bot_id]/(partials)/conversation-header.tsx @@ -12,7 +12,7 @@ import { BotComputersContext } from "../(data)/bot-computers-context"; import BotComputersModal from "./bot-computers-modal"; import BotModelsModal from "./bot-models-modal"; import BotProfileModal from "./bot-profile-modal"; -import BotSessionsModal from "./bot-sessions-modal"; +import BotSessionsModal from "@/src/components/general/bot-sessions-modal"; import NewSessionButton from "./new-session-button"; import HeaderIconButton from "@/src/components/general/header-icon-button"; import RailDrawerButton from "@/src/layouts/bots/(partials)/rail-drawer-button"; diff --git a/src/pages/admin/bots/[bot_id]/(partials)/new-session-button.tsx b/src/pages/admin/bots/[bot_id]/(partials)/new-session-button.tsx index 4fbf83d..14851ce 100644 --- a/src/pages/admin/bots/[bot_id]/(partials)/new-session-button.tsx +++ b/src/pages/admin/bots/[bot_id]/(partials)/new-session-button.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { MessageSquarePlus } from "lucide-react"; -import postSessionApi from "../(functions)/post-session-api"; +import postSessionApi from "@/src/functions/frontend/post-session-api"; import HeaderIconButton from "@/src/components/general/header-icon-button"; type Props = { diff --git a/src/pages/api/admin/bots/list-sessions.ts b/src/pages/api/admin/bots/list-sessions.ts index 3e9f466..8c872e4 100644 --- a/src/pages/api/admin/bots/list-sessions.ts +++ b/src/pages/api/admin/bots/list-sessions.ts @@ -29,7 +29,7 @@ export const handler: BunextAPIRouteHandler< return { success: false, msg: `Unauthorized`, logoutUser: true }; } - const { bot_id, workspace_id, query, page } = parseWithSchema({ + const { bot_id, workspace_id, query, page, limit } = parseWithSchema({ schema: ListBotSessionsRequestSchema, data: body, }); @@ -44,6 +44,7 @@ export const handler: BunextAPIRouteHandler< workspace_id: conversation.workspace_id, query, page, + limit, }), }; } catch (error: unknown) { diff --git a/src/schemas/bot-schema.ts b/src/schemas/bot-schema.ts index f6590f1..736f556 100644 --- a/src/schemas/bot-schema.ts +++ b/src/schemas/bot-schema.ts @@ -63,6 +63,10 @@ export type BotConversationRequest = z.infer;