forked from boos/BoosAPI
refactor: consolidate single-source routing and protocol helpers
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { BrandInfo } from '../../components/BrandIcon.js';
|
||||
import type { RouteDecision, RouteDecisionCandidate, RouteMode } from '../../../shared/tokenRouteContract.js';
|
||||
|
||||
export type RouteSortBy = 'modelPattern' | 'channelCount';
|
||||
export type RouteSortDir = 'asc' | 'desc';
|
||||
export type GroupFilter = null | '__all__' | number;
|
||||
export type RouteRoutingStrategy = 'weighted' | 'round_robin' | 'stable_first';
|
||||
export type RouteRowKind = 'persisted' | 'zero_channel';
|
||||
export type RouteMode = 'pattern' | 'explicit_group';
|
||||
|
||||
export type RouteChannelDraft = {
|
||||
accountId: number;
|
||||
tokenId: number;
|
||||
@@ -81,31 +80,6 @@ export type RouteSummaryRow = {
|
||||
isVirtual?: boolean;
|
||||
};
|
||||
|
||||
export type RouteDecisionCandidate = {
|
||||
channelId: number;
|
||||
accountId: number;
|
||||
username: string;
|
||||
siteName: string;
|
||||
tokenName: string;
|
||||
priority: number;
|
||||
weight: number;
|
||||
eligible: boolean;
|
||||
recentlyFailed: boolean;
|
||||
avoidedByRecentFailure: boolean;
|
||||
probability: number;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
export type RouteDecision = {
|
||||
requestedModel: string;
|
||||
actualModel: string;
|
||||
matched: boolean;
|
||||
selectedChannelId?: number;
|
||||
selectedLabel?: string;
|
||||
summary: string[];
|
||||
candidates: RouteDecisionCandidate[];
|
||||
};
|
||||
|
||||
export type ChannelDecisionState = {
|
||||
probability: number;
|
||||
showBar: boolean;
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import { getBrand, normalizeBrandIconKey, type BrandInfo } from '../../components/BrandIcon.js';
|
||||
import type { RouteRow, RouteChannel, RouteDecisionCandidate, ChannelDecisionState, RouteSummaryRow, RouteMode } from './types.js';
|
||||
import { normalizeTokenRouteMode, type RouteDecisionCandidate, type RouteMode } from '../../../shared/tokenRouteContract.js';
|
||||
import type { RouteRow, RouteChannel, ChannelDecisionState, RouteSummaryRow } from './types.js';
|
||||
import {
|
||||
isExactTokenRouteModelPattern,
|
||||
isTokenRouteRegexPattern,
|
||||
matchesTokenRouteModelPattern,
|
||||
parseTokenRouteRegexPattern,
|
||||
} from '../../../shared/tokenRoutePatterns.js';
|
||||
|
||||
export const AUTO_ROUTE_DECISION_LIMIT = 80;
|
||||
export const ROUTE_RENDER_CHUNK = 40;
|
||||
@@ -39,18 +46,15 @@ export const PLATFORM_ALIASES: Record<string, string> = {
|
||||
};
|
||||
|
||||
export function isRegexModelPattern(modelPattern: string): boolean {
|
||||
return modelPattern.trim().toLowerCase().startsWith('re:');
|
||||
return isTokenRouteRegexPattern(modelPattern);
|
||||
}
|
||||
|
||||
export function isExactModelPattern(modelPattern: string): boolean {
|
||||
const normalized = modelPattern.trim();
|
||||
if (!normalized) return false;
|
||||
if (isRegexModelPattern(normalized)) return false;
|
||||
return !/[\*\?]/.test(normalized);
|
||||
return isExactTokenRouteModelPattern(modelPattern);
|
||||
}
|
||||
|
||||
export function normalizeRouteMode(routeMode: RouteMode | string | null | undefined): RouteMode {
|
||||
return routeMode === 'explicit_group' ? 'explicit_group' : 'pattern';
|
||||
return normalizeTokenRouteMode(routeMode);
|
||||
}
|
||||
|
||||
export function isExplicitGroupRoute(route: Pick<RouteRow | RouteSummaryRow, 'routeMode'>): boolean {
|
||||
@@ -62,81 +66,11 @@ export function isRouteExactModel(route: Pick<RouteRow | RouteSummaryRow, 'model
|
||||
}
|
||||
|
||||
export function parseRegexModelPattern(modelPattern: string): { regex: RegExp | null; error: string | null } {
|
||||
if (!isRegexModelPattern(modelPattern)) return { regex: null, error: null };
|
||||
const body = modelPattern.trim().slice(3).trim();
|
||||
if (!body) return { regex: null, error: 're: 后缺少正则表达式' };
|
||||
try {
|
||||
return { regex: new RegExp(body), error: null };
|
||||
} catch (error) {
|
||||
return { regex: null, error: (error as Error)?.message || '无效正则' };
|
||||
}
|
||||
return parseTokenRouteRegexPattern(modelPattern);
|
||||
}
|
||||
|
||||
function globToRegexSource(glob: string): string {
|
||||
let source = '';
|
||||
for (let i = 0; i < glob.length; i += 1) {
|
||||
const ch = glob[i];
|
||||
if (ch === '*') {
|
||||
source += '.*';
|
||||
continue;
|
||||
}
|
||||
if (ch === '?') {
|
||||
source += '.';
|
||||
continue;
|
||||
}
|
||||
if (ch === '[') {
|
||||
const closeIndex = glob.indexOf(']', i + 1);
|
||||
if (closeIndex > i + 1) {
|
||||
source += glob.slice(i, closeIndex + 1);
|
||||
i = closeIndex;
|
||||
continue;
|
||||
}
|
||||
source += '\\[';
|
||||
continue;
|
||||
}
|
||||
source += ch.replace(/[\\^$+?.()|{}]/g, '\\$&');
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
const compiledGlobCache = new Map<string, RegExp | null>();
|
||||
|
||||
function matchesGlobPattern(model: string, pattern: string): boolean {
|
||||
let re = compiledGlobCache.get(pattern);
|
||||
if (re === undefined) {
|
||||
try {
|
||||
re = new RegExp(`^${globToRegexSource(pattern)}$`);
|
||||
} catch {
|
||||
re = null;
|
||||
}
|
||||
compiledGlobCache.set(pattern, re);
|
||||
}
|
||||
return re ? re.test(model) : false;
|
||||
}
|
||||
|
||||
const matchCache = new Map<string, boolean>();
|
||||
const MATCH_CACHE_LIMIT = 4000;
|
||||
|
||||
export function matchesModelPattern(model: string, pattern: string): boolean {
|
||||
const normalized = (pattern || '').trim();
|
||||
if (!normalized) return false;
|
||||
if (normalized === model) return true;
|
||||
|
||||
const cacheKey = `${model}\0${normalized}`;
|
||||
const cached = matchCache.get(cacheKey);
|
||||
if (cached !== undefined) return cached;
|
||||
|
||||
let result: boolean;
|
||||
if (isRegexModelPattern(normalized)) {
|
||||
const parsed = parseRegexModelPattern(normalized);
|
||||
result = !!parsed.regex && parsed.regex.test(model);
|
||||
} else {
|
||||
result = matchesGlobPattern(model, normalized);
|
||||
}
|
||||
|
||||
if (matchCache.size >= MATCH_CACHE_LIMIT) matchCache.clear();
|
||||
matchCache.set(cacheKey, result);
|
||||
return result;
|
||||
return matchesTokenRouteModelPattern(model, pattern);
|
||||
}
|
||||
|
||||
export function getModelPatternError(modelPattern: string): string | null {
|
||||
|
||||
Reference in New Issue
Block a user