perf(team): reduce render hot path lookups
This commit is contained in:
parent
97eb98466a
commit
5e3e77bf65
2 changed files with 47 additions and 12 deletions
|
|
@ -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)})`;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue