Finish the mobile performance pass

- Cache the model catalog once per user and each conversation's setting
  separately; clear both on logout and after model saves
- Compress hashed assets asynchronously with a byte budget and set Vary
- Memoize message parsing and throttle selection reads
- Debounce draft writes to localStorage
- Keep the composer editor on the page and focused, as before
- Add a bundle budget check and run it in CI
- Show a loading block while sessions load and a workspace avatar in the
  mobile conversation header
This commit is contained in:
2026-10-20 14:37:11 +01:00
parent c1a94fc9a5
commit 4f7e0a6bf0
38 changed files with 1320 additions and 334 deletions
@@ -26,8 +26,10 @@ type Params = {
* 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.
* the models modal: the install-wide catalog (`api/admin/harness/catalog`)
* and the conversation's small setting
* (`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({
@@ -1,33 +0,0 @@
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,
});
}
@@ -0,0 +1,61 @@
import type { BotHarnessSettingsPayload, HarnessSettingOverview } 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 form edits, without the install-wide
* catalog: a worker's own settings, or for the coordinator the defaults it
* runs on. In a workspace the workspace's own models come first, so what
* runs now shows its pick.
*/
export default async function grabConversationHarnessSettings({
bot,
workspace_id,
}: Params): Promise<BotHarnessSettingsPayload> {
const overview =
bot.kind == "coordinator" && !workspace_id
? { harnesses: (await grabHarnessOverview()).harnesses }
: await grabBotHarnessOverview({
bot_id: bot.kind == "coordinator" ? undefined : bot.id,
workspace_id: workspace_id || undefined,
});
return { harnesses: overview.harnesses.map(stripCatalog) };
}
/**
* A harness overview item without the models and providers the catalog
* already carries. A picked model or provider the catalog no longer lists
* travels with the setting, so the form can still show it.
*/
function stripCatalog({
title,
description,
required,
automatic,
providers,
models,
...setting
}: (Awaited<ReturnType<typeof grabBotHarnessOverview>>)["harnesses"][number]): HarnessSettingOverview {
const pinned_model = setting.model_id
? models.find((model) => model.id == setting.model_id)
: undefined;
const unavailable_provider =
setting.provider_id && !providers.some((provider) => provider.id == setting.provider_id)
? { id: setting.provider_id, name: "Unavailable provider" }
: undefined;
return {
...setting,
...(pinned_model ? { pinned_model } : {}),
...(unavailable_provider ? { unavailable_provider } : {}),
};
}
@@ -0,0 +1,43 @@
import { Harnesses } from "@/src/dict/harness-dict";
import modelServesHarness from "@/src/harness/catalog/model-serves-harness";
import type {
HarnessCatalogPayload,
HarnessProviderChoice,
} from "@/src/types";
import grabHarnessModelEntries from "./grab-harness-model-entries";
/**
* Every harness's install-wide catalog: what it is for, the providers with
* a model that can do the job, and those models. One copy serves every
* bot and workspace, so the models modal caches it once per user.
*/
export default async function grabHarnessCatalog(): Promise<HarnessCatalogPayload> {
const entries = await grabHarnessModelEntries();
return {
harnesses: Harnesses.map(({ value, title, description, required, automatic }) => {
const serving = entries.filter(({ model }) =>
modelServesHarness({ model, harness: value }),
);
const providers: HarnessProviderChoice[] = [
...new Map(
serving.map(({ option }) => [
option.provider_id,
{ id: option.provider_id, name: option.provider_name },
]),
).values(),
];
return {
harness: value,
title,
description,
required,
automatic,
providers,
models: serving.map(({ option }) => option),
};
}),
};
}