perf: bill framwork

This commit is contained in:
archer 2023-05-21 13:07:40 +08:00
parent e45c1eb1e0
commit 98444fd04b
No known key found for this signature in database
GPG Key ID: 166CA6BF2383B2BB
19 changed files with 68 additions and 78 deletions

View File

@ -2,10 +2,9 @@ import { GET, POST, PUT } from './request';
import { createHashPassword, Obj2Query } from '@/utils/tools'; import { createHashPassword, Obj2Query } from '@/utils/tools';
import { ResLogin, PromotionRecordType } from './response/user'; import { ResLogin, PromotionRecordType } from './response/user';
import { UserAuthTypeEnum } from '@/constants/common'; import { UserAuthTypeEnum } from '@/constants/common';
import { UserType, UserUpdateParams } from '@/types/user'; import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
import type { PagingData, RequestPaging } from '@/types'; import type { PagingData, RequestPaging } from '@/types';
import { BillSchema, PaySchema } from '@/types/mongoSchema'; import { PaySchema } from '@/types/mongoSchema';
import { adaptBill } from '@/utils/adapt';
export const sendAuthCode = ({ export const sendAuthCode = ({
username, username,
@ -69,10 +68,7 @@ export const loginOut = () => GET('/user/loginout');
export const putUserInfo = (data: UserUpdateParams) => PUT('/user/update', data); export const putUserInfo = (data: UserUpdateParams) => PUT('/user/update', data);
export const getUserBills = (data: RequestPaging) => export const getUserBills = (data: RequestPaging) =>
GET<PagingData<BillSchema>>(`/user/getBill?${Obj2Query(data)}`).then((res) => ({ GET<PagingData<UserBillType>>(`/user/getBill?${Obj2Query(data)}`);
...res,
data: res.data.map((bill) => adaptBill(bill))
}));
export const getPayOrders = () => GET<PaySchema[]>(`/user/getPayOrders`); export const getPayOrders = () => GET<PaySchema[]>(`/user/getPayOrders`);

View File

@ -1,8 +1,7 @@
export enum BillTypeEnum { export enum BillTypeEnum {
chat = 'chat', chat = 'chat',
splitData = 'splitData', openapiChat = 'openapiChat',
QA = 'QA', QA = 'QA',
abstract = 'abstract',
vector = 'vector', vector = 'vector',
return = 'return' return = 'return'
} }
@ -14,9 +13,8 @@ export enum PageTypeEnum {
export const BillTypeMap: Record<`${BillTypeEnum}`, string> = { export const BillTypeMap: Record<`${BillTypeEnum}`, string> = {
[BillTypeEnum.chat]: '对话', [BillTypeEnum.chat]: '对话',
[BillTypeEnum.splitData]: 'QA拆分', [BillTypeEnum.openapiChat]: 'api 对话',
[BillTypeEnum.QA]: 'QA拆分', [BillTypeEnum.QA]: 'QA拆分',
[BillTypeEnum.abstract]: '摘要总结',
[BillTypeEnum.vector]: '索引生成', [BillTypeEnum.vector]: '索引生成',
[BillTypeEnum.return]: '退款' [BillTypeEnum.return]: '退款'
}; };

View File

@ -1,18 +0,0 @@
import React, { useState, useCallback, useRef } from 'react';
export const useTabs = ({
tabs = []
}: {
tabs: {
id: string;
label: string;
}[];
}) => {
const [activeTab, setActiveTab] = useState(tabs[0].id);
return {
tabs,
activeTab,
setActiveTab
};
};

View File

@ -9,6 +9,7 @@ import { pushChatBill } from '@/service/events/pushBill';
import { resStreamResponse } from '@/service/utils/chat'; import { resStreamResponse } from '@/service/utils/chat';
import { searchKb } from '@/service/plugins/searchKb'; import { searchKb } from '@/service/plugins/searchKb';
import { ChatRoleEnum } from '@/constants/chat'; import { ChatRoleEnum } from '@/constants/chat';
import { BillTypeEnum } from '@/constants/user';
/* 发送提示词 */ /* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@ -108,7 +109,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
userId, userId,
chatId, chatId,
textLen: finishMessages.map((item) => item.value).join('').length, textLen: finishMessages.map((item) => item.value).join('').length,
tokens: totalTokens tokens: totalTokens,
type: BillTypeEnum.chat
}); });
} catch (err: any) { } catch (err: any) {
if (step === 1) { if (step === 1) {

View File

@ -9,6 +9,7 @@ import { pushChatBill, updateShareChatBill } from '@/service/events/pushBill';
import { resStreamResponse } from '@/service/utils/chat'; import { resStreamResponse } from '@/service/utils/chat';
import { searchKb } from '@/service/plugins/searchKb'; import { searchKb } from '@/service/plugins/searchKb';
import { ChatRoleEnum } from '@/constants/chat'; import { ChatRoleEnum } from '@/constants/chat';
import { BillTypeEnum } from '@/constants/user';
/* 发送提示词 */ /* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@ -98,7 +99,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
chatModel: model.chat.chatModel, chatModel: model.chat.chatModel,
userId, userId,
textLen: finishMessages.map((item) => item.value).join('').length, textLen: finishMessages.map((item) => item.value).join('').length,
tokens: totalTokens tokens: totalTokens,
type: BillTypeEnum.chat
}); });
updateShareChatBill({ updateShareChatBill({
shareId, shareId,

View File

@ -9,6 +9,7 @@ import { pushChatBill } from '@/service/events/pushBill';
import { searchKb } from '@/service/plugins/searchKb'; import { searchKb } from '@/service/plugins/searchKb';
import { ChatRoleEnum } from '@/constants/chat'; import { ChatRoleEnum } from '@/constants/chat';
import { withNextCors } from '@/service/utils/tools'; import { withNextCors } from '@/service/utils/tools';
import { BillTypeEnum } from '@/constants/user';
/* 发送提示词 */ /* 发送提示词 */
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
@ -45,7 +46,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
let startTime = Date.now(); let startTime = Date.now();
/* 凭证校验 */ /* 凭证校验 */
const { userId } = await authUser({ req, authOpenApi: true }); const { userId } = await authUser({ req });
const { model } = await authModel({ const { model } = await authModel({
userId, userId,
@ -74,7 +75,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// search result is empty // search result is empty
if (code === 201) { if (code === 201) {
return res.send(searchPrompts[0]?.value); return isStream
? res.send(searchPrompts[0]?.value)
: jsonRes(res, { data: searchPrompts[0]?.value });
} }
prompts.splice(prompts.length - 3, 0, ...searchPrompts); prompts.splice(prompts.length - 3, 0, ...searchPrompts);
} else { } else {
@ -129,7 +132,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
chatModel: model.chat.chatModel, chatModel: model.chat.chatModel,
userId, userId,
textLen, textLen,
tokens tokens,
type: BillTypeEnum.openapiChat
}); });
} catch (err: any) { } catch (err: any) {
if (step === 1) { if (step === 1) {

View File

@ -15,7 +15,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
} }
// 凭证校验 // 凭证校验
const { userId } = await authUser({ req, authToken: true }); const { userId } = await authUser({ req });
await PgClient.delete('modelData', { await PgClient.delete('modelData', {
where: [['user_id', userId], 'AND', ['id', dataId]] where: [['user_id', userId], 'AND', ['id', dataId]]

View File

@ -15,7 +15,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
} }
// 凭证校验 // 凭证校验
const { userId } = await authUser({ req, authToken: true }); const { userId } = await authUser({ req });
// 更新 pg 内容.仅修改a不需要更新向量。 // 更新 pg 内容.仅修改a不需要更新向量。
await PgClient.update('modelData', { await PgClient.update('modelData', {

View File

@ -22,7 +22,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
} }
await connectToDatabase(); await connectToDatabase();
const { userId } = await authUser({ req, authToken: true }); const { userId } = await authUser({ req });
// 验证是否是该用户的 model // 验证是否是该用户的 model
await authKb({ await authKb({

View File

@ -3,7 +3,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response'; import { jsonRes } from '@/service/response';
import { connectToDatabase, Bill } from '@/service/mongo'; import { connectToDatabase, Bill } from '@/service/mongo';
import { authUser } from '@/service/utils/auth'; import { authUser } from '@/service/utils/auth';
import type { BillSchema } from '@/types/mongoSchema'; import { adaptBill } from '@/utils/adapt';
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
@ -17,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase(); await connectToDatabase();
// 根据 id 获取用户账单 // 根据 id 获取用户账单
const bills = await Bill.find<BillSchema>({ const bills = await Bill.find({
userId userId
}) })
.sort({ _id: -1 }) // 按照创建时间倒序排列 .sort({ _id: -1 }) // 按照创建时间倒序排列
@ -33,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
data: { data: {
pageNum, pageNum,
pageSize, pageSize,
data: bills, data: bills.map(adaptBill),
total total
} }
}); });

View File

@ -5,6 +5,7 @@ import { getUserBills } from '@/api/user';
import type { UserBillType } from '@/types/user'; import type { UserBillType } from '@/types/user';
import { usePagination } from '@/hooks/usePagination'; import { usePagination } from '@/hooks/usePagination';
import { useLoading } from '@/hooks/useLoading'; import { useLoading } from '@/hooks/useLoading';
import dayjs from 'dayjs';
const BillTable = () => { const BillTable = () => {
const { Loading } = useLoading(); const { Loading } = useLoading();
@ -28,6 +29,7 @@ const BillTable = () => {
<Tr> <Tr>
<Th></Th> <Th></Th>
<Th></Th> <Th></Th>
<Th></Th>
<Th></Th> <Th></Th>
<Th>Tokens </Th> <Th>Tokens </Th>
<Th></Th> <Th></Th>
@ -36,8 +38,9 @@ const BillTable = () => {
<Tbody fontSize={'sm'}> <Tbody fontSize={'sm'}>
{bills.map((item) => ( {bills.map((item) => (
<Tr key={item.id}> <Tr key={item.id}>
<Td>{item.time}</Td> <Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
<Td>{BillTypeMap[item.type]}</Td> <Td>{BillTypeMap[item.type] || '-'}</Td>
<Td>{item.modelName}</Td>
<Td>{item.textLen}</Td> <Td>{item.textLen}</Td>
<Td>{item.tokenLen}</Td> <Td>{item.tokenLen}</Td>
<Td>{item.price}</Td> <Td>{item.price}</Td>

View File

@ -9,6 +9,7 @@ import { SplitDataSchema } from '@/types/mongoSchema';
import { modelServiceToolMap } from '../utils/chat'; import { modelServiceToolMap } from '../utils/chat';
import { ChatRoleEnum } from '@/constants/chat'; import { ChatRoleEnum } from '@/constants/chat';
import { getErrText } from '@/utils/tools'; import { getErrText } from '@/utils/tools';
import { BillTypeEnum } from '@/constants/user';
export async function generateQA(next = false): Promise<any> { export async function generateQA(next = false): Promise<any> {
if (process.env.queueTask !== '1') { if (process.env.queueTask !== '1') {
@ -98,7 +99,7 @@ A2:
pushSplitDataBill({ pushSplitDataBill({
isPay: !userOpenAiKey && result.length > 0, isPay: !userOpenAiKey && result.length > 0,
userId: dataItem.userId, userId: dataItem.userId,
type: 'QA', type: BillTypeEnum.QA,
textLen: responseMessages.map((item) => item.value).join('').length, textLen: responseMessages.map((item) => item.value).join('').length,
totalTokens totalTokens
}); });

View File

@ -8,7 +8,8 @@ export const pushChatBill = async ({
userId, userId,
chatId, chatId,
textLen, textLen,
tokens tokens,
type
}: { }: {
isPay: boolean; isPay: boolean;
chatModel: ChatModelType; chatModel: ChatModelType;
@ -16,6 +17,7 @@ export const pushChatBill = async ({
chatId?: '' | string; chatId?: '' | string;
textLen: number; textLen: number;
tokens: number; tokens: number;
type: BillTypeEnum.chat | BillTypeEnum.openapiChat;
}) => { }) => {
console.log(`chat generate success. text len: ${textLen}. token len: ${tokens}. pay:${isPay}`); console.log(`chat generate success. text len: ${textLen}. token len: ${tokens}. pay:${isPay}`);
if (!isPay) return; if (!isPay) return;
@ -33,7 +35,7 @@ export const pushChatBill = async ({
// 插入 Bill 记录 // 插入 Bill 记录
const res = await Bill.create({ const res = await Bill.create({
userId, userId,
type: 'chat', type,
modelName: chatModel, modelName: chatModel,
chatId: chatId ? chatId : undefined, chatId: chatId ? chatId : undefined,
textLen, textLen,
@ -83,7 +85,7 @@ export const pushSplitDataBill = async ({
userId: string; userId: string;
totalTokens: number; totalTokens: number;
textLen: number; textLen: number;
type: `${BillTypeEnum}`; type: BillTypeEnum.QA;
}) => { }) => {
console.log( console.log(
`splitData generate success. text len: ${textLen}. token len: ${totalTokens}. pay:${isPay}` `splitData generate success. text len: ${textLen}. token len: ${totalTokens}. pay:${isPay}`

View File

@ -1,5 +1,5 @@
import { Schema, model, models, Model } from 'mongoose'; import { Schema, model, models, Model } from 'mongoose';
import { ChatModelMap } from '@/constants/model'; import { ChatModelMap, embeddingModel } from '@/constants/model';
import { BillSchema as BillType } from '@/types/mongoSchema'; import { BillSchema as BillType } from '@/types/mongoSchema';
import { BillTypeMap } from '@/constants/user'; import { BillTypeMap } from '@/constants/user';
@ -16,7 +16,7 @@ const BillSchema = new Schema({
}, },
modelName: { modelName: {
type: String, type: String,
enum: [...Object.keys(ChatModelMap), 'text-embedding-ada-002'], enum: [...Object.keys(ChatModelMap), embeddingModel],
required: true required: true
}, },
chatId: { chatId: {

View File

@ -1,35 +1,34 @@
import axios from 'axios'; import axios from 'axios';
{/*Bing 搜索*/} {
/*Bing 搜索*/
}
const BingSearch = async (wait_val: string) => { const BingSearch = async (wait_val: string) => {
const response = await axios.post("newbing中转服务器", { const response = await axios.post('newbing中转服务器', {
prompt: wait_val, prompt: wait_val
}); });
const result = response.data.result; const result = response.data.result;
return result; return result;
}; };
{
{/*google 搜索*/} /*google 搜索*/
}
const GoogleSearch = async (wait_val: string) => { const GoogleSearch = async (wait_val: string) => {
const response = await axios.get('https://www.googleapis.com/customsearch/v1', { const response = await axios.get('https://www.googleapis.com/customsearch/v1', {
params: { params: {
key: google_api, key: process.env.GOOGLE_KEY,
q: wait_val, q: wait_val,
cx: searchEngineId, cx: process.env.searchEngineId,
start: 1, start: 1,
num: 3, num: 3,
dateRestrict: 'm[1]', //搜索结果限定为一个月内 dateRestrict: 'm[1]' //搜索结果限定为一个月内
}, }
}); });
const results = response.data.items; const results = response.data.items;
if (results !== null) { if (results !== null) {
const result = results.map((item: { snippet: string }) => item.snippet).join('\n'); const result = results.map((item: { snippet: string }) => item.snippet).join('\n');
return result; return result;
} }
}
export {
BingSearch,
GoogleSearch
}; };
export { BingSearch, GoogleSearch };

View File

@ -14,13 +14,11 @@ import { hashPassword } from '@/service/utils/tools';
/* uniform auth user */ /* uniform auth user */
export const authUser = async ({ export const authUser = async ({
req, req,
userId = '',
authToken = false, authToken = false,
authOpenApi = false, authOpenApi = false,
authRoot = false authRoot = false
}: { }: {
req: NextApiRequest; req: NextApiRequest;
userId?: string;
authToken?: boolean; authToken?: boolean;
authOpenApi?: boolean; authOpenApi?: boolean;
authRoot?: boolean; authRoot?: boolean;
@ -68,17 +66,18 @@ export const authUser = async ({
return Promise.reject(error); return Promise.reject(error);
} }
}; };
const parseRootKey = async (rootKey?: string) => { const parseRootKey = async (rootKey?: string, userId?: string) => {
if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) { if (!rootKey || !userId || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) {
return Promise.reject(ERROR_ENUM.unAuthorization); return Promise.reject(ERROR_ENUM.unAuthorization);
} }
return userId; return userId;
}; };
const { cookie, apikey, rootkey } = (req.headers || {}) as { const { cookie, apikey, rootkey, userid } = (req.headers || {}) as {
cookie?: string; cookie?: string;
apikey?: string; apikey?: string;
rootkey?: string; rootkey?: string;
userid?: string;
}; };
let uid = ''; let uid = '';
@ -88,13 +87,13 @@ export const authUser = async ({
} else if (authOpenApi) { } else if (authOpenApi) {
uid = await parseOpenApiKey(apikey); uid = await parseOpenApiKey(apikey);
} else if (authRoot) { } else if (authRoot) {
uid = await parseRootKey(rootkey); uid = await parseRootKey(rootkey, userid);
} else if (cookie) { } else if (cookie) {
uid = await parseCookie(cookie); uid = await parseCookie(cookie);
} else if (apikey) { } else if (apikey) {
uid = await parseOpenApiKey(apikey); uid = await parseOpenApiKey(apikey);
} else if (rootkey) { } else if (rootkey) {
uid = await parseRootKey(rootkey); uid = await parseRootKey(rootkey, userid);
} else { } else {
return Promise.reject(ERROR_ENUM.unAuthorization); return Promise.reject(ERROR_ENUM.unAuthorization);
} }

View File

@ -3,9 +3,11 @@ import {
ModelStatusEnum, ModelStatusEnum,
ModelNameEnum, ModelNameEnum,
ModelVectorSearchModeEnum, ModelVectorSearchModeEnum,
ChatModelType ChatModelType,
EmbeddingModelType
} from '@/constants/model'; } from '@/constants/model';
import type { DataType } from './data'; import type { DataType } from './data';
import { BillTypeEnum } from '@/constants/user';
export interface UserModelSchema { export interface UserModelSchema {
_id: string; _id: string;
@ -89,7 +91,8 @@ export interface ChatPopulate extends ChatSchema {
export interface BillSchema { export interface BillSchema {
_id: string; _id: string;
userId: string; userId: string;
type: 'chat' | 'splitData' | 'return'; type: `${BillTypeEnum}`;
modelName: ChatModelType | EmbeddingModelType;
chatId: string; chatId: string;
time: Date; time: Date;
textLen: number; textLen: number;

8
src/types/user.d.ts vendored
View File

@ -1,3 +1,4 @@
import type { BillSchema } from './mongoSchema';
export interface UserType { export interface UserType {
_id: string; _id: string;
username: string; username: string;
@ -17,11 +18,10 @@ export interface UserUpdateParams {
export interface UserBillType { export interface UserBillType {
id: string; id: string;
time: string; time: Date;
type: 'chat' | 'splitData' | 'return'; modelName: BillSchema['modelName'];
type: BillSchema['type'];
textLen: number; textLen: number;
tokenLen: number; tokenLen: number;
userId: string;
chatId: string;
price: number; price: number;
} }

View File

@ -7,9 +7,8 @@ export const adaptBill = (bill: BillSchema): UserBillType => {
return { return {
id: bill._id, id: bill._id,
type: bill.type, type: bill.type,
userId: bill.userId, modelName: bill.modelName,
chatId: bill.chatId, time: bill.time,
time: dayjs(bill.time).format('YYYY/MM/DD HH:mm:ss'),
textLen: bill.textLen, textLen: bill.textLen,
tokenLen: bill.tokenLen, tokenLen: bill.tokenLen,
price: formatPrice(bill.price) price: formatPrice(bill.price)