From 6bba859060babf4fb5683a430d041f5a4aa4d07c Mon Sep 17 00:00:00 2001 From: archer <545436317@qq.com> Date: Sat, 25 Mar 2023 14:43:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E8=AE=A1=E8=B4=B9?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=B8=BAtokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 8 ++++++++ src/components/ScrollData/index.tsx | 15 +++++++++++++-- src/constants/model.ts | 8 ++++---- src/hooks/usePaging.ts | 8 +++++++- src/pages/api/chat/chatGpt.ts | 6 +++--- src/pages/api/chat/gpt3.ts | 9 ++++----- src/pages/api/model/list.ts | 2 ++ src/pages/api/timer/initBill.ts | 15 +++++++++------ src/pages/api/user/getPayCode.ts | 3 ++- src/pages/data/list.tsx | 13 ++++++++++--- src/pages/model/components/CreateModel.tsx | 2 +- src/pages/model/detail.tsx | 10 ++++++++-- src/pages/number/components/PayModal.tsx | 2 +- src/service/events/pushChatBill.ts | 22 +++++++++++++++------- src/service/models/bill.ts | 5 +++++ src/service/models/user.ts | 3 ++- src/utils/user.ts | 3 ++- 18 files changed, 97 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 73d87f068..b7d61f55b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "eventsource-parser": "^0.1.0", "formidable": "^2.1.1", "framer-motion": "^9.0.6", + "gpt-token-utils": "^1.2.0", "hyperdown": "^2.4.29", "immer": "^9.0.19", "jsonwebtoken": "^9.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 711c59987..a9109580f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,7 @@ specifiers: eventsource-parser: ^0.1.0 formidable: ^2.1.1 framer-motion: ^9.0.6 + gpt-token-utils: ^1.2.0 husky: ^8.0.3 hyperdown: ^2.4.29 immer: ^9.0.19 @@ -69,6 +70,7 @@ dependencies: eventsource-parser: registry.npmmirror.com/eventsource-parser/0.1.0 formidable: registry.npmmirror.com/formidable/2.1.1 framer-motion: registry.npmmirror.com/framer-motion/9.0.6_biqbaboplfbrettd7655fr4n2y + gpt-token-utils: registry.npmmirror.com/gpt-token-utils/1.2.0 hyperdown: registry.npmmirror.com/hyperdown/2.4.29 immer: registry.npmmirror.com/immer/9.0.19 jsonwebtoken: registry.npmmirror.com/jsonwebtoken/9.0.0 @@ -6964,6 +6966,12 @@ packages: get-intrinsic: registry.npmmirror.com/get-intrinsic/1.2.0 dev: true + registry.npmmirror.com/gpt-token-utils/1.2.0: + resolution: {integrity: sha512-s8twaU38UE2Vp65JhQEjz8qvWhWY8KZYvmvYHapxlPT03Ok35Clq+gm9eE27wQILdFisseMVRSiC5lJR9GBklA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/gpt-token-utils/-/gpt-token-utils-1.2.0.tgz} + name: gpt-token-utils + version: 1.2.0 + dev: false + registry.npmmirror.com/graceful-fs/4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz} name: graceful-fs diff --git a/src/components/ScrollData/index.tsx b/src/components/ScrollData/index.tsx index e0f9c09ae..9da609787 100644 --- a/src/components/ScrollData/index.tsx +++ b/src/components/ScrollData/index.tsx @@ -2,15 +2,25 @@ import React, { useRef, useEffect, useMemo } from 'react'; import type { BoxProps } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react'; import { throttle } from 'lodash'; +import { useLoading } from '@/hooks/useLoading'; interface Props extends BoxProps { nextPage: () => void; isLoadAll: boolean; requesting: boolean; children: React.ReactNode; + initRequesting?: boolean; } -const ScrollData = ({ children, nextPage, isLoadAll, requesting, ...props }: Props) => { +const ScrollData = ({ + children, + nextPage, + isLoadAll, + requesting, + initRequesting, + ...props +}: Props) => { + const { Loading } = useLoading({ defaultLoading: true }); const elementRef = useRef(null); const loadText = useMemo(() => { if (requesting) return '请求中……'; @@ -43,7 +53,7 @@ const ScrollData = ({ children, nextPage, isLoadAll, requesting, ...props }: Pro }, [elementRef, nextPage]); return ( - + {children} {loadText} + {initRequesting && } ); }; diff --git a/src/constants/model.ts b/src/constants/model.ts index 420e3ab61..2e8c8ae9d 100644 --- a/src/constants/model.ts +++ b/src/constants/model.ts @@ -13,7 +13,7 @@ export type ModelConstantsData = { trainName: string; // 空字符串代表不能训练 maxToken: number; maxTemperature: number; - price: number; // 多少钱 / 1字,单位: 0.00001元 + price: number; // 多少钱 / 1token,单位: 0.00001元 }; export const modelList: ModelConstantsData[] = [ @@ -21,10 +21,10 @@ export const modelList: ModelConstantsData[] = [ serviceCompany: 'openai', name: 'chatGPT', model: ChatModelNameEnum.GPT35, - trainName: 'turbo', + trainName: '', maxToken: 4000, maxTemperature: 2, - price: 5 + price: 3 }, { serviceCompany: 'openai', @@ -33,7 +33,7 @@ export const modelList: ModelConstantsData[] = [ trainName: 'davinci', maxToken: 4000, maxTemperature: 2, - price: 50 + price: 30 } ]; diff --git a/src/hooks/usePaging.ts b/src/hooks/usePaging.ts index d4bec7e81..a0bf464d2 100644 --- a/src/hooks/usePaging.ts +++ b/src/hooks/usePaging.ts @@ -18,11 +18,15 @@ export const usePaging = ({ const [total, setTotal] = useState(0); const [isLoadAll, setIsLoadAll] = useState(false); const [requesting, setRequesting] = useState(false); + const [initRequesting, setInitRequesting] = useState(false); const getData = useCallback( async (num: number, init = false) => { if (requesting) return; if (!init && isLoadAll) return; + if (init) { + setInitRequesting(true); + } setRequesting(true); try { @@ -49,6 +53,7 @@ export const usePaging = ({ } setRequesting(false); + setInitRequesting(false); return null; }, [api, isLoadAll, pageSize, params, requesting, toast] @@ -66,6 +71,7 @@ export const usePaging = ({ getData, requesting, isLoadAll, - nextPage + nextPage, + initRequesting }; }; diff --git a/src/pages/api/chat/chatGpt.ts b/src/pages/api/chat/chatGpt.ts index c09307896..959751f09 100644 --- a/src/pages/api/chat/chatGpt.ts +++ b/src/pages/api/chat/chatGpt.ts @@ -143,15 +143,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) !stream.destroyed && stream.push(null); stream.destroy(); - const promptsLen = formatPrompts.reduce((sum, item) => sum + item.content.length, 0); - console.log(`responseLen: ${responseContent.length}`, `promptLen: ${promptsLen}`); + const promptsContent = formatPrompts.map((item) => item.content).join(''); + console.log(`responseLen: ${responseContent.length}`, `promptLen: ${promptsContent.length}`); // 只有使用平台的 key 才计费 !userApiKey && pushBill({ modelName: model.service.modelName, userId, chatId, - textLen: promptsLen + responseContent.length + text: promptsContent + responseContent }); } catch (err: any) { if (step === 1) { diff --git a/src/pages/api/chat/gpt3.ts b/src/pages/api/chat/gpt3.ts index 4b7ead1cf..d90c3f72e 100644 --- a/src/pages/api/chat/gpt3.ts +++ b/src/pages/api/chat/gpt3.ts @@ -54,21 +54,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } ); - const responseMessage = response.data.choices[0]?.text || ''; + const responseContent = response.data.choices[0]?.text || ''; - const promptsLen = prompt.reduce((sum, item) => sum + item.value.length, 0); - console.log(`responseLen: ${responseMessage.length}`, `promptLen: ${promptsLen}`); + console.log(`responseLen: ${responseContent.length}`, `promptLen: ${formatPrompts.length}`); // 只有使用平台的 key 才计费 !userApiKey && pushBill({ modelName: model.service.modelName, userId, chatId, - textLen: promptsLen + responseMessage.length + text: formatPrompts + responseContent }); jsonRes(res, { - data: responseMessage + data: responseContent }); } catch (err: any) { jsonRes(res, { diff --git a/src/pages/api/model/list.ts b/src/pages/api/model/list.ts index 992869624..006722400 100644 --- a/src/pages/api/model/list.ts +++ b/src/pages/api/model/list.ts @@ -21,6 +21,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< // 根据 userId 获取模型信息 const models = await Model.find({ userId + }).sort({ + _id: -1 }); jsonRes(res, { diff --git a/src/pages/api/timer/initBill.ts b/src/pages/api/timer/initBill.ts index 80659da33..76d90beb9 100644 --- a/src/pages/api/timer/initBill.ts +++ b/src/pages/api/timer/initBill.ts @@ -12,12 +12,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } await connectToDatabase(); - await Bill.updateMany( - {}, - { - type: 'chat', - modelName: 'gpt-3.5-turbo' - } + const bills = await Bill.find({ + tokenLen: { $exists: false } + }); + await Promise.all( + bills.map((bill) => + Bill.findByIdAndUpdate(bill._id, { + tokenLen: bill.textLen + }) + ) ); jsonRes(res, { diff --git a/src/pages/api/user/getPayCode.ts b/src/pages/api/user/getPayCode.ts index 8a5b17fc9..5ade8a67c 100644 --- a/src/pages/api/user/getPayCode.ts +++ b/src/pages/api/user/getPayCode.ts @@ -5,6 +5,7 @@ import axios from 'axios'; import { authToken } from '@/service/utils/tools'; import { customAlphabet } from 'nanoid'; import { connectToDatabase, Pay } from '@/service/mongo'; +import { PRICE_SCALE } from '@/utils/user'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 20); @@ -35,7 +36,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) // 充值记录 + 1 const payOrder = await Pay.create({ userId, - price: amount * 100000, + price: amount * PRICE_SCALE, orderId: id }); diff --git a/src/pages/data/list.tsx b/src/pages/data/list.tsx index 2c45b85e2..be208c7d8 100644 --- a/src/pages/data/list.tsx +++ b/src/pages/data/list.tsx @@ -32,7 +32,8 @@ const DataList = () => { isLoadAll, requesting, data: dataList, - getData + getData, + initRequesting } = usePaging({ api: getDataList, pageSize: 20 @@ -76,7 +77,13 @@ const DataList = () => { {/* 数据表 */} - + @@ -96,7 +103,7 @@ const DataList = () => { defaultValue={item.name} size={'sm'} onBlur={(e) => { - if (!e.target.value) return; + if (!e.target.value || e.target.value === item.name) return; updateDataName(item._id, e.target.value); }} /> diff --git a/src/pages/model/components/CreateModel.tsx b/src/pages/model/components/CreateModel.tsx index 27d6c98bb..e8b5c5a73 100644 --- a/src/pages/model/components/CreateModel.tsx +++ b/src/pages/model/components/CreateModel.tsx @@ -112,7 +112,7 @@ const CreateModel = ({ {formatPrice( modelList.find((item) => item.model === getValues('serviceModelName'))?.price || 0 ) * 1000} - 元/1000字(包括上下文和标点符号) + 元/1K tokens(包括上下文和标点符号) diff --git a/src/pages/model/detail.tsx b/src/pages/model/detail.tsx index bcee04a5f..b0cfed723 100644 --- a/src/pages/model/detail.tsx +++ b/src/pages/model/detail.tsx @@ -195,7 +195,7 @@ const ModelDetail = ({ modelId }: { modelId: string }) => { return () => { window.onbeforeunload = null; }; - }, []); + }, [router]); return ( <> @@ -246,7 +246,13 @@ const ModelDetail = ({ modelId }: { modelId: string }) => { - {!!model && } + + {canTrain && ( + + + + )} + 神奇操作 diff --git a/src/pages/number/components/PayModal.tsx b/src/pages/number/components/PayModal.tsx index edd7a7f0a..8fc659d51 100644 --- a/src/pages/number/components/PayModal.tsx +++ b/src/pages/number/components/PayModal.tsx @@ -100,7 +100,7 @@ const PayModal = ({ onClose }: { onClose: () => void }) => { - + diff --git a/src/service/events/pushChatBill.ts b/src/service/events/pushChatBill.ts index 5338e2947..d99966e15 100644 --- a/src/service/events/pushChatBill.ts +++ b/src/service/events/pushChatBill.ts @@ -1,41 +1,49 @@ import { connectToDatabase, Bill, User } from '../mongo'; import { modelList } from '@/constants/model'; +import { encode } from 'gpt-token-utils'; +import { formatPrice } from '@/utils/user'; export const pushBill = async ({ modelName, userId, chatId, - textLen + text }: { modelName: string; userId: string; chatId: string; - textLen: number; + text: string; }) => { await connectToDatabase(); let billId; try { + // 获取模型单价格 const modelItem = modelList.find((item) => item.model === modelName); + const unitPrice = modelItem?.price || 5; - if (!modelItem) return; + // 计算 token 数量 + const tokens = encode(text); - const price = modelItem.price * textLen; + // 计算价格 + const price = unitPrice * tokens.length; + console.log('token len:', tokens.length, 'price: ', `${formatPrice(price)}元`); try { // 插入 Bill 记录 const res = await Bill.create({ userId, type: 'chat', - modelName: modelItem.model, + modelName, chatId, - textLen, + textLen: text.length, + tokenLen: tokens.length, price }); billId = res._id; - // 扣费 + // 账号扣费 await User.findByIdAndUpdate(userId, { $inc: { balance: -price } }); diff --git a/src/service/models/bill.ts b/src/service/models/bill.ts index 06a88da34..7392e8d84 100644 --- a/src/service/models/bill.ts +++ b/src/service/models/bill.ts @@ -31,6 +31,11 @@ const BillSchema = new Schema({ type: Number, required: true }, + tokenLen: { + // 折算成 token 的数量 + type: Number, + required: true + }, price: { type: Number, required: true diff --git a/src/service/models/user.ts b/src/service/models/user.ts index b54e5fa94..ae097d85f 100644 --- a/src/service/models/user.ts +++ b/src/service/models/user.ts @@ -1,5 +1,6 @@ import { Schema, model, models } from 'mongoose'; import { hashPassword } from '@/service/utils/tools'; +import { PRICE_SCALE } from '@/utils/user'; const UserSchema = new Schema({ email: { @@ -16,7 +17,7 @@ const UserSchema = new Schema({ }, balance: { type: Number, - default: 0.5 * 100000 + default: 0.5 * PRICE_SCALE }, accounts: [ { diff --git a/src/utils/user.ts b/src/utils/user.ts index f7749d0db..b972e976a 100644 --- a/src/utils/user.ts +++ b/src/utils/user.ts @@ -1,4 +1,5 @@ const tokenKey = 'fast-gpt-token'; +export const PRICE_SCALE = 100000; export const setToken = (val: string) => { localStorage.setItem(tokenKey, val); @@ -14,5 +15,5 @@ export const clearToken = () => { * 把数据库读取到的price,转化成元 */ export const formatPrice = (val: number) => { - return val / 100000; + return val / PRICE_SCALE; };
模型类型价格(元/1000字符,包含所有上下文)价格(元/1K tokens,包含所有上下文)