diff --git a/web/src/components/table/LogsTable.js b/web/src/components/table/LogsTable.js index 88992452..ad28992e 100644 --- a/web/src/components/table/LogsTable.js +++ b/web/src/components/table/LogsTable.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { API, @@ -19,7 +19,8 @@ import { renderNumber, renderQuota, stringToColor, - getLogOther + getLogOther, + renderModelTag } from '../../helpers'; import { @@ -39,18 +40,14 @@ import { Typography, Divider, Input, - DatePicker, + DatePicker } from '@douyinfe/semi-ui'; import { ITEMS_PER_PAGE } from '../../constants'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; import { - IconRefresh, IconSetting, - IconEyeOpened, IconSearch, - IconCoinMoneyStroked, - IconPulse, - IconTypograph, + IconForward } from '@douyinfe/semi-icons'; const { Text } = Typography; @@ -202,19 +199,12 @@ const LogsTable = () => { other?.upstream_model_name && other?.upstream_model_name !== ''; if (!modelMapped) { - return ( - { - copyText(event, record.model_name).then((r) => { }); - }} - > - {' '} - {record.model_name}{' '} - - ); + return renderModelTag(record.model_name, { + color: stringToColor(record.model_name), + onClick: (event) => { + copyText(event, record.model_name).then((r) => { }); + } + }); } else { return ( <> @@ -223,48 +213,35 @@ const LogsTable = () => { content={
- { - copyText(event, record.model_name).then((r) => { }); - }} - > - {t('请求并计费模型')} {record.model_name}{' '} - - { - copyText(event, other.upstream_model_name).then( - (r) => { }, - ); - }} - > - {t('实际模型')} {other.upstream_model_name}{' '} - +
+ {t('请求并计费模型')}: + {renderModelTag(record.model_name, { + color: stringToColor(record.model_name), + onClick: (event) => { + copyText(event, record.model_name).then((r) => { }); + } + })} +
+
+ {t('实际模型')}: + {renderModelTag(other.upstream_model_name, { + color: stringToColor(other.upstream_model_name), + onClick: (event) => { + copyText(event, other.upstream_model_name).then((r) => { }); + } + })} +
} > - { + {renderModelTag(record.model_name, { + color: stringToColor(record.model_name), + onClick: (event) => { copyText(event, record.model_name).then((r) => { }); - }} - suffixIcon={ - - } - > - {' '} - {record.model_name}{' '} - + }, + suffixIcon: + })} diff --git a/web/src/components/table/ModelPricing.js b/web/src/components/table/ModelPricing.js index d3d4459d..a7955c2c 100644 --- a/web/src/components/table/ModelPricing.js +++ b/web/src/components/table/ModelPricing.js @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useRef, useMemo, useState } from 'react'; -import { API, copy, showError, showInfo, showSuccess } from '../../helpers/index.js'; +import { API, copy, showError, showInfo, showSuccess, getModelCategories } from '../../helpers/index.js'; import { useTranslation } from 'react-i18next'; import { @@ -28,7 +28,6 @@ import { } from '@douyinfe/semi-icons'; import { UserContext } from '../../context/User/index.js'; import { AlertCircle } from 'lucide-react'; -import { MODEL_CATEGORIES } from '../../constants/index.js'; const ModelPricing = () => { const { t } = useTranslation(); @@ -321,7 +320,7 @@ const ModelPricing = () => { refresh().then(); }, []); - const modelCategories = MODEL_CATEGORIES(t); + const modelCategories = getModelCategories(t); const renderArrow = (items, pos, handleArrowClick) => { const style = { diff --git a/web/src/constants/index.js b/web/src/constants/index.js index 9538a7dc..f92e2b19 100644 --- a/web/src/constants/index.js +++ b/web/src/constants/index.js @@ -2,5 +2,4 @@ export * from './channel.constants'; export * from './user.constants'; export * from './toast.constants'; export * from './common.constant'; -export * from './model.constants'; export * from './playground.constants'; diff --git a/web/src/constants/model.constants.js b/web/src/constants/model.constants.js deleted file mode 100644 index f9002cea..00000000 --- a/web/src/constants/model.constants.js +++ /dev/null @@ -1,145 +0,0 @@ -import { - OpenAI, - Claude, - Gemini, - Moonshot, - Zhipu, - Qwen, - DeepSeek, - Minimax, - Wenxin, - Spark, - Midjourney, - Hunyuan, - Cohere, - Cloudflare, - Ai360, - Yi, - Jina, - Mistral, - XAI, - Ollama, - Doubao, -} from '@lobehub/icons'; - -export const MODEL_CATEGORIES = (t) => ({ - all: { - label: t('全部模型'), - icon: null, - filter: () => true - }, - openai: { - label: 'OpenAI', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('gpt') || - model.model_name.toLowerCase().includes('dall-e') || - model.model_name.toLowerCase().includes('whisper') || - model.model_name.toLowerCase().includes('tts') || - model.model_name.toLowerCase().includes('text-') || - model.model_name.toLowerCase().includes('babbage') || - model.model_name.toLowerCase().includes('davinci') || - model.model_name.toLowerCase().includes('curie') || - model.model_name.toLowerCase().includes('ada') - }, - anthropic: { - label: 'Anthropic', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('claude') - }, - gemini: { - label: 'Gemini', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('gemini') - }, - moonshot: { - label: 'Moonshot', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('moonshot') - }, - zhipu: { - label: t('智谱'), - icon: , - filter: (model) => model.model_name.toLowerCase().includes('chatglm') || - model.model_name.toLowerCase().includes('glm-') - }, - qwen: { - label: t('通义千问'), - icon: , - filter: (model) => model.model_name.toLowerCase().includes('qwen') - }, - deepseek: { - label: 'DeepSeek', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('deepseek') - }, - minimax: { - label: 'MiniMax', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('abab') - }, - baidu: { - label: t('文心一言'), - icon: , - filter: (model) => model.model_name.toLowerCase().includes('ernie') - }, - xunfei: { - label: t('讯飞星火'), - icon: , - filter: (model) => model.model_name.toLowerCase().includes('spark') - }, - midjourney: { - label: 'Midjourney', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('mj_') - }, - tencent: { - label: t('腾讯混元'), - icon: , - filter: (model) => model.model_name.toLowerCase().includes('hunyuan') - }, - cohere: { - label: 'Cohere', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('command') - }, - cloudflare: { - label: 'Cloudflare', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('@cf/') - }, - ai360: { - label: t('360智脑'), - icon: , - filter: (model) => model.model_name.toLowerCase().includes('360') - }, - yi: { - label: t('零一万物'), - icon: , - filter: (model) => model.model_name.toLowerCase().includes('yi') - }, - jina: { - label: 'Jina', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('jina') - }, - mistral: { - label: 'Mistral AI', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('mistral') - }, - xai: { - label: 'xAI', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('grok') - }, - llama: { - label: 'Llama', - icon: , - filter: (model) => model.model_name.toLowerCase().includes('llama') - }, - doubao: { - label: t('豆包'), - icon: , - filter: (model) => model.model_name.toLowerCase().includes('doubao') - } -}); \ No newline at end of file diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index c8302feb..72374ef6 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -2,6 +2,328 @@ import i18next from 'i18next'; import { Modal, Tag, Typography } from '@douyinfe/semi-ui'; import { copy, isMobile, showSuccess } from './utils'; import { visit } from 'unist-util-visit'; +import { + OpenAI, + Claude, + Gemini, + Moonshot, + Zhipu, + Qwen, + DeepSeek, + Minimax, + Wenxin, + Spark, + Midjourney, + Hunyuan, + Cohere, + Cloudflare, + Ai360, + Yi, + Jina, + Mistral, + XAI, + Ollama, + Doubao, +} from '@lobehub/icons'; + +// 获取模型分类 +export const getModelCategories = (() => { + let categoriesCache = null; + let lastLocale = null; + + return (t) => { + const currentLocale = i18next.language; + if (categoriesCache && lastLocale === currentLocale) { + return categoriesCache; + } + + categoriesCache = { + all: { + label: t('全部模型'), + icon: null, + filter: () => true + }, + openai: { + label: 'OpenAI', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('gpt') || + model.model_name.toLowerCase().includes('dall-e') || + model.model_name.toLowerCase().includes('whisper') || + model.model_name.toLowerCase().includes('tts') || + model.model_name.toLowerCase().includes('text-') || + model.model_name.toLowerCase().includes('babbage') || + model.model_name.toLowerCase().includes('davinci') || + model.model_name.toLowerCase().includes('curie') || + model.model_name.toLowerCase().includes('ada') + }, + anthropic: { + label: 'Anthropic', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('claude') + }, + gemini: { + label: 'Gemini', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('gemini') + }, + moonshot: { + label: 'Moonshot', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('moonshot') + }, + zhipu: { + label: t('智谱'), + icon: , + filter: (model) => model.model_name.toLowerCase().includes('chatglm') || + model.model_name.toLowerCase().includes('glm-') + }, + qwen: { + label: t('通义千问'), + icon: , + filter: (model) => model.model_name.toLowerCase().includes('qwen') + }, + deepseek: { + label: 'DeepSeek', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('deepseek') + }, + minimax: { + label: 'MiniMax', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('abab') + }, + baidu: { + label: t('文心一言'), + icon: , + filter: (model) => model.model_name.toLowerCase().includes('ernie') + }, + xunfei: { + label: t('讯飞星火'), + icon: , + filter: (model) => model.model_name.toLowerCase().includes('spark') + }, + midjourney: { + label: 'Midjourney', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('mj_') + }, + tencent: { + label: t('腾讯混元'), + icon: , + filter: (model) => model.model_name.toLowerCase().includes('hunyuan') + }, + cohere: { + label: 'Cohere', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('command') + }, + cloudflare: { + label: 'Cloudflare', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('@cf/') + }, + ai360: { + label: t('360智脑'), + icon: , + filter: (model) => model.model_name.toLowerCase().includes('360') + }, + yi: { + label: t('零一万物'), + icon: , + filter: (model) => model.model_name.toLowerCase().includes('yi') + }, + jina: { + label: 'Jina', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('jina') + }, + mistral: { + label: 'Mistral AI', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('mistral') + }, + xai: { + label: 'xAI', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('grok') + }, + llama: { + label: 'Llama', + icon: , + filter: (model) => model.model_name.toLowerCase().includes('llama') + }, + doubao: { + label: t('豆包'), + icon: , + filter: (model) => model.model_name.toLowerCase().includes('doubao') + } + }; + + lastLocale = currentLocale; + return categoriesCache; + }; +})(); + +// 颜色列表 +const colors = [ + 'amber', + 'blue', + 'cyan', + 'green', + 'grey', + 'indigo', + 'light-blue', + 'lime', + 'orange', + 'pink', + 'purple', + 'red', + 'teal', + 'violet', + 'yellow', +]; + +// 基础10色色板 (N ≤ 10) +const baseColors = [ + '#1664FF', // 主色 + '#1AC6FF', + '#FF8A00', + '#3CC780', + '#7442D4', + '#FFC400', + '#304D77', + '#B48DEB', + '#009488', + '#FF7DDA', +]; + +// 扩展20色色板 (10 < N ≤ 20) +const extendedColors = [ + '#1664FF', + '#B2CFFF', + '#1AC6FF', + '#94EFFF', + '#FF8A00', + '#FFCE7A', + '#3CC780', + '#B9EDCD', + '#7442D4', + '#DDC5FA', + '#FFC400', + '#FAE878', + '#304D77', + '#8B959E', + '#B48DEB', + '#EFE3FF', + '#009488', + '#59BAA8', + '#FF7DDA', + '#FFCFEE', +]; + +// 模型颜色映射 +export const modelColorMap = { + 'dall-e': 'rgb(147,112,219)', // 深紫色 + // 'dall-e-2': 'rgb(147,112,219)', // 介于紫色和蓝色之间的色调 + 'dall-e-3': 'rgb(153,50,204)', // 介于紫罗兰和洋红之间的色调 + 'gpt-3.5-turbo': 'rgb(184,227,167)', // 浅绿色 + // 'gpt-3.5-turbo-0301': 'rgb(131,220,131)', // 亮绿色 + 'gpt-3.5-turbo-0613': 'rgb(60,179,113)', // 海洋绿 + 'gpt-3.5-turbo-1106': 'rgb(32,178,170)', // 浅海洋绿 + 'gpt-3.5-turbo-16k': 'rgb(149,252,206)', // 淡橙色 + 'gpt-3.5-turbo-16k-0613': 'rgb(119,255,214)', // 淡桃 + 'gpt-3.5-turbo-instruct': 'rgb(175,238,238)', // 粉蓝色 + 'gpt-4': 'rgb(135,206,235)', // 天蓝色 + // 'gpt-4-0314': 'rgb(70,130,180)', // 钢蓝色 + 'gpt-4-0613': 'rgb(100,149,237)', // 矢车菊蓝 + 'gpt-4-1106-preview': 'rgb(30,144,255)', // 道奇蓝 + 'gpt-4-0125-preview': 'rgb(2,177,236)', // 深天蓝 + 'gpt-4-turbo-preview': 'rgb(2,177,255)', // 深天蓝 + 'gpt-4-32k': 'rgb(104,111,238)', // 中紫色 + // 'gpt-4-32k-0314': 'rgb(90,105,205)', // 暗灰蓝色 + 'gpt-4-32k-0613': 'rgb(61,71,139)', // 暗蓝灰色 + 'gpt-4-all': 'rgb(65,105,225)', // 皇家蓝 + 'gpt-4-gizmo-*': 'rgb(0,0,255)', // 纯蓝色 + 'gpt-4-vision-preview': 'rgb(25,25,112)', // 午夜蓝 + 'text-ada-001': 'rgb(255,192,203)', // 粉红色 + 'text-babbage-001': 'rgb(255,160,122)', // 浅珊瑚色 + 'text-curie-001': 'rgb(219,112,147)', // 苍紫罗兰色 + // 'text-davinci-002': 'rgb(199,21,133)', // 中紫罗兰红色 + 'text-davinci-003': 'rgb(219,112,147)', // 苍紫罗兰色(与Curie相同,表示同一个系列) + 'text-davinci-edit-001': 'rgb(255,105,180)', // 热粉色 + 'text-embedding-ada-002': 'rgb(255,182,193)', // 浅粉红 + 'text-embedding-v1': 'rgb(255,174,185)', // 浅粉红色(略有区别) + 'text-moderation-latest': 'rgb(255,130,171)', // 强粉色 + 'text-moderation-stable': 'rgb(255,160,122)', // 浅珊瑚色(与Babbage相同,表示同一类功能) + 'tts-1': 'rgb(255,140,0)', // 深橙色 + 'tts-1-1106': 'rgb(255,165,0)', // 橙色 + 'tts-1-hd': 'rgb(255,215,0)', // 金色 + 'tts-1-hd-1106': 'rgb(255,223,0)', // 金黄色(略有区别) + 'whisper-1': 'rgb(245,245,220)', // 米色 + 'claude-3-opus-20240229': 'rgb(255,132,31)', // 橙红色 + 'claude-3-sonnet-20240229': 'rgb(253,135,93)', // 橙色 + 'claude-3-haiku-20240307': 'rgb(255,175,146)', // 浅橙色 + 'claude-2.1': 'rgb(255,209,190)', // 浅橙色(略有区别) +}; + +export function modelToColor(modelName) { + // 1. 如果模型在预定义的 modelColorMap 中,使用预定义颜色 + if (modelColorMap[modelName]) { + return modelColorMap[modelName]; + } + + // 2. 生成一个稳定的数字作为索引 + let hash = 0; + for (let i = 0; i < modelName.length; i++) { + hash = (hash << 5) - hash + modelName.charCodeAt(i); + hash = hash & hash; // Convert to 32-bit integer + } + hash = Math.abs(hash); + + // 3. 根据模型名称长度选择不同的色板 + const colorPalette = modelName.length > 10 ? extendedColors : baseColors; + + // 4. 使用hash值选择颜色 + const index = hash % colorPalette.length; + return colorPalette[index]; +} + +export function stringToColor(str) { + let sum = 0; + for (let i = 0; i < str.length; i++) { + sum += str.charCodeAt(i); + } + let i = sum % colors.length; + return colors[i]; +} + +// 渲染带有模型图标的标签 +export function renderModelTag(modelName, options = {}) { + const { color, size = 'large', shape = 'circle', onClick, suffixIcon } = options; + + const categories = getModelCategories(i18next.t); + let icon = null; + + for (const [key, category] of Object.entries(categories)) { + if (key !== 'all' && category.filter({ model_name: modelName })) { + icon = category.icon; + break; + } + } + + return ( + + {modelName} + + ); +} export function renderText(text, limit) { if (text.length > limit) { @@ -800,137 +1122,6 @@ export function renderQuotaWithPrompt(quota, digits) { return ''; } -const colors = [ - 'amber', - 'blue', - 'cyan', - 'green', - 'grey', - 'indigo', - 'light-blue', - 'lime', - 'orange', - 'pink', - 'purple', - 'red', - 'teal', - 'violet', - 'yellow', -]; - -// 基础10色色板 (N ≤ 10) -const baseColors = [ - '#1664FF', // 主色 - '#1AC6FF', - '#FF8A00', - '#3CC780', - '#7442D4', - '#FFC400', - '#304D77', - '#B48DEB', - '#009488', - '#FF7DDA', -]; - -// 扩展20色色板 (10 < N ≤ 20) -const extendedColors = [ - '#1664FF', - '#B2CFFF', - '#1AC6FF', - '#94EFFF', - '#FF8A00', - '#FFCE7A', - '#3CC780', - '#B9EDCD', - '#7442D4', - '#DDC5FA', - '#FFC400', - '#FAE878', - '#304D77', - '#8B959E', - '#B48DEB', - '#EFE3FF', - '#009488', - '#59BAA8', - '#FF7DDA', - '#FFCFEE', -]; - -export const modelColorMap = { - 'dall-e': 'rgb(147,112,219)', // 深紫色 - // 'dall-e-2': 'rgb(147,112,219)', // 介于紫色和蓝色之间的色调 - 'dall-e-3': 'rgb(153,50,204)', // 介于紫罗兰和洋红之间的色调 - 'gpt-3.5-turbo': 'rgb(184,227,167)', // 浅绿色 - // 'gpt-3.5-turbo-0301': 'rgb(131,220,131)', // 亮绿色 - 'gpt-3.5-turbo-0613': 'rgb(60,179,113)', // 海洋绿 - 'gpt-3.5-turbo-1106': 'rgb(32,178,170)', // 浅海洋绿 - 'gpt-3.5-turbo-16k': 'rgb(149,252,206)', // 淡橙色 - 'gpt-3.5-turbo-16k-0613': 'rgb(119,255,214)', // 淡桃 - 'gpt-3.5-turbo-instruct': 'rgb(175,238,238)', // 粉蓝色 - 'gpt-4': 'rgb(135,206,235)', // 天蓝色 - // 'gpt-4-0314': 'rgb(70,130,180)', // 钢蓝色 - 'gpt-4-0613': 'rgb(100,149,237)', // 矢车菊蓝 - 'gpt-4-1106-preview': 'rgb(30,144,255)', // 道奇蓝 - 'gpt-4-0125-preview': 'rgb(2,177,236)', // 深天蓝 - 'gpt-4-turbo-preview': 'rgb(2,177,255)', // 深天蓝 - 'gpt-4-32k': 'rgb(104,111,238)', // 中紫色 - // 'gpt-4-32k-0314': 'rgb(90,105,205)', // 暗灰蓝色 - 'gpt-4-32k-0613': 'rgb(61,71,139)', // 暗蓝灰色 - 'gpt-4-all': 'rgb(65,105,225)', // 皇家蓝 - 'gpt-4-gizmo-*': 'rgb(0,0,255)', // 纯蓝色 - 'gpt-4-vision-preview': 'rgb(25,25,112)', // 午夜蓝 - 'text-ada-001': 'rgb(255,192,203)', // 粉红色 - 'text-babbage-001': 'rgb(255,160,122)', // 浅珊瑚色 - 'text-curie-001': 'rgb(219,112,147)', // 苍紫罗兰色 - // 'text-davinci-002': 'rgb(199,21,133)', // 中紫罗兰红色 - 'text-davinci-003': 'rgb(219,112,147)', // 苍紫罗兰色(与Curie相同,表示同一个系列) - 'text-davinci-edit-001': 'rgb(255,105,180)', // 热粉色 - 'text-embedding-ada-002': 'rgb(255,182,193)', // 浅粉红 - 'text-embedding-v1': 'rgb(255,174,185)', // 浅粉红色(略有区别) - 'text-moderation-latest': 'rgb(255,130,171)', // 强粉色 - 'text-moderation-stable': 'rgb(255,160,122)', // 浅珊瑚色(与Babbage相同,表示同一类功能) - 'tts-1': 'rgb(255,140,0)', // 深橙色 - 'tts-1-1106': 'rgb(255,165,0)', // 橙色 - 'tts-1-hd': 'rgb(255,215,0)', // 金色 - 'tts-1-hd-1106': 'rgb(255,223,0)', // 金黄色(略有区别) - 'whisper-1': 'rgb(245,245,220)', // 米色 - 'claude-3-opus-20240229': 'rgb(255,132,31)', // 橙红色 - 'claude-3-sonnet-20240229': 'rgb(253,135,93)', // 橙色 - 'claude-3-haiku-20240307': 'rgb(255,175,146)', // 浅橙色 - 'claude-2.1': 'rgb(255,209,190)', // 浅橙色(略有区别) -}; - -export function modelToColor(modelName) { - // 1. 如果模型在预定义的 modelColorMap 中,使用预定义颜色 - if (modelColorMap[modelName]) { - return modelColorMap[modelName]; - } - - // 2. 生成一个稳定的数字作为索引 - let hash = 0; - for (let i = 0; i < modelName.length; i++) { - hash = (hash << 5) - hash + modelName.charCodeAt(i); - hash = hash & hash; // Convert to 32-bit integer - } - hash = Math.abs(hash); - - // 3. 根据模型名称长度选择不同的色板 - const colorPalette = modelName.length > 10 ? extendedColors : baseColors; - - // 4. 使用hash值选择颜色 - const index = hash % colorPalette.length; - return colorPalette[index]; -} - -export function stringToColor(str) { - let sum = 0; - for (let i = 0; i < str.length; i++) { - sum += str.charCodeAt(i); - } - let i = sum % colors.length; - return colors[i]; -} - export function renderClaudeModelPrice( inputTokens, completionTokens,