mirror of
https://github.com/linux-do/new-api.git
synced 2025-09-18 16:36:37 +08:00
Merge pull request #245 from Calcium-Ion/pricing-page
feat: add pricing page
This commit is contained in:
commit
acbc3649d6
@ -178,6 +178,13 @@ func GetModelPrice(name string, printErr bool) (float64, bool) {
|
||||
return price, true
|
||||
}
|
||||
|
||||
func GetModelPrices() map[string]float64 {
|
||||
if modelPrice == nil {
|
||||
modelPrice = DefaultModelPrice
|
||||
}
|
||||
return modelPrice
|
||||
}
|
||||
|
||||
func ModelRatio2JSONString() string {
|
||||
if modelRatio == nil {
|
||||
modelRatio = DefaultModelRatio
|
||||
@ -209,6 +216,13 @@ func GetModelRatio(name string) float64 {
|
||||
return ratio
|
||||
}
|
||||
|
||||
func GetModelRatios() map[string]float64 {
|
||||
if modelRatio == nil {
|
||||
modelRatio = DefaultModelRatio
|
||||
}
|
||||
return modelRatio
|
||||
}
|
||||
|
||||
func CompletionRatio2JSONString() string {
|
||||
if CompletionRatio == nil {
|
||||
CompletionRatio = DefaultCompletionRatio
|
||||
@ -282,3 +296,10 @@ func GetCompletionRatio(name string) float64 {
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func GetCompletionRatios() map[string]float64 {
|
||||
if CompletionRatio == nil {
|
||||
CompletionRatio = DefaultCompletionRatio
|
||||
}
|
||||
return CompletionRatio
|
||||
}
|
||||
|
@ -250,3 +250,11 @@ func MapToJsonStr(m map[string]interface{}) string {
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func MapToJsonStrFloat(m map[string]float64) string {
|
||||
bytes, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
@ -18,38 +18,13 @@ import (
|
||||
|
||||
// https://platform.openai.com/docs/api-reference/models/list
|
||||
|
||||
type OpenAIModelPermission struct {
|
||||
Id string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int `json:"created"`
|
||||
AllowCreateEngine bool `json:"allow_create_engine"`
|
||||
AllowSampling bool `json:"allow_sampling"`
|
||||
AllowLogprobs bool `json:"allow_logprobs"`
|
||||
AllowSearchIndices bool `json:"allow_search_indices"`
|
||||
AllowView bool `json:"allow_view"`
|
||||
AllowFineTuning bool `json:"allow_fine_tuning"`
|
||||
Organization string `json:"organization"`
|
||||
Group *string `json:"group"`
|
||||
IsBlocking bool `json:"is_blocking"`
|
||||
}
|
||||
|
||||
type OpenAIModels struct {
|
||||
Id string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int `json:"created"`
|
||||
OwnedBy string `json:"owned_by"`
|
||||
Permission []OpenAIModelPermission `json:"permission"`
|
||||
Root string `json:"root"`
|
||||
Parent *string `json:"parent"`
|
||||
}
|
||||
|
||||
var openAIModels []OpenAIModels
|
||||
var openAIModelsMap map[string]OpenAIModels
|
||||
var openAIModels []dto.OpenAIModels
|
||||
var openAIModelsMap map[string]dto.OpenAIModels
|
||||
var channelId2Models map[int][]string
|
||||
|
||||
func getPermission() []OpenAIModelPermission {
|
||||
var permission []OpenAIModelPermission
|
||||
permission = append(permission, OpenAIModelPermission{
|
||||
func getPermission() []dto.OpenAIModelPermission {
|
||||
var permission []dto.OpenAIModelPermission
|
||||
permission = append(permission, dto.OpenAIModelPermission{
|
||||
Id: "modelperm-LwHkVFn8AcMItP432fKKDIKJ",
|
||||
Object: "model_permission",
|
||||
Created: 1626777600,
|
||||
@ -77,7 +52,7 @@ func init() {
|
||||
channelName := adaptor.GetChannelName()
|
||||
modelNames := adaptor.GetModelList()
|
||||
for _, modelName := range modelNames {
|
||||
openAIModels = append(openAIModels, OpenAIModels{
|
||||
openAIModels = append(openAIModels, dto.OpenAIModels{
|
||||
Id: modelName,
|
||||
Object: "model",
|
||||
Created: 1626777600,
|
||||
@ -89,7 +64,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
for _, modelName := range ai360.ModelList {
|
||||
openAIModels = append(openAIModels, OpenAIModels{
|
||||
openAIModels = append(openAIModels, dto.OpenAIModels{
|
||||
Id: modelName,
|
||||
Object: "model",
|
||||
Created: 1626777600,
|
||||
@ -100,7 +75,7 @@ func init() {
|
||||
})
|
||||
}
|
||||
for _, modelName := range moonshot.ModelList {
|
||||
openAIModels = append(openAIModels, OpenAIModels{
|
||||
openAIModels = append(openAIModels, dto.OpenAIModels{
|
||||
Id: modelName,
|
||||
Object: "model",
|
||||
Created: 1626777600,
|
||||
@ -111,7 +86,7 @@ func init() {
|
||||
})
|
||||
}
|
||||
for _, modelName := range lingyiwanwu.ModelList {
|
||||
openAIModels = append(openAIModels, OpenAIModels{
|
||||
openAIModels = append(openAIModels, dto.OpenAIModels{
|
||||
Id: modelName,
|
||||
Object: "model",
|
||||
Created: 1626777600,
|
||||
@ -122,7 +97,7 @@ func init() {
|
||||
})
|
||||
}
|
||||
for modelName, _ := range constant.MidjourneyModel2Action {
|
||||
openAIModels = append(openAIModels, OpenAIModels{
|
||||
openAIModels = append(openAIModels, dto.OpenAIModels{
|
||||
Id: modelName,
|
||||
Object: "model",
|
||||
Created: 1626777600,
|
||||
@ -132,7 +107,7 @@ func init() {
|
||||
Parent: nil,
|
||||
})
|
||||
}
|
||||
openAIModelsMap = make(map[string]OpenAIModels)
|
||||
openAIModelsMap = make(map[string]dto.OpenAIModels)
|
||||
for _, model := range openAIModels {
|
||||
openAIModelsMap[model.Id] = model
|
||||
}
|
||||
@ -160,17 +135,17 @@ func ListModels(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
models := model.GetGroupModels(user.Group)
|
||||
userOpenAiModels := make([]OpenAIModels, 0)
|
||||
userOpenAiModels := make([]dto.OpenAIModels, 0)
|
||||
permission := getPermission()
|
||||
for _, s := range models {
|
||||
if _, ok := openAIModelsMap[s]; ok {
|
||||
userOpenAiModels = append(userOpenAiModels, openAIModelsMap[s])
|
||||
} else {
|
||||
userOpenAiModels = append(userOpenAiModels, OpenAIModels{
|
||||
userOpenAiModels = append(userOpenAiModels, dto.OpenAIModels{
|
||||
Id: s,
|
||||
Object: "model",
|
||||
Created: 1626777600,
|
||||
OwnedBy: "openai",
|
||||
OwnedBy: "custom",
|
||||
Permission: permission,
|
||||
Root: s,
|
||||
Parent: nil,
|
||||
@ -213,3 +188,18 @@ func RetrieveModel(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func GetPricing(c *gin.Context) {
|
||||
userId := c.GetInt("id")
|
||||
user, _ := model.GetUserById(userId, true)
|
||||
groupRatio := common.GetGroupRatio("default")
|
||||
if user != nil {
|
||||
groupRatio = common.GetGroupRatio(user.Group)
|
||||
}
|
||||
pricing := model.GetPricing(user, openAIModels)
|
||||
c.JSON(200, gin.H{
|
||||
"success": true,
|
||||
"data": pricing,
|
||||
"group_ratio": groupRatio,
|
||||
})
|
||||
}
|
||||
|
37
dto/pricing.go
Normal file
37
dto/pricing.go
Normal file
@ -0,0 +1,37 @@
|
||||
package dto
|
||||
|
||||
type OpenAIModelPermission struct {
|
||||
Id string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int `json:"created"`
|
||||
AllowCreateEngine bool `json:"allow_create_engine"`
|
||||
AllowSampling bool `json:"allow_sampling"`
|
||||
AllowLogprobs bool `json:"allow_logprobs"`
|
||||
AllowSearchIndices bool `json:"allow_search_indices"`
|
||||
AllowView bool `json:"allow_view"`
|
||||
AllowFineTuning bool `json:"allow_fine_tuning"`
|
||||
Organization string `json:"organization"`
|
||||
Group *string `json:"group"`
|
||||
IsBlocking bool `json:"is_blocking"`
|
||||
}
|
||||
|
||||
type OpenAIModels struct {
|
||||
Id string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int `json:"created"`
|
||||
OwnedBy string `json:"owned_by"`
|
||||
Permission []OpenAIModelPermission `json:"permission"`
|
||||
Root string `json:"root"`
|
||||
Parent *string `json:"parent"`
|
||||
}
|
||||
|
||||
type ModelPricing struct {
|
||||
Available bool `json:"available"`
|
||||
ModelName string `json:"model_name"`
|
||||
QuotaType int `json:"quota_type"`
|
||||
ModelRatio float64 `json:"model_ratio"`
|
||||
ModelPrice float64 `json:"model_price"`
|
||||
OwnerBy string `json:"owner_by"`
|
||||
CompletionRatio float64 `json:"completion_ratio"`
|
||||
EnableGroup []string `json:"enable_group,omitempty"`
|
||||
}
|
@ -64,6 +64,17 @@ func authHelper(c *gin.Context, minRole int) {
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func TryUserAuth() func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
id := session.Get("id")
|
||||
if id != nil {
|
||||
c.Set("id", id)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func UserAuth() func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
authHelper(c, common.RoleCommonUser)
|
||||
|
@ -29,6 +29,13 @@ func GetGroupModels(group string) []string {
|
||||
return models
|
||||
}
|
||||
|
||||
func GetEnabledModels() []string {
|
||||
var models []string
|
||||
// Find distinct models
|
||||
DB.Table("abilities").Where("enabled = ?", true).Distinct("model").Pluck("model", &models)
|
||||
return models
|
||||
}
|
||||
|
||||
func getPriority(group string, model string, retry int) (int, error) {
|
||||
groupCol := "`group`"
|
||||
trueVal := "1"
|
||||
|
72
model/pricing.go
Normal file
72
model/pricing.go
Normal file
@ -0,0 +1,72 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"one-api/common"
|
||||
"one-api/dto"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
pricingMap []dto.ModelPricing
|
||||
lastGetPricingTime time.Time
|
||||
updatePricingLock sync.Mutex
|
||||
)
|
||||
|
||||
func GetPricing(user *User, openAIModels []dto.OpenAIModels) []dto.ModelPricing {
|
||||
updatePricingLock.Lock()
|
||||
defer updatePricingLock.Unlock()
|
||||
|
||||
if time.Since(lastGetPricingTime) > time.Minute*1 || len(pricingMap) == 0 {
|
||||
updatePricing(openAIModels)
|
||||
}
|
||||
if user != nil {
|
||||
userPricingMap := make([]dto.ModelPricing, 0)
|
||||
models := GetGroupModels(user.Group)
|
||||
for _, pricing := range pricingMap {
|
||||
if !common.StringsContains(models, pricing.ModelName) {
|
||||
pricing.Available = false
|
||||
}
|
||||
userPricingMap = append(userPricingMap, pricing)
|
||||
}
|
||||
return userPricingMap
|
||||
}
|
||||
return pricingMap
|
||||
}
|
||||
|
||||
func updatePricing(openAIModels []dto.OpenAIModels) {
|
||||
modelRatios := common.GetModelRatios()
|
||||
enabledModels := GetEnabledModels()
|
||||
allModels := make(map[string]string)
|
||||
for _, openAIModel := range openAIModels {
|
||||
if common.StringsContains(enabledModels, openAIModel.Id) {
|
||||
allModels[openAIModel.Id] = openAIModel.OwnedBy
|
||||
}
|
||||
}
|
||||
for model, _ := range modelRatios {
|
||||
if common.StringsContains(enabledModels, model) {
|
||||
if _, ok := allModels[model]; !ok {
|
||||
allModels[model] = "custom"
|
||||
}
|
||||
}
|
||||
}
|
||||
pricingMap = make([]dto.ModelPricing, 0)
|
||||
for model, ownerBy := range allModels {
|
||||
pricing := dto.ModelPricing{
|
||||
Available: true,
|
||||
ModelName: model,
|
||||
OwnerBy: ownerBy,
|
||||
}
|
||||
modelPrice, findPrice := common.GetModelPrice(model, false)
|
||||
if findPrice {
|
||||
pricing.ModelPrice = modelPrice
|
||||
pricing.QuotaType = 1
|
||||
} else {
|
||||
pricing.ModelRatio = common.GetModelRatio(model)
|
||||
pricing.CompletionRatio = common.GetCompletionRatio(model)
|
||||
pricing.QuotaType = 0
|
||||
}
|
||||
pricingMap = append(pricingMap, pricing)
|
||||
}
|
||||
lastGetPricingTime = time.Now()
|
||||
}
|
@ -45,6 +45,7 @@ func logQuotaDataCache(userId int, username string, modelName string, quota int,
|
||||
if ok {
|
||||
quotaData.Count += 1
|
||||
quotaData.Quota += quota
|
||||
quotaData.TokenUsed += tokenUsed
|
||||
} else {
|
||||
quotaData = &QuotaData{
|
||||
UserID: userId,
|
||||
|
@ -20,6 +20,7 @@ func SetApiRouter(router *gin.Engine) {
|
||||
apiRouter.GET("/about", controller.GetAbout)
|
||||
//apiRouter.GET("/midjourney", controller.GetMidjourney)
|
||||
apiRouter.GET("/home_page_content", controller.GetHomePageContent)
|
||||
apiRouter.GET("/pricing", middleware.CriticalRateLimit(), middleware.TryUserAuth(), controller.GetPricing)
|
||||
apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification)
|
||||
apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
|
||||
apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
|
||||
|
@ -22,6 +22,7 @@ import Log from './pages/Log';
|
||||
import Chat from './pages/Chat';
|
||||
import { Layout } from '@douyinfe/semi-ui';
|
||||
import Midjourney from './pages/Midjourney';
|
||||
import Pricing from './pages/Pricing/index.js';
|
||||
// import Detail from './pages/Detail';
|
||||
|
||||
const Home = lazy(() => import('./pages/Home'));
|
||||
@ -219,6 +220,14 @@ function App() {
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/pricing'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>}>
|
||||
<Pricing />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/about'
|
||||
element={
|
||||
|
@ -19,6 +19,7 @@ import TelegramLoginButton from 'react-telegram-login';
|
||||
|
||||
import { IconGithubLogo } from '@douyinfe/semi-icons';
|
||||
import WeChatIcon from './WeChatIcon';
|
||||
import { setUserData } from '../helpers/data.js';
|
||||
|
||||
const LoginForm = () => {
|
||||
const [inputs, setInputs] = useState({
|
||||
@ -99,7 +100,7 @@ const LoginForm = () => {
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
setUserData(data);
|
||||
showSuccess('登录成功!');
|
||||
if (username === 'root' && password === '123456') {
|
||||
Modal.error({
|
||||
|
229
web/src/components/ModelPricing.js
Normal file
229
web/src/components/ModelPricing.js
Normal file
@ -0,0 +1,229 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { API, copy, showError, showSuccess } from '../helpers';
|
||||
|
||||
import { Banner, Layout, Modal, Table, Tag, Tooltip } from '@douyinfe/semi-ui';
|
||||
import { stringToColor } from '../helpers/render.js';
|
||||
import { UserContext } from '../context/User/index.js';
|
||||
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
|
||||
|
||||
function renderQuotaType(type) {
|
||||
// Ensure all cases are string literals by adding quotes.
|
||||
switch (type) {
|
||||
case 1:
|
||||
return (
|
||||
<Tag color='green' size='large'>
|
||||
按次计费
|
||||
</Tag>
|
||||
);
|
||||
case 0:
|
||||
return (
|
||||
<Tag color='blue' size='large'>
|
||||
按量计费
|
||||
</Tag>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Tag color='white' size='large'>
|
||||
未知
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderAvailable(available) {
|
||||
return available ? (
|
||||
<Tag color='green' size='large'>
|
||||
可用
|
||||
</Tag>
|
||||
) : (
|
||||
<Tooltip content='您所在的分组不可用'>
|
||||
<Tag color='red' size='large'>
|
||||
不可用
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
const ModelPricing = () => {
|
||||
const columns = [
|
||||
{
|
||||
title: '可用性',
|
||||
dataIndex: 'available',
|
||||
render: (text, record, index) => {
|
||||
return renderAvailable(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '提供者',
|
||||
dataIndex: 'owner_by',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<>
|
||||
<Tag color={stringToColor(text)} size='large'>
|
||||
{text}
|
||||
</Tag>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '模型名称',
|
||||
dataIndex: 'model_name', // 以finish_time作为dataIndex
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<>
|
||||
<Tag
|
||||
color={stringToColor(record.owner_by)}
|
||||
size='large'
|
||||
onClick={() => {
|
||||
copyText(text);
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</Tag>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '计费类型',
|
||||
dataIndex: 'quota_type',
|
||||
render: (text, record, index) => {
|
||||
return renderQuotaType(parseInt(text));
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '模型倍率',
|
||||
dataIndex: 'model_ratio',
|
||||
render: (text, record, index) => {
|
||||
return <div>{record.quota_type === 0 ? text : 'N/A'}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '补全倍率',
|
||||
dataIndex: 'completion_ratio',
|
||||
render: (text, record, index) => {
|
||||
let ratio = parseFloat(text.toFixed(3));
|
||||
return <div>{record.quota_type === 0 ? ratio : 'N/A'}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '模型价格',
|
||||
dataIndex: 'model_price',
|
||||
render: (text, record, index) => {
|
||||
let content = text;
|
||||
if (record.quota_type === 0) {
|
||||
let inputRatioPrice = record.model_ratio * 2.0 * record.group_ratio;
|
||||
let completionRatioPrice =
|
||||
record.model_ratio *
|
||||
record.completion_ratio *
|
||||
2.0 *
|
||||
record.group_ratio;
|
||||
content = (
|
||||
<>
|
||||
<Text>提示 ${inputRatioPrice} / 1M tokens</Text>
|
||||
<br />
|
||||
<Text>补全 ${completionRatioPrice} / 1M tokens</Text>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
let price = parseFloat(text) * record.group_ratio;
|
||||
content = <>模型价格:${price}</>;
|
||||
}
|
||||
return <div>{content}</div>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const [models, setModels] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [userState, userDispatch] = useContext(UserContext);
|
||||
const [groupRatio, setGroupRatio] = useState(1);
|
||||
|
||||
const setModelsFormat = (models, groupRatio) => {
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
models[i].key = i;
|
||||
models[i].group_ratio = groupRatio;
|
||||
}
|
||||
// sort by quota_type
|
||||
models.sort((a, b) => {
|
||||
return a.quota_type - b.quota_type;
|
||||
});
|
||||
|
||||
// sort by owner_by, openai is max, other use localeCompare
|
||||
models.sort((a, b) => {
|
||||
if (a.owner_by === 'openai') {
|
||||
return -1;
|
||||
} else if (b.owner_by === 'openai') {
|
||||
return 1;
|
||||
} else {
|
||||
return a.owner_by.localeCompare(b.owner_by);
|
||||
}
|
||||
});
|
||||
|
||||
setModels(models);
|
||||
};
|
||||
|
||||
const loadPricing = async () => {
|
||||
setLoading(true);
|
||||
|
||||
let url = '';
|
||||
url = `/api/pricing`;
|
||||
const res = await API.get(url);
|
||||
const { success, message, data, group_ratio } = res.data;
|
||||
if (success) {
|
||||
setGroupRatio(group_ratio);
|
||||
setModelsFormat(data, group_ratio);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
await loadPricing();
|
||||
};
|
||||
|
||||
const copyText = async (text) => {
|
||||
if (await copy(text)) {
|
||||
showSuccess('已复制:' + text);
|
||||
} else {
|
||||
// setSearchKeyword(text);
|
||||
Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refresh().then();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
{userState.user ? (
|
||||
<Banner
|
||||
type='info'
|
||||
description={`您的分组为:${userState.user.group},分组倍率为:${groupRatio}`}
|
||||
/>
|
||||
) : (
|
||||
<Banner
|
||||
type='warning'
|
||||
description={`您还未登陆,显示的价格为默认分组倍率: ${groupRatio}`}
|
||||
/>
|
||||
)}
|
||||
<Table
|
||||
style={{ marginTop: 5 }}
|
||||
columns={columns}
|
||||
dataSource={models}
|
||||
loading={loading}
|
||||
pagination={{
|
||||
pageSize: models.length,
|
||||
showSizeChanger: false,
|
||||
}}
|
||||
/>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelPricing;
|
@ -23,10 +23,12 @@ import {
|
||||
IconImage,
|
||||
IconKey,
|
||||
IconLayers,
|
||||
IconPriceTag,
|
||||
IconSetting,
|
||||
IconUser,
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { Layout, Nav } from '@douyinfe/semi-ui';
|
||||
import { setStatusData } from '../helpers/data.js';
|
||||
|
||||
// HeaderBar Buttons
|
||||
|
||||
@ -55,6 +57,7 @@ const SiderBar = () => {
|
||||
about: '/about',
|
||||
chat: '/chat',
|
||||
detail: '/detail',
|
||||
pricing: '/pricing',
|
||||
};
|
||||
|
||||
const headerButtons = useMemo(
|
||||
@ -100,6 +103,12 @@ const SiderBar = () => {
|
||||
to: '/topup',
|
||||
icon: <IconCreditCard />,
|
||||
},
|
||||
{
|
||||
text: '模型价格',
|
||||
itemKey: 'pricing',
|
||||
to: '/pricing',
|
||||
icon: <IconPriceTag />,
|
||||
},
|
||||
{
|
||||
text: '用户管理',
|
||||
itemKey: 'user',
|
||||
@ -161,34 +170,8 @@ const SiderBar = () => {
|
||||
}
|
||||
const { success, data } = res.data;
|
||||
if (success) {
|
||||
localStorage.setItem('status', JSON.stringify(data));
|
||||
statusDispatch({ type: 'set', payload: data });
|
||||
localStorage.setItem('system_name', data.system_name);
|
||||
localStorage.setItem('logo', data.logo);
|
||||
localStorage.setItem('footer_html', data.footer_html);
|
||||
localStorage.setItem('quota_per_unit', data.quota_per_unit);
|
||||
localStorage.setItem('display_in_currency', data.display_in_currency);
|
||||
localStorage.setItem('enable_drawing', data.enable_drawing);
|
||||
localStorage.setItem('enable_data_export', data.enable_data_export);
|
||||
localStorage.setItem(
|
||||
'data_export_default_time',
|
||||
data.data_export_default_time,
|
||||
);
|
||||
localStorage.setItem(
|
||||
'default_collapse_sidebar',
|
||||
data.default_collapse_sidebar,
|
||||
);
|
||||
localStorage.setItem('mj_notify_enabled', data.mj_notify_enabled);
|
||||
if (data.chat_link) {
|
||||
localStorage.setItem('chat_link', data.chat_link);
|
||||
} else {
|
||||
localStorage.removeItem('chat_link');
|
||||
}
|
||||
if (data.chat_link2) {
|
||||
localStorage.setItem('chat_link2', data.chat_link2);
|
||||
} else {
|
||||
localStorage.removeItem('chat_link2');
|
||||
}
|
||||
setStatusData(data);
|
||||
} else {
|
||||
showError('无法正常连接至服务器!');
|
||||
}
|
||||
|
33
web/src/helpers/data.js
Normal file
33
web/src/helpers/data.js
Normal file
@ -0,0 +1,33 @@
|
||||
export function setStatusData(data) {
|
||||
localStorage.setItem('status', JSON.stringify(data));
|
||||
localStorage.setItem('system_name', data.system_name);
|
||||
localStorage.setItem('logo', data.logo);
|
||||
localStorage.setItem('footer_html', data.footer_html);
|
||||
localStorage.setItem('quota_per_unit', data.quota_per_unit);
|
||||
localStorage.setItem('display_in_currency', data.display_in_currency);
|
||||
localStorage.setItem('enable_drawing', data.enable_drawing);
|
||||
localStorage.setItem('enable_data_export', data.enable_data_export);
|
||||
localStorage.setItem(
|
||||
'data_export_default_time',
|
||||
data.data_export_default_time,
|
||||
);
|
||||
localStorage.setItem(
|
||||
'default_collapse_sidebar',
|
||||
data.default_collapse_sidebar,
|
||||
);
|
||||
localStorage.setItem('mj_notify_enabled', data.mj_notify_enabled);
|
||||
if (data.chat_link) {
|
||||
localStorage.setItem('chat_link', data.chat_link);
|
||||
} else {
|
||||
localStorage.removeItem('chat_link');
|
||||
}
|
||||
if (data.chat_link2) {
|
||||
localStorage.setItem('chat_link2', data.chat_link2);
|
||||
} else {
|
||||
localStorage.removeItem('chat_link2');
|
||||
}
|
||||
}
|
||||
|
||||
export function setUserData(data) {
|
||||
localStorage.setItem('user', JSON.stringify(data));
|
||||
}
|
@ -159,7 +159,7 @@ export function renderModelPrice(
|
||||
<article>
|
||||
<p>提示 ${inputRatioPrice} / 1M tokens</p>
|
||||
<p>补全 ${completionRatioPrice} / 1M tokens</p>
|
||||
<p> </p>
|
||||
<p></p>
|
||||
<p>
|
||||
提示 {inputTokens} tokens / 1M tokens * ${inputRatioPrice} + 补全{' '}
|
||||
{completionTokens} tokens / 1M tokens * ${completionRatioPrice} = $
|
||||
|
10
web/src/pages/Pricing/index.js
Normal file
10
web/src/pages/Pricing/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import ModelPricing from '../../components/ModelPricing.js';
|
||||
|
||||
const Pricing = () => (
|
||||
<>
|
||||
<ModelPricing />
|
||||
</>
|
||||
);
|
||||
|
||||
export default Pricing;
|
Loading…
Reference in New Issue
Block a user