mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-17 04:32:31 +03:00
* feat(i18n): modality bridge page strings (en + synced locales) * feat(dashboard): ModalityBridgeVisionTab + stats row + test button * feat(dashboard): Modality Bridge settings page with vision/audio/video tabs + sidebar entry * feat(dashboard): relocate vision bridge card to link + media-providers shortcuts * docs(guardrails): document Modality Bridge dashboard * chore: preserve upstream formatting after base merge * fix(modality-bridge): satisfy i18n quality gates * fix(i18n): preserve canonical Chinese glossary terms * fix(modality-bridge): clear dashboard quality regressions * fix(settings): use catalog-only modality labels * fix(i18n): isolate modality bridge availability copy * chore(i18n): prepare conflict-free Modality Bridge base sync * docs(modality-bridge): align migration note with dead-code decision * fix(i18n): sync capability filter locales after release merge * fix(i18n): restore canonical Traditional Chinese glossary --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
315 lines
9.1 KiB
TypeScript
315 lines
9.1 KiB
TypeScript
export * from "./sidebarVisibility/types";
|
|
export { COMPRESSION_CONTEXT_GROUP, SIDEBAR_SECTIONS } from "./sidebarVisibility/sections";
|
|
|
|
import { HIDEABLE_SIDEBAR_ITEM_IDS } from "./sidebarVisibility/types";
|
|
import type {
|
|
HideableSidebarItemId,
|
|
SidebarItemId,
|
|
SidebarSectionId,
|
|
SidebarItemDefinition,
|
|
SidebarSectionChild,
|
|
SidebarSectionDefinition,
|
|
SidebarPresetDefinition,
|
|
} from "./sidebarVisibility/types";
|
|
|
|
export const SIDEBAR_ICON_ACCENTS: Partial<Record<SidebarItemId, string>> = {
|
|
home: "#60A5FA",
|
|
"api-manager": "#F59E0B",
|
|
endpoints: "#38BDF8",
|
|
providers: "#818CF8",
|
|
combos: "#A855F7",
|
|
quota: "#F472B6",
|
|
"context-caveman": "#F97316",
|
|
"context-rtk": "#2DD4BF",
|
|
"context-combos": "#C084FC",
|
|
"cli-code": "#FACC15",
|
|
"cli-agents": "#93C5FD",
|
|
"cloud-agents": "#7DD3FC",
|
|
"api-endpoints": "#14B8A6",
|
|
webhooks: "#EC4899",
|
|
proxy: "#A3E635",
|
|
"mitm-proxy": "#FB7185",
|
|
"1proxy": "#22D3EE",
|
|
analytics: "#06B6D4",
|
|
"analytics-combo-health": "#34D399",
|
|
"analytics-utilization": "#FBBF24",
|
|
costs: "#FB923C",
|
|
cache: "#84CC16",
|
|
"analytics-compression": "#F97316",
|
|
"analytics-search": "#38BDF8",
|
|
"analytics-evals": "#A78BFA",
|
|
logs: "#CBD5E1",
|
|
"logs-proxy": "#A3E635",
|
|
"logs-console": "#FACC15",
|
|
"logs-timeline": "#F472B6",
|
|
"logs-activity": "#60A5FA",
|
|
health: "#EF4444",
|
|
runtime: "#F59E0B",
|
|
"resilience-connections": "#22C55E",
|
|
"costs-pricing": "#FB923C",
|
|
"costs-budget": "#22C55E",
|
|
"costs-quota-share": "#06B6D4",
|
|
audit: "#F43F5E",
|
|
"audit-mcp": "#818CF8",
|
|
"audit-a2a": "#A855F7",
|
|
translator: "#3B82F6",
|
|
playground: "#EAB308",
|
|
"search-tools": "#0891B2",
|
|
memory: "#10B981",
|
|
skills: "#F43F5E",
|
|
"agent-skills": "#D946EF",
|
|
mcp: "#8B5CF6",
|
|
a2a: "#06B6D4",
|
|
leaderboard: "#FACC15",
|
|
profile: "#60A5FA",
|
|
tokens: "#A3E635",
|
|
media: "#D946EF",
|
|
batch: "#14B8A6",
|
|
"batch-files": "#38BDF8",
|
|
"settings-general": "#64748B",
|
|
"settings-appearance": "#D946EF",
|
|
"settings-ai": "#A78BFA",
|
|
"settings-modality-bridge": "#8B5CF6",
|
|
"settings-routing": "#06B6D4",
|
|
"settings-resilience": "#22C55E",
|
|
"settings-advanced": "#F97316",
|
|
"settings-security": "#EF4444",
|
|
"settings-feature-flags": "#FACC15",
|
|
"settings-cache": "#84CC16",
|
|
"settings-sidebar": "#38BDF8",
|
|
docs: "#2563EB",
|
|
issues: "#DC2626",
|
|
changelog: "#F59E0B",
|
|
};
|
|
|
|
export const SIDEBAR_SUBITEM_ICON_ACCENTS: Record<string, string> = {};
|
|
|
|
function getDeterministicIconAccent(id: string): string {
|
|
let hash = 0;
|
|
for (let index = 0; index < id.length; index += 1) {
|
|
hash = (hash * 31 + id.charCodeAt(index)) >>> 0;
|
|
}
|
|
const hue = hash % 360;
|
|
const saturation = 72;
|
|
const lightness = 56;
|
|
const chroma = (1 - Math.abs((2 * lightness) / 100 - 1)) * (saturation / 100);
|
|
const huePrime = hue / 60;
|
|
const x = chroma * (1 - Math.abs((huePrime % 2) - 1));
|
|
const match = lightness / 100 - chroma / 2;
|
|
const [red, green, blue] =
|
|
huePrime < 1
|
|
? [chroma, x, 0]
|
|
: huePrime < 2
|
|
? [x, chroma, 0]
|
|
: huePrime < 3
|
|
? [0, chroma, x]
|
|
: huePrime < 4
|
|
? [0, x, chroma]
|
|
: huePrime < 5
|
|
? [x, 0, chroma]
|
|
: [chroma, 0, x];
|
|
|
|
return [red, green, blue]
|
|
.map((channel) =>
|
|
Math.round((channel + match) * 255)
|
|
.toString(16)
|
|
.padStart(2, "0")
|
|
.toUpperCase()
|
|
)
|
|
.join("")
|
|
.replace(/^/, "#");
|
|
}
|
|
|
|
export function getSidebarIconAccent(id: string): string {
|
|
return (
|
|
SIDEBAR_ICON_ACCENTS[id as SidebarItemId] ||
|
|
SIDEBAR_SUBITEM_ICON_ACCENTS[id] ||
|
|
getDeterministicIconAccent(id)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Decide whether a sidebar item should be shown given a resolved feature-flag
|
|
* map. Items without `featureFlagKey` are always visible. Fails OPEN when the
|
|
* flag isn't present in the map (e.g. `/api/settings` hasn't returned yet, or
|
|
* an older server response predates the flag) — a missing entry must never
|
|
* hide an unrelated item.
|
|
*/
|
|
export function isSidebarItemVisibleForFlags(
|
|
item: Pick<SidebarItemDefinition, "featureFlagKey">,
|
|
flags: Record<string, boolean>
|
|
): boolean {
|
|
if (!item.featureFlagKey) return true;
|
|
return flags[item.featureFlagKey] !== false;
|
|
}
|
|
|
|
export function getSectionItems(
|
|
section: SidebarSectionDefinition | { children: readonly SidebarSectionChild[] }
|
|
): readonly SidebarItemDefinition[] {
|
|
return section.children.flatMap((child) =>
|
|
"type" in child && child.type === "group" ? child.items : [child as SidebarItemDefinition]
|
|
);
|
|
}
|
|
|
|
// ─── Ordering & preset setting keys ──────────────────────────────────────────
|
|
|
|
export const HIDDEN_SIDEBAR_ITEMS_SETTING_KEY = "hiddenSidebarItems";
|
|
export const SIDEBAR_SECTION_ORDER_KEY = "sidebarSectionOrder";
|
|
export const SIDEBAR_ITEM_ORDER_KEY = "sidebarItemOrder";
|
|
export const SIDEBAR_PRESET_KEY = "sidebarActivePreset";
|
|
export const SIDEBAR_SETTINGS_UPDATED_EVENT = "omniroute:settings-updated";
|
|
|
|
const MINIMAL_SHOWN: ReadonlySet<HideableSidebarItemId> = new Set([
|
|
"home",
|
|
"endpoints",
|
|
"api-manager",
|
|
"providers",
|
|
"combos",
|
|
"analytics",
|
|
"costs",
|
|
"logs",
|
|
"health",
|
|
"settings-general",
|
|
"settings-sidebar",
|
|
"docs",
|
|
"changelog",
|
|
]);
|
|
|
|
const DEVELOPER_SHOWN: ReadonlySet<HideableSidebarItemId> = new Set([
|
|
"home",
|
|
"endpoints",
|
|
"api-manager",
|
|
"providers",
|
|
"combos",
|
|
"quota",
|
|
"context-caveman",
|
|
"context-rtk",
|
|
"context-combos",
|
|
"cli-code",
|
|
"cli-agents",
|
|
"acp-agents",
|
|
"api-endpoints",
|
|
"analytics",
|
|
"analytics-combo-health",
|
|
"costs",
|
|
"cache",
|
|
"logs",
|
|
"health",
|
|
"runtime",
|
|
"resilience-connections",
|
|
"translator",
|
|
"playground",
|
|
"memory",
|
|
"skills",
|
|
"mcp",
|
|
"a2a",
|
|
"settings-general",
|
|
"settings-modality-bridge",
|
|
"settings-routing",
|
|
"settings-resilience",
|
|
"settings-sidebar",
|
|
"docs",
|
|
"issues",
|
|
"changelog",
|
|
]);
|
|
|
|
const ADMIN_SHOWN: ReadonlySet<HideableSidebarItemId> = new Set([
|
|
"home",
|
|
"endpoints",
|
|
"api-manager",
|
|
"providers",
|
|
"combos",
|
|
"quota",
|
|
"analytics",
|
|
"analytics-combo-health",
|
|
"analytics-utilization",
|
|
"costs",
|
|
"costs-pricing",
|
|
"costs-budget",
|
|
"costs-quota-share",
|
|
"cache",
|
|
"logs",
|
|
"activity",
|
|
"health",
|
|
"runtime",
|
|
"audit",
|
|
"audit-mcp",
|
|
"audit-a2a",
|
|
"settings-general",
|
|
"settings-modality-bridge",
|
|
"settings-routing",
|
|
"settings-resilience",
|
|
"settings-security",
|
|
"settings-access-tokens",
|
|
"settings-feature-flags",
|
|
"settings-sidebar",
|
|
"docs",
|
|
"changelog",
|
|
]);
|
|
|
|
function buildHiddenList(shown: ReadonlySet<HideableSidebarItemId>): HideableSidebarItemId[] {
|
|
return HIDEABLE_SIDEBAR_ITEM_IDS.filter((id) => !shown.has(id));
|
|
}
|
|
|
|
export const SIDEBAR_PRESETS: readonly SidebarPresetDefinition[] = [
|
|
{ id: "all", icon: "select_all", hiddenItems: [] },
|
|
{ id: "minimal", icon: "minimize", hiddenItems: buildHiddenList(MINIMAL_SHOWN) },
|
|
{ id: "developer", icon: "code", hiddenItems: buildHiddenList(DEVELOPER_SHOWN) },
|
|
{ id: "admin", icon: "admin_panel_settings", hiddenItems: buildHiddenList(ADMIN_SHOWN) },
|
|
];
|
|
|
|
// ─── Ordering utilities ───────────────────────────────────────────────────────
|
|
|
|
export function applySectionOrder(
|
|
sections: readonly SidebarSectionDefinition[],
|
|
order: SidebarSectionId[]
|
|
): SidebarSectionDefinition[] {
|
|
if (order.length === 0) return [...sections];
|
|
const knownIds = new Set(sections.map((s) => s.id));
|
|
const validOrder = order.filter((id) => knownIds.has(id));
|
|
const orderMap = new Map(validOrder.map((id, i) => [id, i]));
|
|
return [...sections].sort((a, b) => {
|
|
const ai = orderMap.get(a.id) ?? validOrder.length + sections.indexOf(a);
|
|
const bi = orderMap.get(b.id) ?? validOrder.length + sections.indexOf(b);
|
|
return ai - bi;
|
|
});
|
|
}
|
|
|
|
export function applyItemOrder(
|
|
children: readonly SidebarSectionChild[],
|
|
order: string[]
|
|
): SidebarSectionChild[] {
|
|
if (order.length === 0) return [...children];
|
|
const getChildId = (c: SidebarSectionChild): string =>
|
|
"type" in c && c.type === "group" ? c.id : (c as SidebarItemDefinition).id;
|
|
const knownIds = new Set(children.map(getChildId));
|
|
const validOrder = order.filter((id) => knownIds.has(id));
|
|
const orderMap = new Map(validOrder.map((id, i) => [id, i]));
|
|
return [...children].sort((a, b) => {
|
|
const aId = getChildId(a);
|
|
const bId = getChildId(b);
|
|
const ai = orderMap.get(aId) ?? validOrder.length + children.indexOf(a);
|
|
const bi = orderMap.get(bId) ?? validOrder.length + children.indexOf(b);
|
|
return ai - bi;
|
|
});
|
|
}
|
|
|
|
// ─── Settings helpers ─────────────────────────────────────────────────────────
|
|
|
|
export function normalizeHiddenSidebarItems(value: unknown): HideableSidebarItemId[] {
|
|
if (!Array.isArray(value)) return [];
|
|
|
|
const hiddenItems = new Set<HideableSidebarItemId>();
|
|
|
|
for (const item of value) {
|
|
if (
|
|
typeof item === "string" &&
|
|
HIDEABLE_SIDEBAR_ITEM_IDS.includes(item as HideableSidebarItemId)
|
|
) {
|
|
hiddenItems.add(item as HideableSidebarItemId);
|
|
}
|
|
}
|
|
|
|
return HIDEABLE_SIDEBAR_ITEM_IDS.filter((item) => hiddenItems.has(item));
|
|
}
|