Make pages load fast on mobile

- Compress responses: page scripts and styles once (brotli or gzip, in
  memory) and cached immutable; pages and API JSON gzipped as they stream
- Service worker serves content-named assets cache-first
- Load the models modal's data when it opens (cached in localStorage)
  instead of shipping the model catalog in every conversation page
- Lazy-load the Tiptap composer and memoize thread messages
- Import lucide icons directly; twui 0.1.4 drops by-name icons from Button
- bun run start builds the pages minified before serving

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-20 13:41:45 +01:00
co-authored by Claude Opus 5.5
parent 3b355d68bc
commit c1a94fc9a5
36 changed files with 840 additions and 147 deletions
@@ -1,11 +1,9 @@
import type { PagePropsType } from "@/src/types";
import type { BotTextModel, PagePropsType } from "@/src/types";
import grabBots from "../bots/grab-bots";
import grabActiveBotTask from "../bots/grab-active-bot-task";
import grabBotMessages from "../bots/grab-bot-messages";
import grabBotExtensionsOverview from "../extensions/grab-bot-extensions-overview";
import grabBotComputers from "../computers/grab-bot-computers";
import grabBotHarnessOverview from "../harness/grab-bot-harness-overview";
import grabHarnessOverview from "../harness/grab-harness-overview";
import resolveHarnessCandidates from "@/src/harness/resolve/resolve-harness-candidates";
import grabUserWorkspaces from "../workspaces/grab-user-workspaces";
import ensureBotSession from "./ensure-bot-session";
@@ -25,9 +23,11 @@ type Params = {
* The page props of a conversation (`/admin/bots/<id>` or
* `/admin/workspaces/<id>/bots/<id>`): the rail's crew (the workspace's
* in a workspace) and workspaces, the bot, its active session (marked
* read) and its messages, its models, computers, tools and MCP servers
* and active task there, and whether its mic and read-aloud have a model
* to run on.
* read) and its messages, the text model it runs on, its computers,
* tools and MCP servers and active task there, and whether its mic and
* read-aloud have a model to run on. The full model settings are left to
* the models modal (`api/admin/bots/harness-overview`): they are most of
* a page's weight and only that modal uses them.
* Null when the user can't chat with the bot there.
*/
export default async function grabConversationProps({
@@ -60,21 +60,9 @@ export default async function grabConversationProps({
bot.unread = 0;
/**
* The header's models modal: a worker's own settings, or for the
* coordinator the defaults it runs on (without the providers). In a
* workspace the workspace's own models come first, so what runs now
* shows its pick.
*/
const [bot_messages, bot_harness_overview, bot_computers, bot_task, bot_extensions, transcription, speech] = await Promise.all([
const [bot_messages, bot_text_model, bot_computers, bot_task, bot_extensions, transcription, speech] = await Promise.all([
grabBotMessages({ session_id: session.id!, user_id }),
(bot.kind == "coordinator" && !scope
? grabHarnessOverview().then(({ harnesses }) => ({ harnesses }))
: grabBotHarnessOverview({
bot_id: bot.kind == "coordinator" ? undefined : bot.id,
workspace_id: scope || undefined,
})
).catch(() => null),
grabTextModel({ bot_id: bot.id, workspace_id: scope }),
grabBotComputers({ bot_id: bot.id, workspace_id: scope }),
grabActiveBotTask({ bot_id: bot.id, session_id: session.id }),
grabBotExtensionsOverview({ user_id, bot_id: bot.id, workspace_id: scope }),
@@ -89,7 +77,7 @@ export default async function grabConversationProps({
workspace: conversation.workspace,
bot_session: session,
bot_messages,
bot_harness_overview,
bot_text_model,
bot_computers,
bot_task,
bot_extensions,
@@ -105,3 +93,19 @@ async function hasHarnessModel(params: Parameters<typeof resolveHarnessCandidate
.then((candidates) => candidates.length > 0)
.catch(() => false);
}
/**
* The first text model the bot would try there, if any.
*/
async function grabTextModel(
params: Omit<Parameters<typeof resolveHarnessCandidates>[0], "harness">,
): Promise<BotTextModel | null> {
const [first] = await resolveHarnessCandidates({ ...params, harness: "text" }).catch(() => []);
if (!first) return null;
return {
name: first.model.name || first.model.slug,
provider_name: first.provider.name || first.provider.kind,
};
}
@@ -0,0 +1,33 @@
import type { BotHarnessOverviewPayload } from "@/src/types";
import grabBotHarnessOverview from "./grab-bot-harness-overview";
import grabHarnessOverview from "./grab-harness-overview";
type Params = {
/**
* A bot the caller has checked the session user chats with there.
*/
bot: { id?: number; kind?: string | null };
workspace_id?: number | null;
};
/**
* What a conversation's models modal edits: a worker's own settings, or
* for the coordinator the defaults it runs on (without the providers). In
* a workspace the workspace's own models come first, so what runs now
* shows its pick.
*/
export default async function grabConversationHarnessOverview({
bot,
workspace_id,
}: Params): Promise<BotHarnessOverviewPayload> {
if (bot.kind == "coordinator" && !workspace_id) {
const { harnesses } = await grabHarnessOverview();
return { harnesses };
}
return await grabBotHarnessOverview({
bot_id: bot.kind == "coordinator" ? undefined : bot.id,
workspace_id: workspace_id || undefined,
});
}