Browse Source

🎨 refactor(TokensTable): refactor TokensTable UI & UX for clearer data and inline actions

This commit overhauls the `TokensTable` component to deliver a cleaner, more intuitive experience.

Key changes
1. Quota
   • Merged “Used” & “Remaining” into a single “Quota” column.
   • Uses a circular `Progress` with %-label; full details shown on tooltip.

2. Status
   • Tag now embeds a small `Switch` (prefixIcon) to enable/disable a token in-place.
   • Removed enable/disable actions from the old dropdown.

3. Columns & layout
   • Added dedicated “Group” column (moved from Status).
   • Added “Key” column:
     – Read-only `Input` styled like Home page base-URL field.
     – Masked value (`sk-abc********xyz`) by default.
     – Eye button toggles reveal/hide; Copy button copies full key (without modal).
   • Dropped “More” menu; Delete is now a direct button in the action area.

4. Model limits
   • Shows vendor icons inside an `AvatarGroup`; tooltip lists the exact models.

5. IP restriction
   • Displays first IP, extra count as “+N” Tag with tooltip.
   • Unlimited shows white Tag.

6. Cleanup / misc.
   • Removed unused helpers (`getQuotaPerUnit`), icons (`IconMore`, eye/copy duplicates, etc.).
   • Replaced legacy modal view of key with inline input behaviour.
   • Tweaked paddings, themes, sizes to align with design system.

BREAKING CHANGE: Table column order & names have changed; update any tests or docs referencing the old structure.
t0ng7u 6 months ago
parent
commit
7c4b83a430

+ 4 - 4
web/src/components/playground/ParameterControl.js

