Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
75cf3d1e
authored
Mar 25, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 拆分文本账单结算
parent
02cee35a
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
135 additions
and
33 deletions
+135
-33
src/constants/user.ts
+11
-0
src/pages/api/chat/chatGpt.ts
+4
-3
src/pages/api/chat/gpt3.ts
+2
-2
src/pages/api/data/delData.ts
+11
-1
src/pages/api/data/getDataItems.ts
+1
-1
src/pages/api/data/getDataList.ts
+1
-1
src/pages/api/user/getBill.ts
+1
-1
src/pages/data/components/ImportDataModal.tsx
+32
-10
src/pages/data/list.tsx
+5
-1
src/pages/number/setting.tsx
+3
-0
src/service/events/generateQA.ts
+10
-8
src/service/events/pushBill.ts
+49
-2
src/service/models/bill.ts
+2
-3
src/types/mongoSchema.d.ts
+1
-0
src/types/user.d.ts
+1
-0
src/utils/adapt.ts
+1
-0
No files found.
src/constants/user.ts
View file @
75cf3d1e
export
enum
BillTypeEnum
{
chat
=
'chat'
,
splitData
=
'splitData'
,
return
=
'return'
}
export
enum
PageTypeEnum
{
login
=
'login'
,
register
=
'register'
,
forgetPassword
=
'forgetPassword'
}
export
const
BillTypeMap
:
Record
<
`
${
BillTypeEnum
}
`
,
string
>
=
{
[
BillTypeEnum
.
chat
]:
'对话'
,
[
BillTypeEnum
.
splitData
]:
'文本拆分'
,
[
BillTypeEnum
.
return
]:
'退款'
};
src/pages/api/chat/chatGpt.ts
View file @
75cf3d1e
...
...
@@ -9,7 +9,7 @@ import { jsonRes } from '@/service/response';
import
type
{
ModelSchema
}
from
'@/types/mongoSchema'
;
import
{
PassThrough
}
from
'stream'
;
import
{
modelList
}
from
'@/constants/model'
;
import
{
push
Bill
}
from
'@/service/events/pushChat
Bill'
;
import
{
push
ChatBill
}
from
'@/service/events/push
Bill'
;
/* 发送提示词 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
...
...
@@ -91,7 +91,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
messages
:
formatPrompts
,
frequency_penalty
:
0.5
,
// 越大,重复内容越少
presence_penalty
:
-
0.5
,
// 越大,越容易出现新内容
stream
:
true
stream
:
true
,
stop
:
[
'。!?.!.'
]
},
{
timeout
:
40000
,
...
...
@@ -149,7 +150,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const
promptsContent
=
formatPrompts
.
map
((
item
)
=>
item
.
content
).
join
(
''
);
// 只有使用平台的 key 才计费
!
userApiKey
&&
pushBill
({
push
Chat
Bill
({
modelName
:
model
.
service
.
modelName
,
userId
,
chatId
,
...
...
src/pages/api/chat/gpt3.ts
View file @
75cf3d1e
...
...
@@ -8,7 +8,7 @@ import { jsonRes } from '@/service/response';
import
type
{
ModelSchema
}
from
'@/types/mongoSchema'
;
import
{
PassThrough
}
from
'stream'
;
import
{
modelList
}
from
'@/constants/model'
;
import
{
push
Bill
}
from
'@/service/events/pushChat
Bill'
;
import
{
push
ChatBill
}
from
'@/service/events/push
Bill'
;
/* 发送提示词 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
...
...
@@ -142,7 +142,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 只有使用平台的 key 才计费
!
userApiKey
&&
pushBill
({
push
Chat
Bill
({
modelName
:
model
.
service
.
modelName
,
userId
,
chatId
,
...
...
src/pages/api/data/delData.ts
View file @
75cf3d1e
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
{
jsonRes
}
from
'@/service/response'
;
import
{
connectToDatabase
,
Data
}
from
'@/service/mongo'
;
import
{
connectToDatabase
,
Data
,
DataItem
}
from
'@/service/mongo'
;
import
{
authToken
}
from
'@/service/utils/tools'
;
import
type
{
DataListItem
}
from
'@/types/data'
;
import
type
{
PagingData
}
from
'@/types'
;
...
...
@@ -27,6 +27,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
isDeleted
:
true
});
// 改变 dataItem 状态为 0
await
DataItem
.
updateMany
(
{
dataId
},
{
status
:
0
}
);
jsonRes
<
PagingData
<
DataListItem
>>
(
res
);
}
catch
(
err
)
{
jsonRes
(
res
,
{
...
...
src/pages/api/data/getDataItems.ts
View file @
75cf3d1e
...
...
@@ -26,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
dataId
,
status
:
0
})
.
sort
({
time
:
-
1
})
// 按照创建时间倒序排列
.
sort
({
_id
:
-
1
})
// 按照创建时间倒序排列
.
skip
((
pageNum
-
1
)
*
pageSize
)
.
limit
(
pageSize
);
...
...
src/pages/api/data/getDataList.ts
View file @
75cf3d1e
...
...
@@ -58,7 +58,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
$filter
:
{
input
:
'$items'
,
as
:
'item'
,
cond
:
{
$
eq
:
[
'$$item.status'
,
1
]
}
// 统计status为1
的数量
cond
:
{
$
ne
:
[
'$$item.status'
,
0
]
}
// 统计 status 不为0
的数量
}
}
}
...
...
src/pages/api/user/getBill.ts
View file @
75cf3d1e
...
...
@@ -25,7 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const
bills
=
await
Bill
.
find
<
BillSchema
>
({
userId
})
.
sort
({
time
:
-
1
})
// 按照创建时间倒序排列
.
sort
({
_id
:
-
1
})
// 按照创建时间倒序排列
.
skip
((
pageNum
-
1
)
*
pageSize
)
.
limit
(
pageSize
);
...
...
src/pages/data/components/ImportDataModal.tsx
View file @
75cf3d1e
...
...
@@ -8,7 +8,6 @@ import {
ModalBody
,
ModalCloseButton
,
Button
,
Input
,
Box
,
Flex
,
Textarea
...
...
@@ -21,10 +20,20 @@ import { postSplitData } from '@/api/data';
import
{
useMutation
}
from
'@tanstack/react-query'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useLoading
}
from
'@/hooks/useLoading'
;
import
{
formatPrice
}
from
'@/utils/user'
;
import
{
modelList
,
ChatModelNameEnum
}
from
'@/constants/model'
;
const
fileExtension
=
'.txt,.doc,.docx,.pdf,.md'
;
const
ImportDataModal
=
({
dataId
,
onClose
}:
{
dataId
:
string
;
onClose
:
()
=>
void
})
=>
{
const
ImportDataModal
=
({
dataId
,
onClose
,
onSuccess
}:
{
dataId
:
string
;
onClose
:
()
=>
void
;
onSuccess
:
()
=>
void
;
})
=>
{
const
{
openConfirm
,
ConfirmChild
}
=
useConfirm
({
content
:
'确认提交生成任务?该任务无法终止!'
});
...
...
@@ -60,6 +69,7 @@ const ImportDataModal = ({ dataId, onClose }: { dataId: string; onClose: () => v
status
:
'success'
});
onClose
();
onSuccess
();
},
onError
(
err
:
any
)
{
toast
({
...
...
@@ -110,7 +120,16 @@ const ImportDataModal = ({ dataId, onClose }: { dataId: string; onClose: () => v
<
Modal
isOpen=
{
true
}
onClose=
{
onClose
}
>
<
ModalOverlay
/>
<
ModalContent
position=
{
'relative'
}
maxW=
{
[
'90vw'
,
'800px'
]
}
>
<
ModalHeader
>
导入数据,生成QA
</
ModalHeader
>
<
ModalHeader
>
导入数据,生成QA
<
Box
ml=
{
2
}
as=
{
'span'
}
fontSize=
{
'sm'
}
color=
{
'blackAlpha.600'
}
>
{
formatPrice
(
modelList
.
find
((
item
)
=>
item
.
model
===
ChatModelNameEnum
.
GPT35
)?.
price
||
0
,
1000
)
}
元/1K tokens
</
Box
>
</
ModalHeader
>
<
ModalCloseButton
/>
<
ModalBody
display=
{
'flex'
}
>
...
...
@@ -132,13 +151,16 @@ const ImportDataModal = ({ dataId, onClose }: { dataId: string; onClose: () => v
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
ml=
{
3
}
minH=
{
'200px'
}
>
{
activeTab
===
'text'
&&
(
<
Textarea
h=
{
'100%'
}
maxLength=
{
-
1
}
value=
{
textInput
}
placeholder=
{
'请粘贴或输入需要处理的文本'
}
onChange=
{
(
e
)
=>
setTextInput
(
e
.
target
.
value
)
}
/>
<>
<
Textarea
h=
{
'100%'
}
maxLength=
{
-
1
}
value=
{
textInput
}
placeholder=
{
'请粘贴或输入需要处理的文本'
}
onChange=
{
(
e
)
=>
setTextInput
(
e
.
target
.
value
)
}
/>
<
Box
mt=
{
2
}
>
一共
{
textInput
.
length
}
个字
</
Box
>
</>
)
}
{
activeTab
===
'doc'
&&
(
<
Flex
...
...
src/pages/data/list.tsx
View file @
75cf3d1e
...
...
@@ -209,7 +209,11 @@ const DataList = () => {
</
Card
>
{
ImportDataId
&&
(
<
ImportDataModal
dataId=
{
ImportDataId
}
onClose=
{
()
=>
setImportDataId
(
undefined
)
}
/>
<
ImportDataModal
dataId=
{
ImportDataId
}
onClose=
{
()
=>
setImportDataId
(
undefined
)
}
onSuccess=
{
()
=>
getData
(
1
,
true
)
}
/>
)
}
{
isOpenCreateDataModal
&&
(
<
CreateDataModal
onClose=
{
onCloseCreateDataModal
}
onSuccess=
{
()
=>
getData
(
1
,
true
)
}
/>
...
...
src/pages/number/setting.tsx
View file @
75cf3d1e
...
...
@@ -33,6 +33,7 @@ import dayjs from 'dayjs';
import
{
formatPrice
}
from
'@/utils/user'
;
import
WxConcat
from
'@/components/WxConcat'
;
import
ScrollData
from
'@/components/ScrollData'
;
import
{
BillTypeMap
}
from
'@/constants/user'
;
const
PayModal
=
dynamic
(()
=>
import
(
'./components/PayModal'
));
...
...
@@ -266,6 +267,7 @@ const NumberSetting = () => {
<
Thead
>
<
Tr
>
<
Th
>
时间
</
Th
>
<
Th
>
类型
</
Th
>
<
Th
>
内容长度
</
Th
>
<
Th
>
Tokens 长度
</
Th
>
<
Th
>
消费
</
Th
>
...
...
@@ -275,6 +277,7 @@ const NumberSetting = () => {
{
bills
.
map
((
item
)
=>
(
<
Tr
key=
{
item
.
id
}
>
<
Td
>
{
item
.
time
}
</
Td
>
<
Td
>
{
BillTypeMap
[
item
.
type
]
}
</
Td
>
<
Td
>
{
item
.
textLen
}
</
Td
>
<
Td
>
{
item
.
tokenLen
}
</
Td
>
<
Td
>
{
item
.
price
}
元
</
Td
>
...
...
src/service/events/generateQA.ts
View file @
75cf3d1e
...
...
@@ -4,6 +4,7 @@ import { httpsAgent, getOpenApiKey } from '@/service/utils/tools';
import
type
{
ChatCompletionRequestMessage
}
from
'openai'
;
import
{
DataItemSchema
}
from
'@/types/mongoSchema'
;
import
{
ChatModelNameEnum
}
from
'@/constants/model'
;
import
{
pushSplitDataBill
}
from
'@/service/events/pushBill'
;
export
async
function
generateQA
(
next
=
false
):
Promise
<
any
>
{
if
(
global
.
generatingQA
&&
!
next
)
return
;
...
...
@@ -83,20 +84,21 @@ export async function generateQA(next = false): Promise<any> {
const
splitResponse
=
splitText
(
content
||
''
);
// 插入数据库,并修改状态
await
DataItem
.
findByIdAndUpdate
(
dataItem
.
_id
,
{
status
:
dataItem
.
temperature
>=
80
?
0
:
1
,
// 需要生成 5 组内容。0,0.2,0.4,0.6,0.8
temperature
:
dataItem
.
temperature
>=
80
?
dataItem
.
temperature
:
dataItem
.
temperature
+
2
0
,
status
:
dataItem
.
temperature
>=
90
?
0
:
1
,
// 需要生成 4 组内容。0,0.3,0.6,0.9
temperature
:
dataItem
.
temperature
>=
90
?
dataItem
.
temperature
:
dataItem
.
temperature
+
3
0
,
$push
:
{
result
:
{
$each
:
splitResponse
}
}
});
console
.
log
(
'生成成功,time:'
,
`
${(
Date
.
now
()
-
startTime
)
/
1000
}
s`
,
'result length: '
,
splitResponse
.
length
);
// 计费
!
userApiKey
&&
pushSplitDataBill
({
userId
:
dataItem
.
userId
,
text
:
systemPrompt
.
content
+
dataItem
.
text
+
content
});
console
.
log
(
'生成QA成功,time:'
,
`
${(
Date
.
now
()
-
startTime
)
/
1000
}
s`
);
}
catch
(
error
:
any
)
{
console
.
log
(
'error: 生成QA错误'
,
dataItem
?.
_id
);
console
.
log
(
'response:'
,
error
?.
response
);
...
...
src/service/events/push
Chat
Bill.ts
→
src/service/events/pushBill.ts
View file @
75cf3d1e
import
{
connectToDatabase
,
Bill
,
User
}
from
'../mongo'
;
import
{
modelList
}
from
'@/constants/model'
;
import
{
modelList
,
ChatModelNameEnum
}
from
'@/constants/model'
;
import
{
encode
}
from
'gpt-token-utils'
;
import
{
formatPrice
}
from
'@/utils/user'
;
export
const
pushBill
=
async
({
export
const
push
Chat
Bill
=
async
({
modelName
,
userId
,
chatId
,
...
...
@@ -28,6 +28,7 @@ export const pushBill = async ({
// 计算价格
const
price
=
unitPrice
*
tokens
.
length
;
console
.
log
(
'chat bill'
);
console
.
log
(
'token len:'
,
tokens
.
length
);
console
.
log
(
'text len: '
,
text
.
length
);
console
.
log
(
'price: '
,
`
${
formatPrice
(
price
)}
元`
);
...
...
@@ -50,6 +51,52 @@ export const pushBill = async ({
$inc
:
{
balance
:
-
price
}
});
}
catch
(
error
)
{
console
.
log
(
'创建账单失败:'
,
error
);
billId
&&
Bill
.
findByIdAndDelete
(
billId
);
}
}
catch
(
error
)
{
console
.
log
(
error
);
}
};
export
const
pushSplitDataBill
=
async
({
userId
,
text
}:
{
userId
:
string
;
text
:
string
})
=>
{
await
connectToDatabase
();
let
billId
;
try
{
// 获取模型单价格, 都是用 gpt35 拆分
const
modelItem
=
modelList
.
find
((
item
)
=>
item
.
model
===
ChatModelNameEnum
.
GPT35
);
const
unitPrice
=
modelItem
?.
price
||
5
;
// 计算 token 数量
const
tokens
=
encode
(
text
);
// 计算价格
const
price
=
unitPrice
*
tokens
.
length
;
console
.
log
(
'splitData bill'
);
console
.
log
(
'token len:'
,
tokens
.
length
);
console
.
log
(
'text len: '
,
text
.
length
);
console
.
log
(
'price: '
,
`
${
formatPrice
(
price
)}
元`
);
try
{
// 插入 Bill 记录
const
res
=
await
Bill
.
create
({
userId
,
type
:
'splitData'
,
modelName
:
ChatModelNameEnum
.
GPT35
,
textLen
:
text
.
length
,
tokenLen
:
tokens
.
length
,
price
});
billId
=
res
.
_id
;
// 账号扣费
await
User
.
findByIdAndUpdate
(
userId
,
{
$inc
:
{
balance
:
-
price
}
});
}
catch
(
error
)
{
console
.
log
(
'创建账单失败:'
,
error
);
billId
&&
Bill
.
findByIdAndDelete
(
billId
);
}
}
catch
(
error
)
{
...
...
src/service/models/bill.ts
View file @
75cf3d1e
...
...
@@ -10,7 +10,7 @@ const BillSchema = new Schema({
},
type
:
{
type
:
String
,
enum
:
[
'chat'
,
'
generate
Data'
,
'return'
],
enum
:
[
'chat'
,
'
split
Data'
,
'return'
],
required
:
true
},
modelName
:
{
...
...
@@ -20,8 +20,7 @@ const BillSchema = new Schema({
},
chatId
:
{
type
:
Schema
.
Types
.
ObjectId
,
ref
:
'chat'
,
required
:
true
ref
:
'chat'
},
time
:
{
type
:
Date
,
...
...
src/types/mongoSchema.d.ts
View file @
75cf3d1e
...
...
@@ -80,6 +80,7 @@ export interface ChatPopulate extends ChatSchema {
export
interface
BillSchema
{
_id
:
string
;
userId
:
string
;
type
:
'chat'
|
'splitData'
|
'return'
;
chatId
:
string
;
time
:
Date
;
textLen
:
number
;
...
...
src/types/user.d.ts
View file @
75cf3d1e
...
...
@@ -24,6 +24,7 @@ export interface UserUpdateParams {
export
interface
UserBillType
{
id
:
string
;
time
:
string
;
type
:
'chat'
|
'splitData'
|
'return'
;
textLen
:
number
;
tokenLen
:
number
;
userId
:
string
;
...
...
src/utils/adapt.ts
View file @
75cf3d1e
...
...
@@ -6,6 +6,7 @@ import type { UserBillType } from '@/types/user';
export
const
adaptBill
=
(
bill
:
BillSchema
):
UserBillType
=>
{
return
{
id
:
bill
.
_id
,
type
:
bill
.
type
,
userId
:
bill
.
userId
,
chatId
:
bill
.
chatId
,
time
:
dayjs
(
bill
.
time
).
format
(
'YYYY/MM/DD HH:mm:ss'
),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment