- 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]>
61 lines
2.1 KiB
TypeScript
61 lines
2.1 KiB
TypeScript
import type { BotWithLastMessage } from "@/src/types";
|
|
import BunSQLite from "@moduletrace/bun-sqlite";
|
|
|
|
type Params = {
|
|
user_id: number | string;
|
|
/**
|
|
* A workspace's crew instead: the coordinator and the bots in it, with
|
|
* their conversations in that workspace.
|
|
*/
|
|
workspace_id?: number | string | null;
|
|
};
|
|
|
|
/**
|
|
* A user's crew for the rail: the coordinator pinned first, then
|
|
* workers by latest activity. Each row carries its conversation's unread
|
|
* flag, latest activity and latest message: in its own chat, or in the
|
|
* workspace when one is given.
|
|
*/
|
|
export default async function grabBots({
|
|
user_id,
|
|
workspace_id,
|
|
}: Params): Promise<BotWithLastMessage[]> {
|
|
const scope = workspace_id ? Number(workspace_id) : null;
|
|
|
|
let sql = ``;
|
|
|
|
sql += `SELECT\n`;
|
|
sql += ` b.*,\n`;
|
|
sql += ` COALESCE(t.unread, 0) AS unread,\n`;
|
|
sql += ` t.last_activity_at AS last_activity_at,\n`;
|
|
sql += ` lm.text AS last_message_text,\n`;
|
|
sql += ` lm.created_at AS last_message_at\n`;
|
|
sql += `FROM bots b\n`;
|
|
sql += `LEFT JOIN (\n`;
|
|
sql += ` SELECT bot_id, MAX(unread) AS unread, MAX(last_activity_at) AS last_activity_at\n`;
|
|
sql += ` FROM bot_sessions WHERE workspace_id IS ? GROUP BY bot_id\n`;
|
|
sql += `) t ON t.bot_id = b.id\n`;
|
|
sql += `LEFT JOIN bot_messages lm ON lm.id = (\n`;
|
|
sql += ` SELECT id FROM bot_messages\n`;
|
|
sql += ` WHERE bot_id = b.id AND workspace_id IS ?\n`;
|
|
sql += ` ORDER BY created_at DESC, id DESC\n`;
|
|
sql += ` LIMIT 1\n`;
|
|
sql += `)\n`;
|
|
sql += `WHERE b.user_id = ?\n`;
|
|
|
|
if (scope) {
|
|
sql += `AND (b.kind = 'coordinator' OR b.id IN (SELECT bot_id FROM workspace_bots WHERE workspace_id = ?))\n`;
|
|
}
|
|
|
|
sql += `ORDER BY\n`;
|
|
sql += ` CASE WHEN b.kind = 'coordinator' THEN 0 ELSE 1 END,\n`;
|
|
sql += ` COALESCE(lm.created_at, t.last_activity_at, b.created_at) DESC\n`;
|
|
|
|
const res = await BunSQLite.sql<BotWithLastMessage>({
|
|
sql,
|
|
values: [scope, scope, user_id, ...(scope ? [scope] : [])],
|
|
});
|
|
|
|
return res.payload || [];
|
|
}
|