mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-11 02:43:44 +08:00
feat: add new theme air (#1167)
* chore: add theme air with new-api main branch v0.2.0.3-alpha.1(first step) * feat: 完成渠道界面 * chore: 优化渠道界面样式问题 * feat: 完成兑换码界面 * feat: 完成充值(钱包)界面 * chore: 初代air主题将使用default主题的运营设置界面、系统设置界面、其他设置界面 * feat: 完成日志界面 * feat: 完成用户管理界面 * feat: 完成个人设置界面 * feat: 完成令牌界面 * chore: 优化令牌界面逻辑 * feat: 修改版权信息 * chore: make necessary changes --------- Co-authored-by: Calon <1808837298@qq.com> Co-authored-by: Apple\Apple <zeraturing@foxmail.com> Co-authored-by: JustSong <songquanpeng@foxmail.com>
This commit is contained in:
13
web/air/src/helpers/api.js
Normal file
13
web/air/src/helpers/api.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { showError } from './utils';
|
||||
import axios from 'axios';
|
||||
|
||||
export const API = axios.create({
|
||||
baseURL: process.env.REACT_APP_SERVER ? process.env.REACT_APP_SERVER : '',
|
||||
});
|
||||
|
||||
API.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
showError(error);
|
||||
}
|
||||
);
|
||||
10
web/air/src/helpers/auth-header.js
Normal file
10
web/air/src/helpers/auth-header.js
Normal file
@@ -0,0 +1,10 @@
|
||||
export function authHeader() {
|
||||
// return authorization header with jwt token
|
||||
let user = JSON.parse(localStorage.getItem('user'));
|
||||
|
||||
if (user && user.token) {
|
||||
return { 'Authorization': 'Bearer ' + user.token };
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
3
web/air/src/helpers/history.js
Normal file
3
web/air/src/helpers/history.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
export const history = createBrowserHistory();
|
||||
4
web/air/src/helpers/index.js
Normal file
4
web/air/src/helpers/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './history';
|
||||
export * from './auth-header';
|
||||
export * from './utils';
|
||||
export * from './api';
|
||||
170
web/air/src/helpers/render.js
Normal file
170
web/air/src/helpers/render.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import {Label} from 'semantic-ui-react';
|
||||
import {Tag} from "@douyinfe/semi-ui";
|
||||
|
||||
export function renderText(text, limit) {
|
||||
if (text.length > limit) {
|
||||
return text.slice(0, limit - 3) + '...';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export function renderGroup(group) {
|
||||
if (group === '') {
|
||||
return <Tag size='large'>default</Tag>;
|
||||
}
|
||||
let groups = group.split(',');
|
||||
groups.sort();
|
||||
return <>
|
||||
{groups.map((group) => {
|
||||
if (group === 'vip' || group === 'pro') {
|
||||
return <Tag size='large' color='yellow'>{group}</Tag>;
|
||||
} else if (group === 'svip' || group === 'premium') {
|
||||
return <Tag size='large' color='red'>{group}</Tag>;
|
||||
}
|
||||
if (group === 'default') {
|
||||
return <Tag size='large'>{group}</Tag>;
|
||||
} else {
|
||||
return <Tag size='large' color={stringToColor(group)}>{group}</Tag>;
|
||||
}
|
||||
})}
|
||||
</>;
|
||||
}
|
||||
|
||||
export function renderNumber(num) {
|
||||
if (num >= 1000000000) {
|
||||
return (num / 1000000000).toFixed(1) + 'B';
|
||||
} else if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 10000) {
|
||||
return (num / 1000).toFixed(1) + 'k';
|
||||
} else {
|
||||
return num;
|
||||
}
|
||||
}
|
||||
|
||||
export function renderQuotaNumberWithDigit(num, digits = 2) {
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
num = num.toFixed(digits);
|
||||
if (displayInCurrency) {
|
||||
return '$' + num;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
export function renderNumberWithPoint(num) {
|
||||
num = num.toFixed(2);
|
||||
if (num >= 100000) {
|
||||
// Convert number to string to manipulate it
|
||||
let numStr = num.toString();
|
||||
// Find the position of the decimal point
|
||||
let decimalPointIndex = numStr.indexOf('.');
|
||||
|
||||
let wholePart = numStr;
|
||||
let decimalPart = '';
|
||||
|
||||
// If there is a decimal point, split the number into whole and decimal parts
|
||||
if (decimalPointIndex !== -1) {
|
||||
wholePart = numStr.slice(0, decimalPointIndex);
|
||||
decimalPart = numStr.slice(decimalPointIndex);
|
||||
}
|
||||
|
||||
// Take the first two and last two digits of the whole number part
|
||||
let shortenedWholePart = wholePart.slice(0, 2) + '..' + wholePart.slice(-2);
|
||||
|
||||
// Return the formatted number
|
||||
return shortenedWholePart + decimalPart;
|
||||
}
|
||||
|
||||
// If the number is less than 100,000, return it unmodified
|
||||
return num;
|
||||
}
|
||||
|
||||
export function getQuotaPerUnit() {
|
||||
let quotaPerUnit = localStorage.getItem('quota_per_unit');
|
||||
quotaPerUnit = parseFloat(quotaPerUnit);
|
||||
return quotaPerUnit;
|
||||
}
|
||||
|
||||
export function getQuotaWithUnit(quota, digits = 6) {
|
||||
let quotaPerUnit = localStorage.getItem('quota_per_unit');
|
||||
quotaPerUnit = parseFloat(quotaPerUnit);
|
||||
return (quota / quotaPerUnit).toFixed(digits);
|
||||
}
|
||||
|
||||
export function renderQuota(quota, digits = 2) {
|
||||
let quotaPerUnit = localStorage.getItem('quota_per_unit');
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
quotaPerUnit = parseFloat(quotaPerUnit);
|
||||
displayInCurrency = displayInCurrency === 'true';
|
||||
if (displayInCurrency) {
|
||||
return '$' + (quota / quotaPerUnit).toFixed(digits);
|
||||
}
|
||||
return renderNumber(quota);
|
||||
}
|
||||
|
||||
export function renderQuotaWithPrompt(quota, digits) {
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
displayInCurrency = displayInCurrency === 'true';
|
||||
if (displayInCurrency) {
|
||||
return `(等价金额:${renderQuota(quota, digits)})`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
const colors = ['amber', 'blue', 'cyan', 'green', 'grey', 'indigo',
|
||||
'light-blue', 'lime', 'orange', 'pink',
|
||||
'purple', 'red', 'teal', 'violet', 'yellow'
|
||||
]
|
||||
|
||||
export const modelColorMap = {
|
||||
'dall-e': 'rgb(147,112,219)', // 深紫色
|
||||
'dall-e-2': 'rgb(147,112,219)', // 介于紫色和蓝色之间的色调
|
||||
'dall-e-3': 'rgb(153,50,204)', // 介于紫罗兰和洋红之间的色调
|
||||
'midjourney': 'rgb(136,43,180)', // 介于紫罗兰和洋红之间的色调
|
||||
'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(252,200,149)', // 淡橙色
|
||||
'gpt-3.5-turbo-16k-0613': 'rgb(255,181,119)', // 淡桃色
|
||||
'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)' // 米色
|
||||
}
|
||||
|
||||
export function stringToColor(str) {
|
||||
let sum = 0;
|
||||
// 对字符串中的每个字符进行操作
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
// 将字符的ASCII值加到sum中
|
||||
sum += str.charCodeAt(i);
|
||||
}
|
||||
// 使用模运算得到个位数
|
||||
let i = sum % colors.length;
|
||||
return colors[i];
|
||||
}
|
||||
233
web/air/src/helpers/utils.js
Normal file
233
web/air/src/helpers/utils.js
Normal file
@@ -0,0 +1,233 @@
|
||||
import { Toast } from '@douyinfe/semi-ui';
|
||||
import { toastConstants } from '../constants';
|
||||
import React from 'react';
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
const HTMLToastContent = ({ htmlContent }) => {
|
||||
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
|
||||
};
|
||||
export default HTMLToastContent;
|
||||
export function isAdmin() {
|
||||
let user = localStorage.getItem('user');
|
||||
if (!user) return false;
|
||||
user = JSON.parse(user);
|
||||
return user.role >= 10;
|
||||
}
|
||||
|
||||
export function isRoot() {
|
||||
let user = localStorage.getItem('user');
|
||||
if (!user) return false;
|
||||
user = JSON.parse(user);
|
||||
return user.role >= 100;
|
||||
}
|
||||
|
||||
export function getSystemName() {
|
||||
let system_name = localStorage.getItem('system_name');
|
||||
if (!system_name) return 'New API';
|
||||
return system_name;
|
||||
}
|
||||
|
||||
export function getLogo() {
|
||||
let logo = localStorage.getItem('logo');
|
||||
if (!logo) return '/logo.png';
|
||||
return logo
|
||||
}
|
||||
|
||||
export function getFooterHTML() {
|
||||
return localStorage.getItem('footer_html');
|
||||
}
|
||||
|
||||
export async function copy(text) {
|
||||
let okay = true;
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} catch (e) {
|
||||
okay = false;
|
||||
console.error(e);
|
||||
}
|
||||
return okay;
|
||||
}
|
||||
|
||||
export function isMobile() {
|
||||
return window.innerWidth <= 600;
|
||||
}
|
||||
|
||||
let showErrorOptions = { autoClose: toastConstants.ERROR_TIMEOUT };
|
||||
let showWarningOptions = { autoClose: toastConstants.WARNING_TIMEOUT };
|
||||
let showSuccessOptions = { autoClose: toastConstants.SUCCESS_TIMEOUT };
|
||||
let showInfoOptions = { autoClose: toastConstants.INFO_TIMEOUT };
|
||||
let showNoticeOptions = { autoClose: false };
|
||||
|
||||
if (isMobile()) {
|
||||
showErrorOptions.position = 'top-center';
|
||||
// showErrorOptions.transition = 'flip';
|
||||
|
||||
showSuccessOptions.position = 'top-center';
|
||||
// showSuccessOptions.transition = 'flip';
|
||||
|
||||
showInfoOptions.position = 'top-center';
|
||||
// showInfoOptions.transition = 'flip';
|
||||
|
||||
showNoticeOptions.position = 'top-center';
|
||||
// showNoticeOptions.transition = 'flip';
|
||||
}
|
||||
|
||||
export function showError(error) {
|
||||
console.error(error);
|
||||
if (error.message) {
|
||||
if (error.name === 'AxiosError') {
|
||||
switch (error.response.status) {
|
||||
case 401:
|
||||
// toast.error('错误:未登录或登录已过期,请重新登录!', showErrorOptions);
|
||||
window.location.href = '/login?expired=true';
|
||||
break;
|
||||
case 429:
|
||||
Toast.error('错误:请求次数过多,请稍后再试!');
|
||||
break;
|
||||
case 500:
|
||||
Toast.error('错误:服务器内部错误,请联系管理员!');
|
||||
break;
|
||||
case 405:
|
||||
Toast.info('本站仅作演示之用,无服务端!');
|
||||
break;
|
||||
default:
|
||||
Toast.error('错误:' + error.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Toast.error('错误:' + error.message);
|
||||
} else {
|
||||
Toast.error('错误:' + error);
|
||||
}
|
||||
}
|
||||
|
||||
export function showWarning(message) {
|
||||
Toast.warning(message);
|
||||
}
|
||||
|
||||
export function showSuccess(message) {
|
||||
Toast.success(message);
|
||||
}
|
||||
|
||||
export function showInfo(message) {
|
||||
Toast.info(message);
|
||||
}
|
||||
|
||||
export function showNotice(message, isHTML = false) {
|
||||
if (isHTML) {
|
||||
toast(<HTMLToastContent htmlContent={message} />, showNoticeOptions);
|
||||
} else {
|
||||
Toast.info(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function openPage(url) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
export function removeTrailingSlash(url) {
|
||||
if (url.endsWith('/')) {
|
||||
return url.slice(0, -1);
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
export function timestamp2string(timestamp) {
|
||||
let date = new Date(timestamp * 1000);
|
||||
let year = date.getFullYear().toString();
|
||||
let month = (date.getMonth() + 1).toString();
|
||||
let day = date.getDate().toString();
|
||||
let hour = date.getHours().toString();
|
||||
let minute = date.getMinutes().toString();
|
||||
let second = date.getSeconds().toString();
|
||||
if (month.length === 1) {
|
||||
month = '0' + month;
|
||||
}
|
||||
if (day.length === 1) {
|
||||
day = '0' + day;
|
||||
}
|
||||
if (hour.length === 1) {
|
||||
hour = '0' + hour;
|
||||
}
|
||||
if (minute.length === 1) {
|
||||
minute = '0' + minute;
|
||||
}
|
||||
if (second.length === 1) {
|
||||
second = '0' + second;
|
||||
}
|
||||
return (
|
||||
year +
|
||||
'-' +
|
||||
month +
|
||||
'-' +
|
||||
day +
|
||||
' ' +
|
||||
hour +
|
||||
':' +
|
||||
minute +
|
||||
':' +
|
||||
second
|
||||
);
|
||||
}
|
||||
|
||||
export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
|
||||
let date = new Date(timestamp * 1000);
|
||||
// let year = date.getFullYear().toString();
|
||||
let month = (date.getMonth() + 1).toString();
|
||||
let day = date.getDate().toString();
|
||||
let hour = date.getHours().toString();
|
||||
if (month.length === 1) {
|
||||
month = '0' + month;
|
||||
}
|
||||
if (day.length === 1) {
|
||||
day = '0' + day;
|
||||
}
|
||||
if (hour.length === 1) {
|
||||
hour = '0' + hour;
|
||||
}
|
||||
let str = month + '-' + day
|
||||
if (dataExportDefaultTime === 'hour') {
|
||||
str += ' ' + hour + ":00"
|
||||
} else if (dataExportDefaultTime === 'week') {
|
||||
let nextWeek = new Date(timestamp * 1000 + 6 * 24 * 60 * 60 * 1000);
|
||||
let nextMonth = (nextWeek.getMonth() + 1).toString();
|
||||
let nextDay = nextWeek.getDate().toString();
|
||||
if (nextMonth.length === 1) {
|
||||
nextMonth = '0' + nextMonth;
|
||||
}
|
||||
if (nextDay.length === 1) {
|
||||
nextDay = '0' + nextDay;
|
||||
}
|
||||
str += ' - ' + nextMonth + '-' + nextDay
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function downloadTextAsFile(text, filename) {
|
||||
let blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
|
||||
let url = URL.createObjectURL(blob);
|
||||
let a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
}
|
||||
|
||||
export const verifyJSON = (str) => {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export function shouldShowPrompt(id) {
|
||||
let prompt = localStorage.getItem(`prompt-${id}`);
|
||||
return !prompt;
|
||||
|
||||
}
|
||||
|
||||
export function setPromptShown(id) {
|
||||
localStorage.setItem(`prompt-${id}`, 'true');
|
||||
}
|
||||
Reference in New Issue
Block a user