Browse Source

✨ 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.
Apple\Apple 7 months ago
parent
commit
7362047e51

+ 35 - 58
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 (
-        <Tag
-          color={stringToColor(record.model_name)}
-          size='large'
-          shape='circle'
-          onClick={(event) => {
-            copyText(event, record.model_name).then((r) => { });
-          }}
-        >
-          {' '}
-          {record.model_name}{' '}
-        </Tag>
-      );
+      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={
                 <div style={{ padding: 10 }}>
                   <Space vertical align={'start'}>
-                    <Tag
-                      color={stringToColor(record.model_name)}
-                      size='large'
-                      shape='circle'
-                      onClick={(event) => {
-                        copyText(event, record.model_name).then((r) => { });
-                      }}
-                    >
-                      {t('请求并计费模型')} {record.model_name}{' '}
-                    </Tag>
-                    <Tag
-                      color={stringToColor(other.upstream_model_name)}
-                      size='large'
-                      shape='circle'
-                      onClick={(event) => {
-                        copyText(event, other.upstream_model_name).then(
-                          (r) => { },
-                        );
-                      }}
-                    >
-                      {t('实际模型')} {other.upstream_model_name}{' '}
-                    </Tag>
+                    <div className="flex items-center">
+                      <Text strong style={{ marginRight: 8 }}>{t('请求并计费模型')}:</Text>
+                      {renderModelTag(record.model_name, {
+                        color: stringToColor(record.model_name),
+                        onClick: (event) => {
+                          copyText(event, record.model_name).then((r) => { });
+                        }
+                      })}
+                    </div>
+                    <div className="flex items-center">
+                      <Text strong style={{ marginRight: 8 }}>{t('实际模型')}:</Text>
+                      {renderModelTag(other.upstream_model_name, {
+                        color: stringToColor(other.upstream_model_name),
+                        onClick: (event) => {
+                          copyText(event, other.upstream_model_name).then((r) => { });
+                        }
+                      })}
+                    </div>
                   </Space>
                 </div>
               }
             >
-              <Tag
-                color={stringToColor(record.model_name)}
-                size='large'
-                shape='circle'
-                onClick={(event) => {
+              {renderModelTag(record.model_name, {
+                color: stringToColor(record.model_name),
+                onClick: (event) => {
                   copyText(event, record.model_name).then((r) => { });
-                }}
-                suffixIcon={
-                  <IconRefresh
-                    style={{ width: '0.8em', height: '0.8em', opacity: 0.6 }}
-                  />
-                }
-              >
-                {' '}
-                {record.model_name}{' '}
-              </Tag>
+                },
+                suffixIcon: <IconForward style={{ width: '0.9em', height: '0.9em', opacity: 0.75 }} />
+              })}
             </Popover>
           </Space>
         </>

+ 2 - 3
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 = {

+ 0 - 1
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';

+ 0 - 145
web/src/constants/model.constants.js

@@ -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')
-    }
-}); 

+ 322 - 131
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: <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) {
   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,