✨ feat(ui): Enhance model tag rendering in logs table with icons
Improve the logs table by implementing brand-specific model icons and better redirection visualization: - Replace standard tags with `renderModelTag` to display appropriate brand icons for each model (OpenAI, Claude, Gemini, etc.) - Fix background colors by explicitly passing color parameters - Restore descriptive text for model redirection in popover - Replace refresh icon with forward icon for better representation of model redirection - Clean up unused imports This change provides a more intuitive visual representation of models and their relationships, making the logs table easier to understand at a glance.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
API,
|
API,
|
||||||
@@ -19,7 +19,8 @@ import {
|
|||||||
renderNumber,
|
renderNumber,
|
||||||
renderQuota,
|
renderQuota,
|
||||||
stringToColor,
|
stringToColor,
|
||||||
getLogOther
|
getLogOther,
|
||||||
|
renderModelTag
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -39,18 +40,14 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Divider,
|
Divider,
|
||||||
Input,
|
Input,
|
||||||
DatePicker,
|
DatePicker
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import { ITEMS_PER_PAGE } from '../../constants';
|
import { ITEMS_PER_PAGE } from '../../constants';
|
||||||
import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
|
import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
|
||||||
import {
|
import {
|
||||||
IconRefresh,
|
|
||||||
IconSetting,
|
IconSetting,
|
||||||
IconEyeOpened,
|
|
||||||
IconSearch,
|
IconSearch,
|
||||||
IconCoinMoneyStroked,
|
IconForward
|
||||||
IconPulse,
|
|
||||||
IconTypograph,
|
|
||||||
} from '@douyinfe/semi-icons';
|
} from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
@@ -202,19 +199,12 @@ const LogsTable = () => {
|
|||||||
other?.upstream_model_name &&
|
other?.upstream_model_name &&
|
||||||
other?.upstream_model_name !== '';
|
other?.upstream_model_name !== '';
|
||||||
if (!modelMapped) {
|
if (!modelMapped) {
|
||||||
return (
|
return renderModelTag(record.model_name, {
|
||||||
<Tag
|
color: stringToColor(record.model_name),
|
||||||
color={stringToColor(record.model_name)}
|
onClick: (event) => {
|
||||||
size='large'
|
copyText(event, record.model_name).then((r) => { });
|
||||||
shape='circle'
|
}
|
||||||
onClick={(event) => {
|
});
|
||||||
copyText(event, record.model_name).then((r) => { });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{' '}
|
|
||||||
{record.model_name}{' '}
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -223,48 +213,35 @@ const LogsTable = () => {
|
|||||||
content={
|
content={
|
||||||
<div style={{ padding: 10 }}>
|
<div style={{ padding: 10 }}>
|
||||||
<Space vertical align={'start'}>
|
<Space vertical align={'start'}>
|
||||||
<Tag
|
<div className="flex items-center">
|
||||||
color={stringToColor(record.model_name)}
|
<Text strong style={{ marginRight: 8 }}>{t('请求并计费模型')}:</Text>
|
||||||
size='large'
|
{renderModelTag(record.model_name, {
|
||||||
shape='circle'
|
color: stringToColor(record.model_name),
|
||||||
onClick={(event) => {
|
onClick: (event) => {
|
||||||
copyText(event, record.model_name).then((r) => { });
|
copyText(event, record.model_name).then((r) => { });
|
||||||
}}
|
}
|
||||||
>
|
})}
|
||||||
{t('请求并计费模型')} {record.model_name}{' '}
|
</div>
|
||||||
</Tag>
|
<div className="flex items-center">
|
||||||
<Tag
|
<Text strong style={{ marginRight: 8 }}>{t('实际模型')}:</Text>
|
||||||
color={stringToColor(other.upstream_model_name)}
|
{renderModelTag(other.upstream_model_name, {
|
||||||
size='large'
|
color: stringToColor(other.upstream_model_name),
|
||||||
shape='circle'
|
onClick: (event) => {
|
||||||
onClick={(event) => {
|
copyText(event, other.upstream_model_name).then((r) => { });
|
||||||
copyText(event, other.upstream_model_name).then(
|
}
|
||||||
(r) => { },
|
})}
|
||||||
);
|
</div>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('实际模型')} {other.upstream_model_name}{' '}
|
|
||||||
</Tag>
|
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Tag
|
{renderModelTag(record.model_name, {
|
||||||
color={stringToColor(record.model_name)}
|
color: stringToColor(record.model_name),
|
||||||
size='large'
|
onClick: (event) => {
|
||||||
shape='circle'
|
|
||||||
onClick={(event) => {
|
|
||||||
copyText(event, record.model_name).then((r) => { });
|
copyText(event, record.model_name).then((r) => { });
|
||||||
}}
|
},
|
||||||
suffixIcon={
|
suffixIcon: <IconForward style={{ width: '0.9em', height: '0.9em', opacity: 0.75 }} />
|
||||||
<IconRefresh
|
})}
|
||||||
style={{ width: '0.8em', height: '0.8em', opacity: 0.6 }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{' '}
|
|
||||||
{record.model_name}{' '}
|
|
||||||
</Tag>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</Space>
|
</Space>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useContext, useEffect, useRef, useMemo, useState } from 'react';
|
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 { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
} from '@douyinfe/semi-icons';
|
} from '@douyinfe/semi-icons';
|
||||||
import { UserContext } from '../../context/User/index.js';
|
import { UserContext } from '../../context/User/index.js';
|
||||||
import { AlertCircle } from 'lucide-react';
|
import { AlertCircle } from 'lucide-react';
|
||||||
import { MODEL_CATEGORIES } from '../../constants/index.js';
|
|
||||||
|
|
||||||
const ModelPricing = () => {
|
const ModelPricing = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -321,7 +320,7 @@ const ModelPricing = () => {
|
|||||||
refresh().then();
|
refresh().then();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const modelCategories = MODEL_CATEGORIES(t);
|
const modelCategories = getModelCategories(t);
|
||||||
|
|
||||||
const renderArrow = (items, pos, handleArrowClick) => {
|
const renderArrow = (items, pos, handleArrowClick) => {
|
||||||
const style = {
|
const style = {
|
||||||
|
|||||||
@@ -2,5 +2,4 @@ export * from './channel.constants';
|
|||||||
export * from './user.constants';
|
export * from './user.constants';
|
||||||
export * from './toast.constants';
|
export * from './toast.constants';
|
||||||
export * from './common.constant';
|
export * from './common.constant';
|
||||||
export * from './model.constants';
|
|
||||||
export * from './playground.constants';
|
export * from './playground.constants';
|
||||||
|
|||||||
@@ -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: <OpenAI />,
|
|
||||||
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: <Claude.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('claude')
|
|
||||||
},
|
|
||||||
gemini: {
|
|
||||||
label: 'Gemini',
|
|
||||||
icon: <Gemini.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('gemini')
|
|
||||||
},
|
|
||||||
moonshot: {
|
|
||||||
label: 'Moonshot',
|
|
||||||
icon: <Moonshot />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('moonshot')
|
|
||||||
},
|
|
||||||
zhipu: {
|
|
||||||
label: t('智谱'),
|
|
||||||
icon: <Zhipu.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('chatglm') ||
|
|
||||||
model.model_name.toLowerCase().includes('glm-')
|
|
||||||
},
|
|
||||||
qwen: {
|
|
||||||
label: t('通义千问'),
|
|
||||||
icon: <Qwen.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('qwen')
|
|
||||||
},
|
|
||||||
deepseek: {
|
|
||||||
label: 'DeepSeek',
|
|
||||||
icon: <DeepSeek.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('deepseek')
|
|
||||||
},
|
|
||||||
minimax: {
|
|
||||||
label: 'MiniMax',
|
|
||||||
icon: <Minimax.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('abab')
|
|
||||||
},
|
|
||||||
baidu: {
|
|
||||||
label: t('文心一言'),
|
|
||||||
icon: <Wenxin.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('ernie')
|
|
||||||
},
|
|
||||||
xunfei: {
|
|
||||||
label: t('讯飞星火'),
|
|
||||||
icon: <Spark.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('spark')
|
|
||||||
},
|
|
||||||
midjourney: {
|
|
||||||
label: 'Midjourney',
|
|
||||||
icon: <Midjourney />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('mj_')
|
|
||||||
},
|
|
||||||
tencent: {
|
|
||||||
label: t('腾讯混元'),
|
|
||||||
icon: <Hunyuan.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('hunyuan')
|
|
||||||
},
|
|
||||||
cohere: {
|
|
||||||
label: 'Cohere',
|
|
||||||
icon: <Cohere.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('command')
|
|
||||||
},
|
|
||||||
cloudflare: {
|
|
||||||
label: 'Cloudflare',
|
|
||||||
icon: <Cloudflare.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('@cf/')
|
|
||||||
},
|
|
||||||
ai360: {
|
|
||||||
label: t('360智脑'),
|
|
||||||
icon: <Ai360.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('360')
|
|
||||||
},
|
|
||||||
yi: {
|
|
||||||
label: t('零一万物'),
|
|
||||||
icon: <Yi.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('yi')
|
|
||||||
},
|
|
||||||
jina: {
|
|
||||||
label: 'Jina',
|
|
||||||
icon: <Jina />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('jina')
|
|
||||||
},
|
|
||||||
mistral: {
|
|
||||||
label: 'Mistral AI',
|
|
||||||
icon: <Mistral.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('mistral')
|
|
||||||
},
|
|
||||||
xai: {
|
|
||||||
label: 'xAI',
|
|
||||||
icon: <XAI />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('grok')
|
|
||||||
},
|
|
||||||
llama: {
|
|
||||||
label: 'Llama',
|
|
||||||
icon: <Ollama />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('llama')
|
|
||||||
},
|
|
||||||
doubao: {
|
|
||||||
label: t('豆包'),
|
|
||||||
icon: <Doubao.Color />,
|
|
||||||
filter: (model) => model.model_name.toLowerCase().includes('doubao')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -2,6 +2,328 @@ import i18next from 'i18next';
|
|||||||
import { Modal, Tag, Typography } from '@douyinfe/semi-ui';
|
import { Modal, Tag, Typography } from '@douyinfe/semi-ui';
|
||||||
import { copy, isMobile, showSuccess } from './utils';
|
import { copy, isMobile, showSuccess } from './utils';
|
||||||
import { visit } from 'unist-util-visit';
|
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: <OpenAI />,
|
||||||
|
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: <Claude.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('claude')
|
||||||
|
},
|
||||||
|
gemini: {
|
||||||
|
label: 'Gemini',
|
||||||
|
icon: <Gemini.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('gemini')
|
||||||
|
},
|
||||||
|
moonshot: {
|
||||||
|
label: 'Moonshot',
|
||||||
|
icon: <Moonshot />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('moonshot')
|
||||||
|
},
|
||||||
|
zhipu: {
|
||||||
|
label: t('智谱'),
|
||||||
|
icon: <Zhipu.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('chatglm') ||
|
||||||
|
model.model_name.toLowerCase().includes('glm-')
|
||||||
|
},
|
||||||
|
qwen: {
|
||||||
|
label: t('通义千问'),
|
||||||
|
icon: <Qwen.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('qwen')
|
||||||
|
},
|
||||||
|
deepseek: {
|
||||||
|
label: 'DeepSeek',
|
||||||
|
icon: <DeepSeek.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('deepseek')
|
||||||
|
},
|
||||||
|
minimax: {
|
||||||
|
label: 'MiniMax',
|
||||||
|
icon: <Minimax.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('abab')
|
||||||
|
},
|
||||||
|
baidu: {
|
||||||
|
label: t('文心一言'),
|
||||||
|
icon: <Wenxin.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('ernie')
|
||||||
|
},
|
||||||
|
xunfei: {
|
||||||
|
label: t('讯飞星火'),
|
||||||
|
icon: <Spark.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('spark')
|
||||||
|
},
|
||||||
|
midjourney: {
|
||||||
|
label: 'Midjourney',
|
||||||
|
icon: <Midjourney />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('mj_')
|
||||||
|
},
|
||||||
|
tencent: {
|
||||||
|
label: t('腾讯混元'),
|
||||||
|
icon: <Hunyuan.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('hunyuan')
|
||||||
|
},
|
||||||
|
cohere: {
|
||||||
|
label: 'Cohere',
|
||||||
|
icon: <Cohere.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('command')
|
||||||
|
},
|
||||||
|
cloudflare: {
|
||||||
|
label: 'Cloudflare',
|
||||||
|
icon: <Cloudflare.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('@cf/')
|
||||||
|
},
|
||||||
|
ai360: {
|
||||||
|
label: t('360智脑'),
|
||||||
|
icon: <Ai360.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('360')
|
||||||
|
},
|
||||||
|
yi: {
|
||||||
|
label: t('零一万物'),
|
||||||
|
icon: <Yi.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('yi')
|
||||||
|
},
|
||||||
|
jina: {
|
||||||
|
label: 'Jina',
|
||||||
|
icon: <Jina />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('jina')
|
||||||
|
},
|
||||||
|
mistral: {
|
||||||
|
label: 'Mistral AI',
|
||||||
|
icon: <Mistral.Color />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('mistral')
|
||||||
|
},
|
||||||
|
xai: {
|
||||||
|
label: 'xAI',
|
||||||
|
icon: <XAI />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('grok')
|
||||||
|
},
|
||||||
|
llama: {
|
||||||
|
label: 'Llama',
|
||||||
|
icon: <Ollama />,
|
||||||
|
filter: (model) => model.model_name.toLowerCase().includes('llama')
|
||||||
|
},
|
||||||
|
doubao: {
|
||||||
|
label: t('豆包'),
|
||||||
|
icon: <Doubao.Color />,
|
||||||
|
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 (
|
||||||
|
<Tag
|
||||||
|
color={color || modelToColor(modelName)}
|
||||||
|
prefixIcon={icon}
|
||||||
|
suffixIcon={suffixIcon}
|
||||||
|
size={size}
|
||||||
|
shape={shape}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{modelName}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function renderText(text, limit) {
|
export function renderText(text, limit) {
|
||||||
if (text.length > limit) {
|
if (text.length > limit) {
|
||||||
@@ -800,137 +1122,6 @@ export function renderQuotaWithPrompt(quota, digits) {
|
|||||||
return '';
|
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(
|
export function renderClaudeModelPrice(
|
||||||
inputTokens,
|
inputTokens,
|
||||||
completionTokens,
|
completionTokens,
|
||||||
|
|||||||
Reference in New Issue
Block a user