🚀 feat: Introduce full Model & Vendor Management suite (backend + frontend) and UI refinements
Backend • Add `model/model_meta.go` and `model/vendor_meta.go` defining Model & Vendor entities with CRUD helpers, soft-delete and time stamps • Create corresponding controllers `controller/model_meta.go`, `controller/vendor_meta.go` and register routes in `router/api-router.go` • Auto-migrate new tables in DB startup logic Frontend • Build complete “Model Management” module under `/console/models` - New pages, tables, filters, actions, hooks (`useModelsData`) and dynamic vendor tabs - Modals `EditModelModal.jsx` & unified `EditVendorModal.jsx`; latter now uses default confirm/cancel footer and mobile-friendly modal sizing (`full-width` / `small`) via `useIsMobile` • Update sidebar (`SiderBar.js`) and routing (`App.js`) to surface the feature • Add helper updates (`render.js`) incl. `stringToColor`, dynamic LobeHub icon retrieval, and tag color palettes Table UX improvements • Replace separate status column with inline Enable / Disable buttons in operation column (matching channel table style) • Limit visible tags to max 3; overflow represented as “+x” tag with padded `Popover` showing remaining tags • Color all tags deterministically using `stringToColor` for consistent theming • Change vendor column tag color to white for better contrast Misc • Minor layout tweaks, compact-mode toggle relocation, lint fixes and TypeScript/ESLint clean-up These changes collectively deliver end-to-end model & vendor administration while unifying visual language across management tables.
This commit is contained in:
@@ -18,10 +18,11 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import i18next from 'i18next';
|
||||
import { Modal, Tag, Typography } from '@douyinfe/semi-ui';
|
||||
import { Modal, Tag, Typography, Avatar } from '@douyinfe/semi-ui';
|
||||
import { copy, showSuccess } from './utils';
|
||||
import { MOBILE_BREAKPOINT } from '../hooks/common/useIsMobile.js';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import * as LobeIcons from '@lobehub/icons';
|
||||
import {
|
||||
OpenAI,
|
||||
Claude,
|
||||
@@ -85,6 +86,7 @@ export const sidebarIconColors = {
|
||||
gift: '#F43F5E', // 玫红色
|
||||
user: '#10B981', // 绿色
|
||||
settings: '#F97316', // 橙色
|
||||
models: '#10B981', // 绿色
|
||||
};
|
||||
|
||||
// 获取侧边栏Lucide图标组件
|
||||
@@ -177,6 +179,13 @@ export function getLucideIcon(key, selected = false) {
|
||||
color={selected ? sidebarIconColors.user : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'models':
|
||||
return (
|
||||
<Layers
|
||||
{...commonProps}
|
||||
color={selected ? sidebarIconColors.models : 'currentColor'}
|
||||
/>
|
||||
);
|
||||
case 'setting':
|
||||
return (
|
||||
<Settings
|
||||
@@ -422,6 +431,37 @@ export function getChannelIcon(channelType) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据图标名称动态获取 LobeHub 图标组件
|
||||
* @param {string} iconName - 图标名称
|
||||
* @param {number} size - 图标大小,默认为 14
|
||||
* @returns {JSX.Element} - 对应的图标组件或 Avatar
|
||||
*/
|
||||
export function getLobeHubIcon(iconName, size = 14) {
|
||||
if (typeof iconName === 'string') iconName = iconName.trim();
|
||||
// 如果没有图标名称,返回 Avatar
|
||||
if (!iconName) {
|
||||
return <Avatar size="extra-extra-small">?</Avatar>;
|
||||
}
|
||||
|
||||
let IconComponent;
|
||||
|
||||
if (iconName.includes('.')) {
|
||||
const [base, variant] = iconName.split('.');
|
||||
const BaseIcon = LobeIcons[base];
|
||||
IconComponent = BaseIcon ? BaseIcon[variant] : undefined;
|
||||
} else {
|
||||
IconComponent = LobeIcons[iconName];
|
||||
}
|
||||
|
||||
if (IconComponent && (typeof IconComponent === 'function' || typeof IconComponent === 'object')) {
|
||||
return <IconComponent size={size} />;
|
||||
}
|
||||
|
||||
const firstLetter = iconName.charAt(0).toUpperCase();
|
||||
return <Avatar size="extra-extra-small">{firstLetter}</Avatar>;
|
||||
}
|
||||
|
||||
// 颜色列表
|
||||
const colors = [
|
||||
'amber',
|
||||
@@ -891,13 +931,13 @@ export function renderQuota(quota, digits = 2) {
|
||||
if (displayInCurrency) {
|
||||
const result = quota / quotaPerUnit;
|
||||
const fixedResult = result.toFixed(digits);
|
||||
|
||||
|
||||
// 如果 toFixed 后结果为 0 但原始值不为 0,显示最小值
|
||||
if (parseFloat(fixedResult) === 0 && quota > 0 && result > 0) {
|
||||
const minValue = Math.pow(10, -digits);
|
||||
return '$' + minValue.toFixed(digits);
|
||||
}
|
||||
|
||||
|
||||
return '$' + fixedResult;
|
||||
}
|
||||
return renderNumber(quota);
|
||||
|
||||
Reference in New Issue
Block a user