nanobot/webui/src/lib/provider-brand.ts
comadreja f3eb2aa08b feat(transcription): add AssemblyAI as transcription provider
Add AssemblyAI as a third transcription provider option alongside
OpenAI and Groq. AssemblyAI offers better accuracy for certain
audio types (distant voices, noisy environments) and serves as a
reliable fallback when other providers struggle.

Changes:
- Add AssemblyAITranscriptionProvider class in providers/transcription.py
- Add 'assemblyai' option in base channel's transcribe_audio()
- Per-channel configuration via transcriptionProvider in config

Usage:
  Set transcriptionProvider: 'assemblyai' and provide an AssemblyAI
  API key via transcriptionApiKey in the channel config.
2026-06-09 05:33:18 +08:00

198 lines
6.9 KiB
TypeScript

export interface ProviderBrand {
logoUrl: string;
logoUrls: string[];
color: string;
initials: string;
}
function officialFaviconUrl(domain: string): string {
return `https://${domain}/favicon.ico`;
}
function duckDuckGoFaviconUrl(domain: string): string {
return `https://icons.duckduckgo.com/ip3/${encodeURIComponent(domain)}.ico`;
}
function googleFaviconUrl(domain: string): string {
return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=64`;
}
export function faviconUrls(domain: string): string[] {
const faviconDomain = faviconDomainFromValue(domain);
return [
officialFaviconUrl(faviconDomain),
duckDuckGoFaviconUrl(faviconDomain),
googleFaviconUrl(domain),
];
}
function brand(
domain: string,
color: string,
initials: string,
logoOverrides: string[] = [],
): ProviderBrand {
const logoUrls = [...logoOverrides];
faviconUrls(domain).forEach((url) => addUniqueLogoUrl(logoUrls, url));
return {
logoUrl: logoUrls[0],
logoUrls,
color,
initials,
};
}
function addUniqueLogoUrl(urls: string[], url: string | null | undefined): void {
const value = url?.trim();
if (value && !urls.includes(value)) urls.push(value);
}
function domainFromLogoUrl(url: string): string | null {
if (url.startsWith("/")) return null;
try {
const parsed = new URL(url);
if (!/^https?:$/.test(parsed.protocol)) return null;
const host = parsed.hostname.toLowerCase();
if (host === "www.google.com" || host === "google.com") {
return parsed.searchParams.get("domain");
}
if (host === "icons.duckduckgo.com") {
const match = parsed.pathname.match(/^\/ip3\/(.+)\.ico$/);
return match ? decodeURIComponent(match[1]) : null;
}
return host.replace(/^www\./, "");
} catch {
return null;
}
}
function faviconDomainFromValue(value: string): string {
const host = value.split("/")[0]?.trim();
return host || value;
}
export function logoFallbackUrls(logoUrl: string | null | undefined): string[] {
const value = logoUrl?.trim();
if (!value) return [];
if (value.startsWith("/")) return [value];
const urls: string[] = [];
const domain = domainFromLogoUrl(value);
const isFaviconProxy = /^(https?:\/\/)?(www\.google\.com|google\.com|icons\.duckduckgo\.com)\//i.test(value);
if (domain && isFaviconProxy) {
addUniqueLogoUrl(urls, value);
faviconUrls(domain).forEach((url) => addUniqueLogoUrl(urls, url));
return urls;
}
addUniqueLogoUrl(urls, value);
if (domain) faviconUrls(domain).forEach((url) => addUniqueLogoUrl(urls, url));
return urls;
}
export const PROVIDER_BRAND_ALIASES: Record<string, string> = {
brave_search: "brave",
byteplus_coding_plan: "byteplus",
mimo: "xiaomi_mimo",
minimaxAnthropic: "minimax",
minimax_anthropic: "minimax",
openai_codex: "openai",
xiaomi: "xiaomi_mimo",
volcengine_coding_plan: "volcengine",
};
export const PROVIDER_LABEL_ALIASES: Record<string, string> = {
brave_search: "Brave Search",
byteplus_coding_plan: "BytePlus",
minimaxAnthropic: "MiniMax",
minimax_anthropic: "MiniMax",
openai_codex: "OpenAI",
volcengine_coding_plan: "Volcengine",
};
const PROVIDER_BRANDS: Record<string, ProviderBrand> = {
aihubmix: brand("aihubmix.com", "#111827", "AH"),
ant_ling: brand("ant-ling.com", "#7C3AED", "AL"),
anthropic: brand("anthropic.com", "#D97757", "A"),
assemblyai: brand("assemblyai.com", "#111827", "AA"),
atomic_chat: brand("atomic.chat", "#111827", "AC"),
azure_openai: brand("azure.microsoft.com", "#0078D4", "AZ"),
bedrock: brand("aws.amazon.com", "#FF9900", "AWS"),
brave: brand("brave.com", "#FB542B", "B"),
byteplus: brand("byteplus.com", "#325CFF", "BP"),
dashscope: brand("dashscope.aliyun.com", "#FF6A00", "DS"),
deepseek: brand("deepseek.com", "#4D6BFE", "DS"),
duckduckgo: brand("duckduckgo.com", "#DE5833", "DDG"),
exa: brand("exa.ai", "#5B5BF6", "E"),
gemini: brand("gemini.google.com", "#4285F4", "G"),
github_copilot: brand("github.com", "#24292F", "GH"),
groq: brand("groq.com", "#F55036", "GQ"),
huggingface: brand("huggingface.co", "#FF9D00", "HF"),
jina: brand("jina.ai", "#7C3AED", "J"),
kagi: brand("kagi.com", "#FFB319", "K"),
lm_studio: brand("lmstudio.ai", "#111827", "LM"),
longcat: brand("longcatai.org", "#4F8CFF", "LC", [
"https://www.longcatai.org/favicon.svg",
]),
minimax: brand("minimax.io", "#111827", "MM"),
mistral: brand("mistral.ai", "#FA520F", "M"),
moonshot: brand("moonshot.ai", "#111827", "MS"),
novita: brand("novita.ai", "#7C3AED", "N"),
olostep: brand("olostep.com", "#111827", "O"),
nvidia: brand("nvidia.com", "#76B900", "NV"),
ollama: brand("ollama.com", "#111827", "O"),
openai: brand("openai.com", "#111827", "AI"),
openrouter: brand("openrouter.ai", "#111827", "OR"),
ovms: brand("openvino.ai", "#0071C5", "OV"),
qianfan: brand("cloud.baidu.com", "#2932E1", "QF"),
searxng: brand("searxng.org", "#3050FF", "SX"),
siliconflow: brand("siliconflow.cn", "#111827", "SF"),
skywork: brand("skywork.ai", "#5B5BF6", "SW"),
stepfun: brand("stepfun.com", "#2F6BFF", "SF", [
"https://www.stepfun.com/step_favicon.svg",
]),
tavily: brand("tavily.com", "#111827", "T"),
volcengine: brand("volcengine.com", "#1664FF", "VE"),
vllm: brand("vllm.ai", "#2563EB", "VL"),
xiaomi_mimo: brand("mimo.xiaomi.com", "#FF6900", "MI", [
"https://mimo.xiaomi.com/mimo-v2-pro/assets/logo.svg",
]),
zhipu: brand("z.ai", "#155EEF", "Z", [
"https://z-cdn.chatglm.cn/z-ai/static/logo.svg",
"https://www.google.com/s2/favicons?domain=z.ai&sz=64",
]),
};
export function providerBrand(provider: string | null | undefined): ProviderBrand | null {
if (!provider) return null;
const key = PROVIDER_BRAND_ALIASES[provider] ?? provider;
return PROVIDER_BRANDS[key] ?? null;
}
export function providerDisplayLabel(
providers: Array<{ name: string; label: string }>,
value: string | null | undefined,
): string {
if (!value) return "";
return providers.find((provider) => provider.name === value)?.label
?? PROVIDER_LABEL_ALIASES[value]
?? value;
}
export function inferProviderFromModelName(modelName: string | null | undefined): string | null {
const normalized = (modelName ?? "").trim().toLowerCase();
if (!normalized) return null;
const prefix = normalized.split(/[/:]/)[0];
if (providerBrand(prefix)) return prefix;
if (/claude|anthropic/.test(normalized)) return "anthropic";
if (/gpt-|^o\d|chatgpt|openai/.test(normalized)) return "openai";
if (/deepseek/.test(normalized)) return "deepseek";
if (/gemini/.test(normalized)) return "gemini";
if (/qwen|dashscope/.test(normalized)) return "dashscope";
if (/kimi|moonshot/.test(normalized)) return "moonshot";
if (/minimax/.test(normalized)) return "minimax";
if (/mistral|mixtral/.test(normalized)) return "mistral";
if (/skywork|skyclaw/.test(normalized)) return "skywork";
if (/ring-/.test(normalized)) return "ant_ling";
return null;
}