openvibers/openvibe.shared ai-marks.js
ai-marks.js
| 1 | ; |
| 2 | /** |
| 3 | * openvibe-shared/ai-marks — the AI makers' marks, one place for every site that lists models (Exchange, Report, Chat, |
| 4 | * Bench, Design). The owner, 11-09, on Exchange's home: model-family glyphs we drew ourselves read as "what is it even |
| 5 | * supposed to be?"; the AI comparison plan asks for the makers' recognisable marks, as every leaderboard shows them. |
| 6 | * |
| 7 | * const marks = require('openvibe-shared/ai-marks'); |
| 8 | * marks.family('deepseek/deepseek-chat') → 'DeepSeek' (a model id, a slug, a family or a provider name) |
| 9 | * marks.mark('anthropic', { size: 24 }) → <span class="ov-aim ov-aim--anthropic" …><svg …>…</svg></span> |
| 10 | * marks.mark('someone-new') → the neutral spark (never a letter in a circle, never another maker's mark) |
| 11 | * marks.CSS the few rules (a tile in the theme's tokens), once per page |
| 12 | * |
| 13 | * Marks are monochrome in currentColor (they follow the theme); the maker's name is the label (label: false when the row |
| 14 | * already names it). The drawings come from @lobehub/icons-static-svg (MIT; see ai-marks-data.js and NOTICE in it). |
| 15 | */ |
| 16 | const DATA = require('./ai-marks-data'); |
| 17 | |
| 18 | const NAMES = { |
| 19 | OpenAI: 'OpenAI', Anthropic: 'Anthropic', Google: 'Google', Gemma: 'Gemma', Meta: 'Meta', DeepSeek: 'DeepSeek', Qwen: 'Qwen', |
| 20 | Mistral: 'Mistral AI', Kimi: 'Moonshot AI', GLM: 'Zhipu AI', MiniMax: 'MiniMax', Microsoft: 'Microsoft', xAI: 'xAI', |
| 21 | NVIDIA: 'NVIDIA', Cohere: 'Cohere', Perplexity: 'Perplexity', |
| 22 | }; |
| 23 | // Order matters: the more specific first (gemma before gemini/google; grok before x). |
| 24 | const RULES = [ |
| 25 | [/gemma/i, 'Gemma'], [/gemini|google|palm|bard/i, 'Google'], [/claude|anthropic/i, 'Anthropic'], |
| 26 | [/deepseek/i, 'DeepSeek'], [/qwen|qwq|alibaba|tongyi/i, 'Qwen'], [/kimi|moonshot/i, 'Kimi'], [/glm|zhipu|z\.ai|chatglm/i, 'GLM'], |
| 27 | [/minimax|abab/i, 'MiniMax'], [/llama|meta\b|^meta|facebook/i, 'Meta'], [/mistral|mixtral|codestral|ministral|magistral|pixtral/i, 'Mistral'], |
| 28 | [/\bphi-?\d|microsoft|wizardlm/i, 'Microsoft'], [/grok|x-ai|\bxai\b/i, 'xAI'], [/nvidia|nemotron/i, 'NVIDIA'], |
| 29 | [/cohere|command-?r|\baya\b/i, 'Cohere'], [/perplexity|sonar/i, 'Perplexity'], |
| 30 | [/openai|gpt|\bo[134](-|\b)|chatgpt|davinci|whisper|dall-?e/i, 'OpenAI'], |
| 31 | ]; |
| 32 | const SPARK = '<path d="M12 3l1.9 5.2L19 10l-5.1 1.8L12 17l-1.9-5.2L5 10l5.1-1.8Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>'; |
| 33 | const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); |
| 34 | |
| 35 | /** The maker's family key for a model id, slug, family or provider name; null when none matches. */ |
| 36 | function family(text) { |
| 37 | const t = String(text || '').trim(); |
| 38 | if (!t) return null; |
| 39 | const exact = Object.keys(DATA).find((k) => k.toLowerCase() === t.toLowerCase()); |
| 40 | if (exact) return exact; |
| 41 | for (const [re, key] of RULES) if (re.test(t)) return key; |
| 42 | return null; |
| 43 | } |
| 44 | |
| 45 | /** The mark as HTML: a tile with the maker's drawing (or the neutral spark), labelled with the maker's name. */ |
| 46 | function mark(text, { size = 24, label = true, cls = '' } = {}) { |
| 47 | const key = family(text); |
| 48 | const px = Math.max(12, Math.min(96, Number(size) || 24)); |
| 49 | const name = key ? NAMES[key] : String(text || 'Other'); |
| 50 | const svg = `<svg viewBox="0 0 24 24" width="${Math.round(px * 0.62)}" height="${Math.round(px * 0.62)}" fill="currentColor" fill-rule="evenodd" aria-hidden="true" focusable="false">${key ? DATA[key] : SPARK}</svg>`; |
| 51 | return `<span class="ov-aim ov-aim--${key ? key.toLowerCase() : 'other'}${cls ? ` ${esc(cls)}` : ''}" style="--ov-aim:${px}px"${label ? ` role="img" aria-label="${esc(name)}" title="${esc(name)}"` : ' aria-hidden="true"'}>${svg}</span>`; |
| 52 | } |
| 53 | |
| 54 | const CSS = '.ov-aim{display:inline-grid;place-items:center;flex:none;width:var(--ov-aim,24px);height:var(--ov-aim,24px);border-radius:calc(var(--ov-aim,24px) * .28);' |
| 55 | + 'background:color-mix(in srgb,var(--text-primary,#e8ecf3) 8%,transparent);color:var(--text-primary,#e8ecf3);box-shadow:inset 0 0 0 1px var(--border,rgba(255,255,255,.1))}' |
| 56 | + '.ov-aim svg{display:block}.ov-aim--other{color:var(--accent,#3b82f6)}'; |
| 57 | |
| 58 | module.exports = { family, mark, CSS, FAMILIES: Object.keys(DATA), NAMES }; |
| 59 |