perf(team): reduce render hot path lookups

This commit is contained in:
777genius 2026-05-28 22:03:40 +03:00
parent 97eb98466a
commit 5e3e77bf65
2 changed files with 47 additions and 12 deletions

View file

@ -19,10 +19,11 @@ export function linkifyMentionsInMarkdown(
text: string,
memberColorMap: Map<string, string>
): string {
if (memberColorMap.size === 0) return text;
if (memberColorMap.size === 0 || !text.includes('@')) return text;
// Sort by name length descending for greedy matching
const names = [...memberColorMap.keys()].sort((a, b) => b.length - a.length);
const canonicalNameByLower = new Map(names.map((name) => [name.toLowerCase(), name]));
const escaped = names.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
const pattern = new RegExp(
// eslint-disable-next-line no-useless-escape -- backslash-quote and backslash-hyphen needed in template literal for RegExp
@ -32,7 +33,7 @@ export function linkifyMentionsInMarkdown(
return text.replace(pattern, (_match: string, prefix: string, name: string) => {
// Find the canonical name (case-insensitive lookup)
const canonical = names.find((n) => n.toLowerCase() === name.toLowerCase()) ?? name;
const canonical = canonicalNameByLower.get(name.toLowerCase()) ?? name;
const color = memberColorMap.get(canonical) ?? '';
return `${prefix}[@${canonical}](mention://${encodeURIComponent(color)}/${encodeURIComponent(canonical)})`;
});
@ -51,10 +52,11 @@ export function linkifyTeamMentionsInMarkdown(
teamNames: ReadonlySet<string> | readonly string[]
): string {
const names: readonly string[] = Array.isArray(teamNames) ? teamNames : [...teamNames];
if (names.length === 0) return text;
if (names.length === 0 || !text.includes('@')) return text;
// Sort by name length descending for greedy matching
const sorted = [...names].sort((a, b) => b.length - a.length);
const canonicalNameByLower = new Map(sorted.map((name) => [name.toLowerCase(), name]));
const escaped = sorted.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
const pattern = new RegExp(
// eslint-disable-next-line no-useless-escape -- backslash-quote and backslash-hyphen needed in template literal for RegExp
@ -63,7 +65,7 @@ export function linkifyTeamMentionsInMarkdown(
);
return text.replace(pattern, (_match: string, prefix: string, name: string) => {
const canonical = sorted.find((n) => n.toLowerCase() === name.toLowerCase()) ?? name;
const canonical = canonicalNameByLower.get(name.toLowerCase()) ?? name;
return `${prefix}[${canonical}](team://${encodeURIComponent(canonical)})`;
});
}

View file

@ -19,6 +19,8 @@ type RuntimeAwareProviderStatus = Pick<
CliProviderStatus,
'providerId' | 'authMethod' | 'backend' | 'modelCatalog'
>;
type RuntimeAwareModelCatalog = NonNullable<RuntimeAwareProviderStatus['modelCatalog']>;
type RuntimeAwareCatalogModel = RuntimeAwareModelCatalog['models'][number];
export interface TeamProviderModelOption {
value: string;
@ -216,6 +218,34 @@ const SUPPORTED_ANTHROPIC_TEAM_MODELS = new Set<string>([
'claude-haiku-4-5-20251001',
]);
const runtimeCatalogModelIndexCache = new WeakMap<
RuntimeAwareModelCatalog,
Map<string, RuntimeAwareCatalogModel>
>();
function getRuntimeCatalogModelIndex(
modelCatalog: RuntimeAwareModelCatalog
): Map<string, RuntimeAwareCatalogModel> {
const cached = runtimeCatalogModelIndexCache.get(modelCatalog);
if (cached) {
return cached;
}
const index = new Map<string, RuntimeAwareCatalogModel>();
for (const model of modelCatalog.models) {
const launchModel = model.launchModel.trim();
if (launchModel) {
index.set(launchModel, model);
}
const id = model.id.trim();
if (id) {
index.set(id, model);
}
}
runtimeCatalogModelIndexCache.set(modelCatalog, index);
return index;
}
export function isSupportedAnthropicTeamModel(model: string | undefined): boolean {
const trimmed = model?.trim();
if (!trimmed) {
@ -294,17 +324,13 @@ function getRuntimeCatalogModel(
providerId: SupportedProviderId | undefined,
model: string | undefined,
providerStatus?: RuntimeAwareProviderStatus | null
): NonNullable<RuntimeAwareProviderStatus['modelCatalog']>['models'][number] | null {
): RuntimeAwareCatalogModel | null {
const trimmed = model?.trim();
if (!providerId || !trimmed || providerStatus?.modelCatalog?.providerId !== providerId) {
return null;
}
return (
providerStatus.modelCatalog.models.find(
(item) => item.launchModel === trimmed || item.id === trimmed
) ?? null
);
return getRuntimeCatalogModelIndex(providerStatus.modelCatalog).get(trimmed) ?? null;
}
export function getTeamModelBadgeLabel(
@ -456,11 +482,18 @@ export function sortTeamProviderModels(
return sorted;
}
const freeByModel = new Map(
sorted.map((model) => [
model,
isFreeOpenCodeModelForOrdering(providerId, model, providerStatus),
])
);
return sorted
.map((model, index) => ({ model, index }))
.sort((left, right) => {
const leftFree = isFreeOpenCodeModelForOrdering(providerId, left.model, providerStatus);
const rightFree = isFreeOpenCodeModelForOrdering(providerId, right.model, providerStatus);
const leftFree = freeByModel.get(left.model) ?? false;
const rightFree = freeByModel.get(right.model) ?? false;
if (leftFree !== rightFree) {
return leftFree ? -1 : 1;
}