@@ -34,7 +34,7 @@ const ParameterControl = ({
             <Typography.Text strong className="text-sm">
               Temperature
             </Typography.Text>
-            <Tag size="small" className="!rounded-full">
+            <Tag size="small" shape='circle'>
               {inputs.temperature}
             </Tag>
           </div>
@@ -70,7 +70,7 @@ const ParameterControl = ({
             <Typography.Text strong className="text-sm">
               Top P
             </Typography.Text>
-            <Tag size="small" className="!rounded-full">
+            <Tag size="small" shape='circle'>
               {inputs.top_p}
             </Tag>
           </div>
@@ -106,7 +106,7 @@ const ParameterControl = ({
             <Typography.Text strong className="text-sm">
               Frequency Penalty
             </Typography.Text>
-            <Tag size="small" className="!rounded-full">
+            <Tag size="small" shape='circle'>
               {inputs.frequency_penalty}
             </Tag>
           </div>
@@ -142,7 +142,7 @@ const ParameterControl = ({
             <Typography.Text strong className="text-sm">
               Presence Penalty
             </Typography.Text>
-            <Tag size="small" className="!rounded-full">
+            <Tag size="small" shape='circle'>
               {inputs.presence_penalty}
             </Tag>
           </div>

+ 4 - 4
web/src/components/settings/ChannelSelectorModal.js

@@ -118,25 +118,25 @@ const ChannelSelectorModal = forwardRef(({
     switch (status) {
       case 1:
         return (
-          <Tag size='large' color='green' shape='circle' prefixIcon={<CheckCircle size={14} />}>
+          <Tag color='green' shape='circle' prefixIcon={<CheckCircle size={14} />}>
             {t('已启用')}
           </Tag>
         );
       case 2:
         return (
-          <Tag size='large' color='red' shape='circle' prefixIcon={<XCircle size={14} />}>
+          <Tag color='red' shape='circle' prefixIcon={<XCircle size={14} />}>
             {t('已禁用')}
           </Tag>
         );
       case 3:
         return (
-          <Tag size='large' color='yellow' shape='circle' prefixIcon={<AlertCircle size={14} />}>
+          <Tag color='yellow' shape='circle' prefixIcon={<AlertCircle size={14} />}>
             {t('自动禁用')}
           </Tag>
         );
       default:
         return (
-          <Tag size='large' color='grey' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='grey' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知状态')}
           </Tag>
         );

+ 16 - 20
web/src/components/table/ChannelsTable.js

@@ -63,7 +63,6 @@ const ChannelsTable = () => {
     }
     return (
       <Tag
-        size='large'
         color={type2label[type]?.color}
         shape='circle'
         prefixIcon={getChannelIcon(type)}
@@ -77,7 +76,6 @@ const ChannelsTable = () => {
     return (
       <Tag
         color='light-blue'
-        size='large'
         shape='circle'
         type='light'
       >
@@ -90,25 +88,25 @@ const ChannelsTable = () => {
     switch (status) {
       case 1:
         return (
-          <Tag size='large' color='green' shape='circle'>
+          <Tag color='green' shape='circle'>
             {t('已启用')}
           </Tag>
         );
       case 2:
         return (
-          <Tag size='large' color='red' shape='circle'>
+          <Tag color='red' shape='circle'>
             {t('已禁用')}
           </Tag>
         );
       case 3:
         return (
-          <Tag size='large' color='yellow' shape='circle'>
+          <Tag color='yellow' shape='circle'>
             {t('自动禁用')}
           </Tag>
         );
       default:
         return (
-          <Tag size='large' color='grey' shape='circle'>
+          <Tag color='grey' shape='circle'>
             {t('未知状态')}
           </Tag>
         );
@@ -120,31 +118,31 @@ const ChannelsTable = () => {
     time = time.toFixed(2) + t(' 秒');
     if (responseTime === 0) {
       return (
-        <Tag size='large' color='grey' shape='circle'>
+        <Tag color='grey' shape='circle'>
           {t('未测试')}
         </Tag>
       );
     } else if (responseTime <= 1000) {
       return (
-        <Tag size='large' color='green' shape='circle'>
+        <Tag color='green' shape='circle'>
           {time}
         </Tag>
       );
     } else if (responseTime <= 3000) {
       return (
-        <Tag size='large' color='lime' shape='circle'>
+        <Tag color='lime' shape='circle'>
           {time}
         </Tag>
       );
     } else if (responseTime <= 5000) {
       return (
-        <Tag size='large' color='yellow' shape='circle'>
+        <Tag color='yellow' shape='circle'>
           {time}
         </Tag>
       );
     } else {
       return (
-        <Tag size='large' color='red' shape='circle'>
+        <Tag color='red' shape='circle'>
           {time}
         </Tag>
       );
@@ -331,7 +329,7 @@ const ChannelsTable = () => {
             <div>
               <Space spacing={1}>
                 <Tooltip content={t('已用额度')}>
-                  <Tag color='white' type='ghost' size='large' shape='circle'>
+                  <Tag color='white' type='ghost' shape='circle'>
                     {renderQuota(record.used_quota)}
                   </Tag>
                 </Tooltip>
@@ -339,7 +337,6 @@ const ChannelsTable = () => {
                   <Tag
                     color='white'
                     type='ghost'
-                    size='large'
                     shape='circle'
                     onClick={() => updateChannelBalance(record)}
                   >
@@ -352,7 +349,7 @@ const ChannelsTable = () => {
         } else {
           return (
             <Tooltip content={t('已用额度')}>
-              <Tag color='white' type='ghost' size='large' shape='circle'>
+              <Tag color='white' type='ghost' shape='circle'>
                 {renderQuota(record.used_quota)}
               </Tag>
             </Tooltip>
@@ -1240,7 +1237,7 @@ const ChannelsTable = () => {
           tab={
             <span className="flex items-center gap-2">
               {t('全部')}
-              <Tag color={activeTypeKey === 'all' ? 'red' : 'grey'} size='small' shape='circle'>
+              <Tag color={activeTypeKey === 'all' ? 'red' : 'grey'} shape='circle'>
                 {channelTypeCounts['all'] || 0}
               </Tag>
             </span>
@@ -1258,7 +1255,7 @@ const ChannelsTable = () => {
                 <span className="flex items-center gap-2">
                   {getChannelIcon(option.value)}
                   {option.label}
-                  <Tag color={activeTypeKey === key ? 'red' : 'grey'} size='small' shape='circle'>
+                  <Tag color={activeTypeKey === key ? 'red' : 'grey'} shape='circle'>
                     {count}
                   </Tag>
                 </span>
@@ -1461,7 +1458,7 @@ const ChannelsTable = () => {
 
   const fixChannelsAbilities = async () => {
     const res = await API.post(`/api/channel/fix`);
-    const { success, message, data  } = res.data;
+    const { success, message, data } = res.data;
     if (success) {
       showSuccess(t('已修复 ${success} 个通道,失败 ${fails} 个通道。').replace('${success}', data.success).replace('${fails}', data.fails));
       await refresh();
@@ -2033,7 +2030,7 @@ const ChannelsTable = () => {
 
                       if (isTesting) {
                         return (
-                          <Tag size='large' color='blue' shape='circle'>
+                          <Tag color='blue' shape='circle'>
                             {t('测试中')}
                           </Tag>
                         );
@@ -2041,7 +2038,7 @@ const ChannelsTable = () => {
 
                       if (!testResult) {
                         return (
-                          <Tag size='large' color='grey' shape='circle'>
+                          <Tag color='grey' shape='circle'>
                             {t('未开始')}
                           </Tag>
                         );
@@ -2050,7 +2047,6 @@ const ChannelsTable = () => {
                       return (
                         <div className="flex items-center gap-2">
                           <Tag
-                            size='large'
                             color={testResult.success ? 'green' : 'red'}
                             shape='circle'
                           >

+ 14 - 17
web/src/components/table/LogsTable.js

@@ -78,37 +78,37 @@ const LogsTable = () => {
     switch (type) {
       case 1:
         return (
-          <Tag color='cyan' size='large' shape='circle'>
+          <Tag color='cyan' shape='circle'>
             {t('充值')}
           </Tag>
         );
       case 2:
         return (
-          <Tag color='lime' size='large' shape='circle'>
+          <Tag color='lime' shape='circle'>
             {t('消费')}
           </Tag>
         );
       case 3:
         return (
-          <Tag color='orange' size='large' shape='circle'>
+          <Tag color='orange' shape='circle'>
             {t('管理')}
           </Tag>
         );
       case 4:
         return (
-          <Tag color='purple' size='large' shape='circle'>
+          <Tag color='purple' shape='circle'>
             {t('系统')}
           </Tag>
         );
       case 5:
         return (
-          <Tag color='red' size='large' shape='circle'>
+          <Tag color='red' shape='circle'>
             {t('错误')}
           </Tag>
         );
       default:
         return (
-          <Tag color='grey' size='large' shape='circle'>
+          <Tag color='grey' shape='circle'>
             {t('未知')}
           </Tag>
         );
@@ -118,13 +118,13 @@ const LogsTable = () => {
   function renderIsStream(bool) {
     if (bool) {
       return (
-        <Tag color='blue' size='large' shape='circle'>
+        <Tag color='blue' shape='circle'>
           {t('流')}
         </Tag>
       );
     } else {
       return (
-        <Tag color='purple' size='large' shape='circle'>
+        <Tag color='purple' shape='circle'>
           {t('非流')}
         </Tag>
       );
@@ -135,21 +135,21 @@ const LogsTable = () => {
     const time = parseInt(type);
     if (time < 101) {
       return (
-        <Tag color='green' size='large' shape='circle'>
+        <Tag color='green' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
       );
     } else if (time < 300) {
       return (
-        <Tag color='orange' size='large' shape='circle'>
+        <Tag color='orange' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
       );
     } else {
       return (
-        <Tag color='red' size='large' shape='circle'>
+        <Tag color='red' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
@@ -162,21 +162,21 @@ const LogsTable = () => {
     time = time.toFixed(1);
     if (time < 3) {
       return (
-        <Tag color='green' size='large' shape='circle'>
+        <Tag color='green' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
       );
     } else if (time < 10) {
       return (
-        <Tag color='orange' size='large' shape='circle'>
+        <Tag color='orange' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
       );
     } else {
       return (
-        <Tag color='red' size='large' shape='circle'>
+        <Tag color='red' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
@@ -363,7 +363,6 @@ const LogsTable = () => {
                 <Tooltip content={record.channel_name || '[未知]'}>
                   <Tag
                     color={colors[parseInt(text) % colors.length]}
-                    size='large'
                     shape='circle'
                   >
                     {' '}
@@ -415,7 +414,6 @@ const LogsTable = () => {
           <div>
             <Tag
               color='grey'
-              size='large'
               shape='circle'
               onClick={(event) => {
                 //cancel the row click event
@@ -567,7 +565,6 @@ const LogsTable = () => {
           <Tooltip content={text}>
             <Tag
               color='orange'
-              size='large'
               shape='circle'
               onClick={(event) => {
                 copyText(event, text);

+ 32 - 33
web/src/components/table/MjLogsTable.js

@@ -185,115 +185,115 @@ const LogsTable = () => {
     switch (type) {
       case 'IMAGINE':
         return (
-          <Tag color='blue' size='large' shape='circle' prefixIcon={<Palette size={14} />}>
+          <Tag color='blue' shape='circle' prefixIcon={<Palette size={14} />}>
             {t('绘图')}
           </Tag>
         );
       case 'UPSCALE':
         return (
-          <Tag color='orange' size='large' shape='circle' prefixIcon={<ZoomIn size={14} />}>
+          <Tag color='orange' shape='circle' prefixIcon={<ZoomIn size={14} />}>
             {t('放大')}
           </Tag>
         );
       case 'VIDEO':
         return (
-          <Tag color='orange' size='large' shape='circle' prefixIcon={<Video size={14} />}>
+          <Tag color='orange' shape='circle' prefixIcon={<Video size={14} />}>
             {t('视频')}
           </Tag>
         );
       case 'EDITS':
         return (
-          <Tag color='orange' size='large' shape='circle' prefixIcon={<Video size={14} />}>
+          <Tag color='orange' shape='circle' prefixIcon={<Video size={14} />}>
             {t('编辑')}
           </Tag>
         );
       case 'VARIATION':
         return (
-          <Tag color='purple' size='large' shape='circle' prefixIcon={<Shuffle size={14} />}>
+          <Tag color='purple' shape='circle' prefixIcon={<Shuffle size={14} />}>
             {t('变换')}
           </Tag>
         );
       case 'HIGH_VARIATION':
         return (
-          <Tag color='purple' size='large' shape='circle' prefixIcon={<Shuffle size={14} />}>
+          <Tag color='purple' shape='circle' prefixIcon={<Shuffle size={14} />}>
             {t('强变换')}
           </Tag>
         );
       case 'LOW_VARIATION':
         return (
-          <Tag color='purple' size='large' shape='circle' prefixIcon={<Shuffle size={14} />}>
+          <Tag color='purple' shape='circle' prefixIcon={<Shuffle size={14} />}>
             {t('弱变换')}
           </Tag>
         );
       case 'PAN':
         return (
-          <Tag color='cyan' size='large' shape='circle' prefixIcon={<Move size={14} />}>
+          <Tag color='cyan' shape='circle' prefixIcon={<Move size={14} />}>
             {t('平移')}
           </Tag>
         );
       case 'DESCRIBE':
         return (
-          <Tag color='yellow' size='large' shape='circle' prefixIcon={<FileText size={14} />}>
+          <Tag color='yellow' shape='circle' prefixIcon={<FileText size={14} />}>
             {t('图生文')}
           </Tag>
         );
       case 'BLEND':
         return (
-          <Tag color='lime' size='large' shape='circle' prefixIcon={<Blend size={14} />}>
+          <Tag color='lime' shape='circle' prefixIcon={<Blend size={14} />}>
             {t('图混合')}
           </Tag>
         );
       case 'UPLOAD':
         return (
-          <Tag color='blue' size='large' shape='circle' prefixIcon={<Upload size={14} />}>
+          <Tag color='blue' shape='circle' prefixIcon={<Upload size={14} />}>
             上传文件
           </Tag>
         );
       case 'SHORTEN':
         return (
-          <Tag color='pink' size='large' shape='circle' prefixIcon={<Minimize2 size={14} />}>
+          <Tag color='pink' shape='circle' prefixIcon={<Minimize2 size={14} />}>
             {t('缩词')}
           </Tag>
         );
       case 'REROLL':
         return (
-          <Tag color='indigo' size='large' shape='circle' prefixIcon={<RotateCcw size={14} />}>
+          <Tag color='indigo' shape='circle' prefixIcon={<RotateCcw size={14} />}>
             {t('重绘')}
           </Tag>
         );
       case 'INPAINT':
         return (
-          <Tag color='violet' size='large' shape='circle' prefixIcon={<PaintBucket size={14} />}>
+          <Tag color='violet' shape='circle' prefixIcon={<PaintBucket size={14} />}>
             {t('局部重绘-提交')}
           </Tag>
         );
       case 'ZOOM':
         return (
-          <Tag color='teal' size='large' shape='circle' prefixIcon={<Focus size={14} />}>
+          <Tag color='teal' shape='circle' prefixIcon={<Focus size={14} />}>
             {t('变焦')}
           </Tag>
         );
       case 'CUSTOM_ZOOM':
         return (
-          <Tag color='teal' size='large' shape='circle' prefixIcon={<Move3D size={14} />}>
+          <Tag color='teal' shape='circle' prefixIcon={<Move3D size={14} />}>
             {t('自定义变焦-提交')}
           </Tag>
         );
       case 'MODAL':
         return (
-          <Tag color='green' size='large' shape='circle' prefixIcon={<Monitor size={14} />}>
+          <Tag color='green' shape='circle' prefixIcon={<Monitor size={14} />}>
             {t('窗口处理')}
           </Tag>
         );
       case 'SWAP_FACE':
         return (
-          <Tag color='light-green' size='large' shape='circle' prefixIcon={<UserCheck size={14} />}>
+          <Tag color='light-green' shape='circle' prefixIcon={<UserCheck size={14} />}>
             {t('换脸')}
           </Tag>
         );
       default:
         return (
-          <Tag color='white' size='large' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知')}
           </Tag>
         );
@@ -304,31 +304,31 @@ const LogsTable = () => {
     switch (code) {
       case 1:
         return (
-          <Tag color='green' size='large' shape='circle' prefixIcon={<CheckCircle size={14} />}>
+          <Tag color='green' shape='circle' prefixIcon={<CheckCircle size={14} />}>
             {t('已提交')}
           </Tag>
         );
       case 21:
         return (
-          <Tag color='lime' size='large' shape='circle' prefixIcon={<Clock size={14} />}>
+          <Tag color='lime' shape='circle' prefixIcon={<Clock size={14} />}>
             {t('等待中')}
           </Tag>
         );
       case 22:
         return (
-          <Tag color='orange' size='large' shape='circle' prefixIcon={<Copy size={14} />}>
+          <Tag color='orange' shape='circle' prefixIcon={<Copy size={14} />}>
             {t('重复提交')}
           </Tag>
         );
       case 0:
         return (
-          <Tag color='yellow' size='large' shape='circle' prefixIcon={<FileX size={14} />}>
+          <Tag color='yellow' shape='circle' prefixIcon={<FileX size={14} />}>
             {t('未提交')}
           </Tag>
         );
       default:
         return (
-          <Tag color='white' size='large' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知')}
           </Tag>
         );
@@ -339,43 +339,43 @@ const LogsTable = () => {
     switch (type) {
       case 'SUCCESS':
         return (
-          <Tag color='green' size='large' shape='circle' prefixIcon={<CheckCircle size={14} />}>
+          <Tag color='green' shape='circle' prefixIcon={<CheckCircle size={14} />}>
             {t('成功')}
           </Tag>
         );
       case 'NOT_START':
         return (
-          <Tag color='grey' size='large' shape='circle' prefixIcon={<Pause size={14} />}>
+          <Tag color='grey' shape='circle' prefixIcon={<Pause size={14} />}>
             {t('未启动')}
           </Tag>
         );
       case 'SUBMITTED':
         return (
-          <Tag color='yellow' size='large' shape='circle' prefixIcon={<Clock size={14} />}>
+          <Tag color='yellow' shape='circle' prefixIcon={<Clock size={14} />}>
             {t('队列中')}
           </Tag>
         );
       case 'IN_PROGRESS':
         return (
-          <Tag color='blue' size='large' shape='circle' prefixIcon={<Loader size={14} />}>
+          <Tag color='blue' shape='circle' prefixIcon={<Loader size={14} />}>
             {t('执行中')}
           </Tag>
         );
       case 'FAILURE':
         return (
-          <Tag color='red' size='large' shape='circle' prefixIcon={<XCircle size={14} />}>
+          <Tag color='red' shape='circle' prefixIcon={<XCircle size={14} />}>
             {t('失败')}
           </Tag>
         );
       case 'MODAL':
         return (
-          <Tag color='yellow' size='large' shape='circle' prefixIcon={<AlertCircle size={14} />}>
+          <Tag color='yellow' shape='circle' prefixIcon={<AlertCircle size={14} />}>
             {t('窗口等待')}
           </Tag>
         );
       default:
         return (
-          <Tag color='white' size='large' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知')}
           </Tag>
         );
@@ -405,7 +405,7 @@ const LogsTable = () => {
     const color = durationSec > 60 ? 'red' : 'green';
 
     return (
-      <Tag color={color} size='large' shape='circle' prefixIcon={<Clock size={14} />}>
+      <Tag color={color} shape='circle' prefixIcon={<Clock size={14} />}>
         {durationSec} {t('秒')}
       </Tag>
     );
@@ -439,7 +439,6 @@ const LogsTable = () => {
           <div>
             <Tag
               color={colors[parseInt(text) % colors.length]}
-              size='large'
               shape='circle'
               prefixIcon={<Hash size={14} />}
               onClick={() => {

+ 3 - 8
web/src/components/table/ModelPricing.js

@@ -76,13 +76,13 @@ const ModelPricing = () => {
     switch (type) {
       case 1:
         return (
-          <Tag color='teal' size='large' shape='circle'>
+          <Tag color='teal' shape='circle'>
             {t('按次计费')}
           </Tag>
         );
       case 0:
         return (
-          <Tag color='violet' size='large' shape='circle'>
+          <Tag color='violet' shape='circle'>
             {t('按量计费')}
           </Tag>
         );
@@ -116,7 +116,6 @@ const ModelPricing = () => {
           <Tag
             key={endpoint}
             color={stringToColor(endpoint)}
-            size='large'
             shape='circle'
           >
             {endpoint}
@@ -179,7 +178,7 @@ const ModelPricing = () => {
               if (usableGroup[group]) {
                 if (group === selectedGroup) {
                   return (
-                    <Tag color='blue' size='large' shape='circle' prefixIcon={<IconVerify />}>
+                    <Tag color='blue' shape='circle' prefixIcon={<IconVerify />}>
                       {group}
                     </Tag>
                   );
@@ -187,7 +186,6 @@ const ModelPricing = () => {
                   return (
                     <Tag
                       color='blue'
-                      size='large'
                       shape='circle'
                       onClick={() => {
                         setSelectedGroup(group);
@@ -392,7 +390,6 @@ const ModelPricing = () => {
                     {category.label}
                     <Tag
                       color={activeKey === key ? 'red' : 'grey'}
-                      size='small'
                       shape='circle'
                     >
                       {modelCount}
@@ -436,7 +433,6 @@ const ModelPricing = () => {
             onCompositionEnd={handleCompositionEnd}
             onChange={handleChange}
             showClear
-            size="large"
           />
         </div>
         <Button
@@ -446,7 +442,6 @@ const ModelPricing = () => {
           onClick={() => copyText(selectedRowKeys)}
           disabled={selectedRowKeys.length === 0}
           className="!bg-blue-500 hover:!bg-blue-600 text-white"
-          size="large"
         >
           {t('复制选中模型')}
         </Button>

+ 6 - 6
web/src/components/table/RedemptionsTable.js

@@ -53,31 +53,31 @@ const RedemptionsTable = () => {
   const renderStatus = (status, record) => {
     if (isExpired(record)) {
       return (
-        <Tag color='orange' size='large' shape='circle'>{t('已过期')}</Tag>
+        <Tag color='orange' shape='circle'>{t('已过期')}</Tag>
       );
     }
     switch (status) {
       case 1:
         return (
-          <Tag color='green' size='large' shape='circle'>
+          <Tag color='green' shape='circle'>
             {t('未使用')}
           </Tag>
         );
       case 2:
         return (
-          <Tag color='red' size='large' shape='circle'>
+          <Tag color='red' shape='circle'>
             {t('已禁用')}
           </Tag>
         );
       case 3:
         return (
-          <Tag color='grey' size='large' shape='circle'>
+          <Tag color='grey' shape='circle'>
             {t('已使用')}
           </Tag>
         );
       default:
         return (
-          <Tag color='black' size='large' shape='circle'>
+          <Tag color='black' shape='circle'>
             {t('未知状态')}
           </Tag>
         );
@@ -107,7 +107,7 @@ const RedemptionsTable = () => {
       render: (text, record, index) => {
         return (
           <div>
-            <Tag size={'large'} color={'grey'} shape='circle'>
+            <Tag color='grey' shape='circle'>
               {renderQuota(parseInt(text))}
             </Tag>
           </div>

+ 19 - 19
web/src/components/table/TaskLogsTable.js

@@ -106,7 +106,7 @@ function renderDuration(submit_time, finishTime) {
 
   // 返回带有样式的颜色标签
   return (
-    <Tag color={color} size='large' prefixIcon={<Clock size={14} />}>
+    <Tag color={color} prefixIcon={<Clock size={14} />}>
       {durationSec} 秒
     </Tag>
   );
@@ -198,31 +198,31 @@ const LogsTable = () => {
     switch (type) {
       case 'MUSIC':
         return (
-          <Tag color='grey' size='large' shape='circle' prefixIcon={<Music size={14} />}>
+          <Tag color='grey' shape='circle' prefixIcon={<Music size={14} />}>
             {t('生成音乐')}
           </Tag>
         );
       case 'LYRICS':
         return (
-          <Tag color='pink' size='large' shape='circle' prefixIcon={<FileText size={14} />}>
+          <Tag color='pink' shape='circle' prefixIcon={<FileText size={14} />}>
             {t('生成歌词')}
           </Tag>
         );
       case TASK_ACTION_GENERATE:
         return (
-          <Tag color='blue' size='large' shape='circle' prefixIcon={<Sparkles size={14} />}>
+          <Tag color='blue' shape='circle' prefixIcon={<Sparkles size={14} />}>
             {t('图生视频')}
           </Tag>
         );
       case TASK_ACTION_TEXT_GENERATE:
         return (
-          <Tag color='blue' size='large' shape='circle' prefixIcon={<Sparkles size={14} />}>
+          <Tag color='blue' shape='circle' prefixIcon={<Sparkles size={14} />}>
             {t('文生视频')}
           </Tag>
         );
       default:
         return (
-          <Tag color='white' size='large' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知')}
           </Tag>
         );
@@ -233,25 +233,25 @@ const LogsTable = () => {
     switch (platform) {
       case 'suno':
         return (
-          <Tag color='green' size='large' shape='circle' prefixIcon={<Music size={14} />}>
+          <Tag color='green' shape='circle' prefixIcon={<Music size={14} />}>
             Suno
           </Tag>
         );
       case 'kling':
         return (
-          <Tag color='orange' size='large' shape='circle' prefixIcon={<Video size={14} />}>
+          <Tag color='orange' shape='circle' prefixIcon={<Video size={14} />}>
             Kling
           </Tag>
         );
       case 'jimeng':
         return (
-          <Tag color='purple' size='large' shape='circle' prefixIcon={<Video size={14} />}>
+          <Tag color='purple' shape='circle' prefixIcon={<Video size={14} />}>
             Jimeng
           </Tag>
         );
       default:
         return (
-          <Tag color='white' size='large' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知')}
           </Tag>
         );
@@ -262,55 +262,55 @@ const LogsTable = () => {
     switch (type) {
       case 'SUCCESS':
         return (
-          <Tag color='green' size='large' shape='circle' prefixIcon={<CheckCircle size={14} />}>
+          <Tag color='green' shape='circle' prefixIcon={<CheckCircle size={14} />}>
             {t('成功')}
           </Tag>
         );
       case 'NOT_START':
         return (
-          <Tag color='grey' size='large' shape='circle' prefixIcon={<Pause size={14} />}>
+          <Tag color='grey' shape='circle' prefixIcon={<Pause size={14} />}>
             {t('未启动')}
           </Tag>
         );
       case 'SUBMITTED':
         return (
-          <Tag color='yellow' size='large' shape='circle' prefixIcon={<Clock size={14} />}>
+          <Tag color='yellow' shape='circle' prefixIcon={<Clock size={14} />}>
             {t('队列中')}
           </Tag>
         );
       case 'IN_PROGRESS':
         return (
-          <Tag color='blue' size='large' shape='circle' prefixIcon={<Play size={14} />}>
+          <Tag color='blue' shape='circle' prefixIcon={<Play size={14} />}>
             {t('执行中')}
           </Tag>
         );
       case 'FAILURE':
         return (
-          <Tag color='red' size='large' shape='circle' prefixIcon={<XCircle size={14} />}>
+          <Tag color='red' shape='circle' prefixIcon={<XCircle size={14} />}>
             {t('失败')}
           </Tag>
         );
       case 'QUEUED':
         return (
-          <Tag color='orange' size='large' shape='circle' prefixIcon={<List size={14} />}>
+          <Tag color='orange' shape='circle' prefixIcon={<List size={14} />}>
             {t('排队中')}
           </Tag>
         );
       case 'UNKNOWN':
         return (
-          <Tag color='white' size='large' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知')}
           </Tag>
         );
       case '':
         return (
-          <Tag color='grey' size='large' shape='circle' prefixIcon={<Loader size={14} />}>
+          <Tag color='grey' shape='circle' prefixIcon={<Loader size={14} />}>
             {t('正在提交')}
           </Tag>
         );
       default:
         return (
-          <Tag color='white' size='large' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知')}
           </Tag>
         );

+ 254 - 161
web/src/components/table/TokensTable.js

@@ -7,7 +7,7 @@ import {
   timestamp2string,
   renderGroup,
   renderQuota,
-  getQuotaPerUnit
+  getModelCategories
 } from '../../helpers';
 import { ITEMS_PER_PAGE } from '../../constants';
 import {
@@ -22,6 +22,12 @@ import {
   SplitButtonGroup,
   Table,
   Tag,
+  AvatarGroup,
+  Avatar,
+  Tooltip,
+  Progress,
+  Switch,
+  Input,
   Typography
 } from '@douyinfe/semi-ui';
 import {
@@ -31,7 +37,10 @@ import {
 import {
   IconSearch,
   IconTreeTriangleDown,
-  IconMore,
+  IconCopy,
+  IconEyeOpened,
+  IconEyeClosed,
+  IconBolt,
 } from '@douyinfe/semi-icons';
 import { Key } from 'lucide-react';
 import EditToken from '../../pages/Token/EditToken';
@@ -47,49 +56,6 @@ function renderTimestamp(timestamp) {
 const TokensTable = () => {
   const { t } = useTranslation();
 
-  const renderStatus = (status, model_limits_enabled = false) => {
-    switch (status) {
-      case 1:
-        if (model_limits_enabled) {
-          return (
-            <Tag color='green' size='large' shape='circle' >
-              {t('已启用:限制模型')}
-            </Tag>
-          );
-        } else {
-          return (
-            <Tag color='green' size='large' shape='circle' >
-              {t('已启用')}
-            </Tag>
-          );
-        }
-      case 2:
-        return (
-          <Tag color='red' size='large' shape='circle' >
-            {t('已禁用')}
-          </Tag>
-        );
-      case 3:
-        return (
-          <Tag color='yellow' size='large' shape='circle' >
-            {t('已过期')}
-          </Tag>
-        );
-      case 4:
-        return (
-          <Tag color='grey' size='large' shape='circle' >
-            {t('已耗尽')}
-          </Tag>
-        );
-      default:
-        return (
-          <Tag color='black' size='large' shape='circle' >
-            {t('未知状态')}
-          </Tag>
-        );
-    }
-  };
-
   const columns = [
     {
       title: t('名称'),
@@ -99,66 +65,249 @@ const TokensTable = () => {
       title: t('状态'),
       dataIndex: 'status',
       key: 'status',
-      render: (text, record, index) => {
+      render: (text, record) => {
+        const enabled = text === 1;
+        const handleToggle = (checked) => {
+          if (checked) {
+            manageToken(record.id, 'enable', record);
+          } else {
+            manageToken(record.id, 'disable', record);
+          }
+        };
+
+        let tagColor = 'black';
+        let tagText = t('未知状态');
+        if (enabled) {
+          tagColor = 'green';
+          tagText = t('已启用');
+        } else if (text === 2) {
+          tagColor = 'red';
+          tagText = t('已禁用');
+        } else if (text === 3) {
+          tagColor = 'yellow';
+          tagText = t('已过期');
+        } else if (text === 4) {
+          tagColor = 'grey';
+          tagText = t('已耗尽');
+        }
+
         return (
-          <div>
-            <Space>
-              {renderStatus(text, record.model_limits_enabled)}
-              {renderGroup(record.group)}
-            </Space>
-          </div>
+          <Tag
+            color={tagColor}
+            shape='circle'
+            prefixIcon={
+              <Switch
+                size='small'
+                checked={enabled}
+                onChange={handleToggle}
+                aria-label='token status switch'
+              />
+            }
+          >
+            {tagText}
+          </Tag>
         );
       },
     },
     {
-      title: t('已用额度'),
-      dataIndex: 'used_quota',
-      render: (text, record, index) => {
+      title: t('分组'),
+      dataIndex: 'group',
+      key: 'group',
+      render: (text) => {
+        if (text === 'auto') {
+          return (
+            <Tooltip
+              content={t('当前分组为 auto,会自动选择最优分组,当一个组不可用时自动降级到下一个组(熔断机制)')}
+              position='top'
+            >
+              <Tag color='blue' shape='circle' prefixIcon={<IconBolt />}> {t('智能熔断')} </Tag>
+            </Tooltip>
+          );
+        }
+        return renderGroup(text);
+      },
+    },
+    {
+      title: t('密钥'),
+      key: 'token_key',
+      render: (text, record) => {
+        const fullKey = 'sk-' + record.key;
+        const maskedKey = 'sk-' + record.key.slice(0, 3) + '********' + record.key.slice(-3);
+        const revealed = !!showKeys[record.id];
+
         return (
-          <div>
-            <Tag size={'large'} color={'grey'} shape='circle' >
-              {renderQuota(parseInt(text))}
-            </Tag>
+          <div className='w-[200px]'>
+            <Input
+              readOnly
+              value={revealed ? fullKey : maskedKey}
+              size='small'
+              suffix={
+                <div className='flex items-center'>
+                  <Button
+                    theme='borderless'
+                    size='small'
+                    type='tertiary'
+                    icon={revealed ? <IconEyeClosed /> : <IconEyeOpened />}
+                    aria-label='toggle token visibility'
+                    onClick={(e) => {
+                      e.stopPropagation();
+                      setShowKeys(prev => ({ ...prev, [record.id]: !revealed }));
+                    }}
+                  />
+                  <Button
+                    theme='borderless'
+                    size='small'
+                    type='tertiary'
+                    icon={<IconCopy />}
+                    aria-label='copy token key'
+                    onClick={async (e) => {
+                      e.stopPropagation();
+                      await copyText(fullKey);
+                    }}
+                  />
+                </div>
+              }
+            />
           </div>
         );
       },
     },
     {
-      title: t('剩余额度'),
-      dataIndex: 'remain_quota',
-      render: (text, record, index) => {
-        const getQuotaColor = (quotaValue) => {
-          const quotaPerUnit = getQuotaPerUnit();
-          const dollarAmount = quotaValue / quotaPerUnit;
-
-          if (dollarAmount <= 0) {
-            return 'red';
-          } else if (dollarAmount <= 100) {
-            return 'yellow';
-          } else {
-            return 'green';
-          }
+      title: t('额度'),
+      key: 'quota',
+      render: (text, record) => {
+        if (record.unlimited_quota) {
+          return <Tag color='white' shape='circle'>{t('无限制')}</Tag>;
+        }
+
+        const used = parseInt(record.used_quota) || 0;
+        const remain = parseInt(record.remain_quota) || 0;
+        const total = used + remain;
+        const percent = total > 0 ? (used / total) * 100 : 0;
+
+        const getProgressColor = (pct) => {
+          if (pct >= 90) return 'var(--semi-color-danger)';
+          if (pct >= 70) return 'var(--semi-color-warning)';
+          return undefined; // default color
         };
 
         return (
-          <div>
-            {record.unlimited_quota ? (
-              <Tag size={'large'} color={'white'} shape='circle' >
-                {t('无限制')}
-              </Tag>
-            ) : (
-              <Tag
-                size={'large'}
-                color={getQuotaColor(parseInt(text))}
-                shape='circle'
-              >
-                {renderQuota(parseInt(text))}
-              </Tag>
-            )}
-          </div>
+          <Tooltip
+            content={
+              <div className='text-xs'>
+                <div>{t('已用额度')}: {renderQuota(used)}</div>
+                <div>{t('剩余额度')}: {renderQuota(remain)}</div>
+                <div>{t('总额度')}: {renderQuota(total)}</div>
+              </div>
+            }
+          >
+            <div className='w-[30px]'>
+              <Progress
+                percent={percent}
+                stroke={getProgressColor(percent)}
+                showInfo
+                aria-label='quota usage'
+                format={percent => <span className="text-xs">{percent.toFixed(0)}%</span>}
+                type="circle"
+                width={30}
+              />
+            </div>
+          </Tooltip>
         );
       },
     },
+    {
+      title: t('可用模型'),
+      dataIndex: 'model_limits',
+      render: (text, record) => {
+        if (record.model_limits_enabled && text) {
+          const models = text.split(',').filter(Boolean);
+          const categories = getModelCategories(t);
+
+          const vendorAvatars = [];
+          Object.entries(categories).forEach(([key, category]) => {
+            if (key === 'all') return;
+            if (!category.icon || !category.filter) return;
+            const vendorModels = models.filter((m) => category.filter({ model_name: m }));
+            if (vendorModels.length > 0) {
+              vendorAvatars.push(
+                <Tooltip key={key} content={vendorModels.join(', ')} position='top' showArrow>
+                  <Avatar size='extra-extra-small' alt={category.label} color='transparent'>
+                    {category.icon}
+                  </Avatar>
+                </Tooltip>
+              );
+            }
+          });
+
+          if (vendorAvatars.length === 0) {
+            vendorAvatars.push(
+              <Tooltip key='default' content={models.join(', ')} position='top' showArrow>
+                <Avatar size='extra-extra-small' alt='models'>
+                  {models[0].slice(0, 2).toUpperCase()}
+                </Avatar>
+              </Tooltip>
+            );
+          }
+
+          return (
+            <AvatarGroup size='extra-extra-small'>
+              {vendorAvatars}
+            </AvatarGroup>
+          );
+        } else {
+          return (
+            <Tag color='white' shape='circle'>
+              {t('无限制')}
+            </Tag>
+          );
+        }
+      },
+    },
+    {
+      title: t('IP限制'),
+      dataIndex: 'allow_ips',
+      render: (text) => {
+        if (!text || text.trim() === '') {
+          return (
+            <Tag color='white' shape='circle'>
+              {t('无限制')}
+            </Tag>
+          );
+        }
+
+        const ips = text
+          .split('\n')
+          .map((ip) => ip.trim())
+          .filter(Boolean);
+
+        const displayIps = ips.slice(0, 1);
+        const extraCount = ips.length - displayIps.length;
+
+        const ipTags = displayIps.map((ip, idx) => (
+          <Tag key={idx} shape='circle'>
+            {ip}
+          </Tag>
+        ));
+
+        if (extraCount > 0) {
+          ipTags.push(
+            <Tooltip
+              key='extra'
+              content={ips.slice(2).join(', ')}
+              position='top'
+              showArrow
+            >
+              <Tag shape='circle'>
+                {'+' + extraCount}
+              </Tag>
+            </Tooltip>
+          );
+        }
+
+        return <Space wrap>{ipTags}</Space>;
+      },
+    },
     {
       title: t('创建时间'),
       dataIndex: 'created_time',
@@ -211,58 +360,6 @@ const TokensTable = () => {
           }
         }
 
-        // 创建更多操作的下拉菜单项
-        const moreMenuItems = [
-          {
-            node: 'item',
-            name: t('查看'),
-            onClick: () => {
-              Modal.info({
-                title: t('令牌详情'),
-                content: 'sk-' + record.key,
-                size: 'large',
-              });
-            },
-          },
-          {
-            node: 'item',
-            name: t('删除'),
-            type: 'danger',
-            onClick: () => {
-              Modal.confirm({
-                title: t('确定是否要删除此令牌?'),
-                content: t('此修改将不可逆'),
-                onOk: () => {
-                  manageToken(record.id, 'delete', record).then(() => {
-                    removeRecord(record.key);
-                  });
-                },
-              });
-            },
-          }
-        ];
-
-        // 动态添加启用/禁用按钮
-        if (record.status === 1) {
-          moreMenuItems.push({
-            node: 'item',
-            name: t('禁用'),
-            type: 'warning',
-            onClick: () => {
-              manageToken(record.id, 'disable', record);
-            },
-          });
-        } else {
-          moreMenuItems.push({
-            node: 'item',
-            name: t('启用'),
-            type: 'secondary',
-            onClick: () => {
-              manageToken(record.id, 'enable', record);
-            },
-          });
-        }
-
         return (
           <Space wrap>
             <SplitButtonGroup
@@ -306,39 +403,34 @@ const TokensTable = () => {
 
             <Button
               theme='light'
-              type='secondary'
+              type='tertiary'
               size="small"
-              onClick={async (text) => {
-                await copyText('sk-' + record.key);
+              onClick={() => {
+                setEditingToken(record);
+                setShowEdit(true);
               }}
             >
-              {t('复制')}
+              {t('编辑')}
             </Button>
 
             <Button
               theme='light'
-              type='tertiary'
+              type='danger'
               size="small"
               onClick={() => {
-                setEditingToken(record);
-                setShowEdit(true);
+                Modal.confirm({
+                  title: t('确定是否要删除此令牌?'),
+                  content: t('此修改将不可逆'),
+                  onOk: () => {
+                    manageToken(record.id, 'delete', record).then(() => {
+                      removeRecord(record.key);
+                    });
+                  },
+                });
               }}
             >
-              {t('编辑')}
+              {t('删除')}
             </Button>
-
-            <Dropdown
-              trigger='click'
-              position='bottomRight'
-              menu={moreMenuItems}
-            >
-              <Button
-                icon={<IconMore />}
-                theme='light'
-                type='tertiary'
-                size="small"
-              />
-            </Dropdown>
           </Space>
         );
       },
@@ -357,6 +449,7 @@ const TokensTable = () => {
     id: undefined,
   });
   const [compactMode, setCompactMode] = useTableCompactMode('tokens');
+  const [showKeys, setShowKeys] = useState({});
 
   // Form 初始值
   const formInitValues = {

+ 14 - 14
web/src/components/table/UsersTable.js

@@ -54,25 +54,25 @@ const UsersTable = () => {
     switch (role) {
       case 1:
         return (
-          <Tag size='large' color='blue' shape='circle' prefixIcon={<User size={14} />}>
+          <Tag color='blue' shape='circle' prefixIcon={<User size={14} />}>
             {t('普通用户')}
           </Tag>
         );
       case 10:
         return (
-          <Tag color='yellow' size='large' shape='circle' prefixIcon={<Shield size={14} />}>
+          <Tag color='yellow' shape='circle' prefixIcon={<Shield size={14} />}>
             {t('管理员')}
           </Tag>
         );
       case 100:
         return (
-          <Tag color='orange' size='large' shape='circle' prefixIcon={<Crown size={14} />}>
+          <Tag color='orange' shape='circle' prefixIcon={<Crown size={14} />}>
             {t('超级管理员')}
           </Tag>
         );
       default:
         return (
-          <Tag color='red' size='large' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='red' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知身份')}
           </Tag>
         );
@@ -82,16 +82,16 @@ const UsersTable = () => {
   const renderStatus = (status) => {
     switch (status) {
       case 1:
-        return <Tag size='large' color='green' shape='circle' prefixIcon={<CheckCircle size={14} />}>{t('已激活')}</Tag>;
+        return <Tag color='green' shape='circle' prefixIcon={<CheckCircle size={14} />}>{t('已激活')}</Tag>;
       case 2:
         return (
-          <Tag size='large' color='red' shape='circle' prefixIcon={<XCircle size={14} />}>
+          <Tag color='red' shape='circle' prefixIcon={<XCircle size={14} />}>
             {t('已封禁')}
           </Tag>
         );
       default:
         return (
-          <Tag size='large' color='grey' shape='circle' prefixIcon={<HelpCircle size={14} />}>
+          <Tag color='grey' shape='circle' prefixIcon={<HelpCircle size={14} />}>
             {t('未知状态')}
           </Tag>
         );
@@ -117,7 +117,7 @@ const UsersTable = () => {
           <Space spacing={2}>
             <span>{text}</span>
             <Tooltip content={remark} position="top" showArrow>
-              <Tag color='white' size='large' shape='circle' className="!text-xs">
+              <Tag color='white' shape='circle' className="!text-xs">
                 <div className="flex items-center gap-1">
                   <div className="w-2 h-2 flex-shrink-0 rounded-full" style={{ backgroundColor: '#10b981' }} />
                   {displayRemark}
@@ -142,13 +142,13 @@ const UsersTable = () => {
         return (
           <div>
             <Space spacing={1}>
-              <Tag color='white' size='large' shape='circle' className="!text-xs" prefixIcon={<Coins size={14} />}>
+              <Tag color='white' shape='circle' className="!text-xs" prefixIcon={<Coins size={14} />}>
                 {t('剩余')}: {renderQuota(record.quota)}
               </Tag>
-              <Tag color='white' size='large' shape='circle' className="!text-xs" prefixIcon={<Coins size={14} />}>
+              <Tag color='white' shape='circle' className="!text-xs" prefixIcon={<Coins size={14} />}>
                 {t('已用')}: {renderQuota(record.used_quota)}
               </Tag>
-              <Tag color='white' size='large' shape='circle' className="!text-xs" prefixIcon={<Activity size={14} />}>
+              <Tag color='white' shape='circle' className="!text-xs" prefixIcon={<Activity size={14} />}>
                 {t('调用')}: {renderNumber(record.request_count)}
               </Tag>
             </Space>
@@ -163,13 +163,13 @@ const UsersTable = () => {
         return (
           <div>
             <Space spacing={1}>
-              <Tag color='white' size='large' shape='circle' className="!text-xs" prefixIcon={<Users size={14} />}>
+              <Tag color='white' shape='circle' className="!text-xs" prefixIcon={<Users size={14} />}>
                 {t('邀请')}: {renderNumber(record.aff_count)}
               </Tag>
-              <Tag color='white' size='large' shape='circle' className="!text-xs" prefixIcon={<DollarSign size={14} />}>
+              <Tag color='white' shape='circle' className="!text-xs" prefixIcon={<DollarSign size={14} />}>
                 {t('收益')}: {renderQuota(record.aff_history_quota)}
               </Tag>
-              <Tag color='white' size='large' shape='circle' className="!text-xs" prefixIcon={<UserPlus size={14} />}>
+              <Tag color='white' shape='circle' className="!text-xs" prefixIcon={<UserPlus size={14} />}>
                 {record.inviter_id === 0 ? t('无邀请人') : `邀请人: ${record.inviter_id}`}
               </Tag>
             </Space>

+ 2 - 3
web/src/helpers/render.js

@@ -539,7 +539,7 @@ export function stringToColor(str) {
 export function renderModelTag(modelName, options = {}) {
   const {
     color,
-    size = 'large',
+    size = 'default',
     shape = 'circle',
     onClick,
     suffixIcon,
@@ -584,7 +584,7 @@ export function renderText(text, limit) {
 export function renderGroup(group) {
   if (group === '') {
     return (
-      <Tag size='large' key='default' color='orange' shape='circle'>
+      <Tag key='default' color='orange' shape='circle'>
         {i18next.t('用户分组')}
       </Tag>
     );
@@ -603,7 +603,6 @@ export function renderGroup(group) {
     <span key={group}>
       {groups.map((group) => (
         <Tag
-          size='large'
           color={tagColors[group] || stringToColor(group)}
           key={group}
           shape='circle'

+ 4 - 0
web/src/i18n/locales/en.json

@@ -373,6 +373,9 @@
   "搜索令牌的名称 ...": "Search for the name of the token...",
   "已用额度": "Quota used",
   "剩余额度": "Remaining quota",
+  "总额度": "Total quota",
+  "智能熔断": "Smart fallback",
+  "当前分组为 auto,会自动选择最优分组,当一个组不可用时自动降级到下一个组(熔断机制)": "The current group is auto, it will automatically select the optimal group, and automatically downgrade to the next group when a group is unavailable (breakage mechanism)",
   "过期时间": "Expiration time",
   "无": "None",
   "无限制": "Unlimited",
@@ -962,6 +965,7 @@
   "启用突发备用号池(建议勾选,极大降低故障率)": "Enable burst backup number pool (it is recommended to check this box to greatly reduce the failure rate)",
   "查看说明": "View instructions",
   "添加令牌": "Create token",
+  "IP限制": "IP restrictions",
   "令牌纬度控制 Midjouney 配置,设置优先级:令牌 > 路径参数 > 系统默认": "Token latitude controls Midjouney configuration, setting priority: token > path parameter > system default",
   "启用速率限制": "Enable rate limiting",
   "复制BaseURL": "Copy BaseURL",

+ 1 - 1
web/src/pages/Detail/index.js

@@ -1381,7 +1381,7 @@ const Detail = (props) => {
                       <div className="flex items-center gap-2">
                         <Bell size={16} />
                         {t('系统公告')}
-                        <Tag size="small" color="grey" shape="circle">
+                        <Tag color="grey" shape="circle">
                           {t('显示最新20条')}
                         </Tag>
                       </div>

+ 12 - 12
web/src/pages/Setup/index.js

@@ -183,7 +183,7 @@ const Setup = () => {
                         title={
                           <div className="flex items-center">
                             <span className="font-medium">{t('数据库警告')}</span>
-                            <Tag color='orange' size='small' className="ml-2 !rounded-full">
+                            <Tag color='orange' shape='circle' className="ml-2">
                               SQLite
                             </Tag>
                           </div>
@@ -222,7 +222,7 @@ const Setup = () => {
                         title={
                           <div className="flex items-center">
                             <span className="font-medium">{t('数据库信息')}</span>
-                            <Tag color='blue' size='small' className="ml-2 !rounded-full">
+                            <Tag color='blue' shape='circle' className="ml-2">
                               MySQL
                             </Tag>
                           </div>
@@ -256,7 +256,7 @@ const Setup = () => {
                         title={
                           <div className="flex items-center">
                             <span className="font-medium">{t('数据库信息')}</span>
-                            <Tag color='green' size='small' className="ml-2 !rounded-full">
+                            <Tag color='green' shape='circle' className="ml-2">
                               PostgreSQL
                             </Tag>
                           </div>
@@ -425,7 +425,7 @@ const Setup = () => {
                               <div className="flex-1">
                                 <div className="font-medium text-gray-900 mb-1">{t('对外运营模式')}</div>
                                 <div className="text-sm text-gray-500">{t('适用于为多个用户提供服务的场景')}</div>
-                                <Tag color='blue' size='small' className="!rounded-full mt-2">
+                                <Tag color='blue' shape='circle' className="mt-2">
                                   {t('默认模式')}
                                 </Tag>
                               </div>
@@ -443,7 +443,7 @@ const Setup = () => {
                               <div className="flex-1">
                                 <div className="font-medium text-gray-900 mb-1">{t('自用模式')}</div>
                                 <div className="text-sm text-gray-500">{t('适用于个人使用的场景,不需要设置模型价格')}</div>
-                                <Tag color='green' size='small' className="!rounded-full mt-2">
+                                <Tag color='green' shape='circle' className="mt-2">
                                   {t('无需计费')}
                                 </Tag>
                               </div>
@@ -461,7 +461,7 @@ const Setup = () => {
                               <div className="flex-1">
                                 <div className="font-medium text-gray-900 mb-1">{t('演示站点模式')}</div>
                                 <div className="text-sm text-gray-500">{t('适用于展示系统功能的场景,提供基础功能演示')}</div>
-                                <Tag color='purple' size='small' className="!rounded-full mt-2">
+                                <Tag color='purple' shape='circle' className="mt-2">
                                   {t('演示体验')}
                                 </Tag>
                               </div>
@@ -522,8 +522,8 @@ const Setup = () => {
                   <p>{t('默认模式,适用于为多个用户提供服务的场景。')}</p>
                   <p>{t('此模式下,系统将计算每次调用的用量,您需要对每个模型都设置价格,如果没有设置价格,用户将无法使用该模型。')}</p>
                   <div className="mt-3">
-                    <Tag color='blue' className="!rounded-full mr-2">{t('计费模式')}</Tag>
-                    <Tag color='blue' className="!rounded-full">{t('多用户支持')}</Tag>
+                    <Tag color='blue' shape='circle' className="mr-2">{t('计费模式')}</Tag>
+                    <Tag color='blue' shape='circle'>{t('多用户支持')}</Tag>
                   </div>
                 </div>
               </div>
@@ -542,8 +542,8 @@ const Setup = () => {
                   <p>{t('适用于个人使用的场景。')}</p>
                   <p>{t('不需要设置模型价格,系统将弱化用量计算,您可专注于使用模型。')}</p>
                   <div className="mt-3">
-                    <Tag color='green' className="!rounded-full mr-2">{t('无需计费')}</Tag>
-                    <Tag color='green' className="!rounded-full">{t('个人使用')}</Tag>
+                    <Tag color='green' shape='circle' className="mr-2">{t('无需计费')}</Tag>
+                    <Tag color='green' shape='circle'>{t('个人使用')}</Tag>
                   </div>
                 </div>
               </div>
@@ -562,8 +562,8 @@ const Setup = () => {
                   <p>{t('适用于展示系统功能的场景。')}</p>
                   <p>{t('提供基础功能演示,方便用户了解系统特性。')}</p>
                   <div className="mt-3">
-                    <Tag color='purple' className="!rounded-full mr-2">{t('功能演示')}</Tag>
-                    <Tag color='purple' className="!rounded-full">{t('体验试用')}</Tag>
+                    <Tag color='purple' shape='circle' className="mr-2">{t('功能演示')}</Tag>
+                    <Tag color='purple' shape='circle'>{t('体验试用')}</Tag>
                   </div>
                 </div>
               </div>