Просмотр исходного кода

refactor: update group handling and rendering logic

- Changed the structure of usableGroups in GetUserGroups to store additional information (ratio and description) for each group.
- Introduced a new renderRatio function to visually represent group ratios with color coding.
- Updated the Playground and EditToken components to utilize the new group structure and rendering options.
- Enhanced the renderGroupOption function for better UI representation of group options.
- Fixed minor comments and improved code readability.
CalciumIon 1 год назад
Родитель
Сommit
d13d81baba

+ 7 - 4
controller/group.go

@@ -20,15 +20,18 @@ func GetGroups(c *gin.Context) {
 }
 
 func GetUserGroups(c *gin.Context) {
-	usableGroups := make(map[string]string)
+	usableGroups := make(map[string]map[string]interface{})
 	userGroup := ""
 	userId := c.GetInt("id")
 	userGroup, _ = model.GetUserGroup(userId, false)
-	for groupName, _ := range setting.GetGroupRatioCopy() {
+	for groupName, ratio := range setting.GetGroupRatioCopy() {
 		// UserUsableGroups contains the groups that the user can use
 		userUsableGroups := setting.GetUserUsableGroups(userGroup)
-		if _, ok := userUsableGroups[groupName]; ok {
-			usableGroups[groupName] = userUsableGroups[groupName]
+		if desc, ok := userUsableGroups[groupName]; ok {
+			usableGroups[groupName] = map[string]interface{}{
+				"ratio": ratio,
+				"desc":  desc,
+			}
 		}
 	}
 	c.JSON(http.StatusOK, gin.H{

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

@@ -1,5 +1,5 @@
 import i18next from 'i18next';
-import { Modal, Tag } from '@douyinfe/semi-ui';
+import { Modal, Tag, Typography } from '@douyinfe/semi-ui';
 import { copy, showSuccess } from './utils.js';
 
 export function renderText(text, limit) {
@@ -55,6 +55,81 @@ export function renderGroup(group) {
   );
 }
 
+export function renderRatio(ratio) {
+  let color = 'green';
+  if (ratio > 5) {
+    color = 'red';
+  } else if (ratio > 3) {
+    color = 'orange';
+  } else if (ratio > 1) {
+    color = 'blue';
+  }
+  return <Tag color={color}>{ratio} {i18next.t('倍率')}</Tag>;
+}
+
+export const renderGroupOption = (item) => {
+  const {
+    disabled,
+    selected,
+    label,
+    value,
+    focused,
+    className,
+    style,
+    onMouseEnter,
+    onClick,
+    empty,
+    emptyContent,
+    ...rest
+  } = item;
+  
+  const baseStyle = {
+    display: 'flex', 
+    justifyContent: 'space-between', 
+    alignItems: 'center', 
+    padding: '8px 16px',
+    cursor: disabled ? 'not-allowed' : 'pointer',
+    backgroundColor: focused ? 'var(--semi-color-fill-0)' : 'transparent',
+    opacity: disabled ? 0.5 : 1,
+    ...(selected && {
+      backgroundColor: 'var(--semi-color-primary-light-default)',
+    }),
+    '&:hover': {
+      backgroundColor: !disabled && 'var(--semi-color-fill-1)'
+    }
+  };
+
+  const handleClick = () => {
+    if (!disabled && onClick) {
+      onClick();
+    }
+  };
+
+  const handleMouseEnter = (e) => {
+    if (!disabled && onMouseEnter) {
+      onMouseEnter(e);
+    }
+  };
+  
+  return (
+    <div 
+      style={baseStyle}
+      onClick={handleClick}
+      onMouseEnter={handleMouseEnter}
+    >
+      <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
+        <Typography.Text strong type={disabled ? 'tertiary' : undefined}>
+          {value}
+        </Typography.Text>
+        <Typography.Text type="secondary" size="small">
+          {label}
+        </Typography.Text>
+      </div>
+      {item.ratio && renderRatio(item.ratio)}
+    </div>
+  );
+};
+
 export function renderNumber(num) {
   if (num >= 1000000000) {
     return (num / 1000000000).toFixed(1) + 'B';
@@ -352,7 +427,7 @@ export const modelColorMap = {
   '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-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)', // 钢蓝色
@@ -375,7 +450,7 @@ export const modelColorMap = {
   '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相同,表示同一类功能)
+  '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)', // 金色

+ 9 - 9
web/src/pages/Playground/Playground.js

@@ -2,11 +2,12 @@ import React, { useCallback, useContext, useEffect, useState } from 'react';
 import { useNavigate, useSearchParams } from 'react-router-dom';
 import { UserContext } from '../../context/User/index.js';
 import { API, getUserIdFromLocalStorage, showError } from '../../helpers/index.js';
-import { Card, Chat, Input, Layout, Select, Slider, TextArea, Typography, Button } from '@douyinfe/semi-ui';
+import { Card, Chat, Input, Layout, Select, Slider, TextArea, Typography, Button, Highlight } from '@douyinfe/semi-ui';
 import { SSE } from 'sse';
 import { IconSetting } from '@douyinfe/semi-icons';
 import { StyleContext } from '../../context/Style/index.js';
 import { useTranslation } from 'react-i18next';
+import { renderGroupOption } from '../../helpers/render.js';
 
 const roleInfo = {
   user: {
@@ -97,15 +98,17 @@ const Playground = () => {
     let res = await API.get(`/api/user/self/groups`);
     const { success, message, data } = res.data;
     if (success) {
-      let localGroupOptions = Object.keys(data).map((group) => ({
-        label: data[group],
+      let localGroupOptions = Object.entries(data).map(([group, info]) => ({
+        label: info.desc,
         value: group,
+        ratio: info.ratio
       }));
 
       if (localGroupOptions.length === 0) {
         localGroupOptions = [{
           label: t('用户分组'),
           value: '',
+          ratio: 1
         }];
       } else {
         const localUser = JSON.parse(localStorage.getItem('user'));
@@ -326,12 +329,9 @@ const Playground = () => {
               }}
               value={inputs.group}
               autoComplete='new-password'
-              optionList={groups.map((group) => ({
-                ...group,
-                label: styleState.isMobile && group.label.length > 16
-                  ? group.label.substring(0, 16) + '...'
-                  : group.label,
-              }))}
+              optionList={groups}
+              renderOptionItem={renderGroupOption}
+              style={{ width: '100%' }}
             />
             <div style={{ marginTop: 10 }}>
               <Typography.Text strong>{t('模型')}:</Typography.Text>

+ 6 - 3
web/src/pages/Token/EditToken.js

@@ -7,7 +7,7 @@ import {
   showSuccess,
   timestamp2string,
 } from '../../helpers';
-import { renderQuotaWithPrompt } from '../../helpers/render';
+import { renderGroupOption, renderQuotaWithPrompt } from '../../helpers/render';
 import {
   AutoComplete,
   Banner,
@@ -97,9 +97,10 @@ const EditToken = (props) => {
     let res = await API.get(`/api/user/self/groups`);
     const { success, message, data } = res.data;
     if (success) {
-      let localGroupOptions = Object.keys(data).map((group) => ({
-        label: data[group],
+      let localGroupOptions = Object.entries(data).map(([group, info]) => ({
+        label: info.desc,
         value: group,
+        ratio: info.ratio
       }));
       setGroups(localGroupOptions);
     } else {
@@ -449,6 +450,8 @@ const EditToken = (props) => {
               onChange={(value) => {
                 handleInputChange('group', value);
               }}
+              position={'topLeft'}
+              renderOptionItem={renderGroupOption}
               value={inputs.group}
               autoComplete='new-password'
               optionList={groups}