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
e0b1a783
authored
Apr 23, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 拆分文本增加滑块,增加直接分段导入方式
parent
27749408
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
317 additions
and
155 deletions
+317
-155
src/api/model.ts
+6
-2
src/components/Radio/index.tsx
+52
-0
src/pages/api/chat/vectorGpt.ts
+11
-7
src/pages/api/model/data/pushModelDataInput.ts
+1
-2
src/pages/api/model/data/splitData.ts
+30
-39
src/pages/api/openapi/chat/vectorGpt.ts
+11
-7
src/pages/chat/index.tsx
+1
-1
src/pages/model/detail/components/InputDataModal.tsx
+4
-4
src/pages/model/detail/components/ModelDataCard.tsx
+19
-20
src/pages/model/detail/components/SelectFileModal.tsx
+118
-61
src/pages/model/detail/components/SelectUrlModal.tsx
+5
-4
src/service/events/generateQA.ts
+7
-3
src/service/models/splitData.ts
+0
-4
src/types/mongoSchema.d.ts
+0
-1
src/utils/file.ts
+52
-0
No files found.
src/api/model.ts
View file @
e0b1a783
...
@@ -85,8 +85,12 @@ export const postModelDataInput = (data: {
...
@@ -85,8 +85,12 @@ export const postModelDataInput = (data: {
/**
/**
* 拆分数据
* 拆分数据
*/
*/
export
const
postModelDataSplitData
=
(
data
:
{
modelId
:
string
;
text
:
string
;
prompt
:
string
})
=>
export
const
postModelDataSplitData
=
(
data
:
{
POST
(
`/model/data/splitData`
,
data
);
modelId
:
string
;
chunks
:
string
[];
prompt
:
string
;
mode
:
'qa'
|
'subsection'
;
})
=>
POST
(
`/model/data/splitData`
,
data
);
/**
/**
* json导入数据
* json导入数据
...
...
src/components/Radio/index.tsx
0 → 100644
View file @
e0b1a783
import
React
from
'react'
;
import
{
Stack
,
Box
,
Flex
,
useTheme
}
from
'@chakra-ui/react'
;
import
type
{
StackProps
}
from
'@chakra-ui/react'
;
// @ts-ignore
interface
Props
extends
StackProps
{
list
:
{
label
:
string
;
value
:
string
|
number
}[];
value
:
string
|
number
;
onChange
:
(
e
:
string
|
number
)
=>
void
;
}
const
Radio
=
({
list
,
value
,
onChange
,
...
props
}:
Props
)
=>
{
return
(
<
Stack
{
...
props
}
spacing=
{
5
}
direction=
{
'row'
}
>
{
list
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
value
}
alignItems=
{
'center'
}
cursor=
{
'pointer'
}
userSelect=
{
'none'
}
_before=
{
{
content
:
'""'
,
w
:
'16px'
,
h
:
'16px'
,
mr
:
1
,
borderRadius
:
'16px'
,
transition
:
'0.2s'
,
...(
value
===
item
.
value
?
{
border
:
'5px solid'
,
borderColor
:
'blue.500'
}
:
{
border
:
'2px solid'
,
borderColor
:
'gray.200'
})
}
}
_hover=
{
{
_before
:
{
borderColor
:
'blue.400'
}
}
}
onClick=
{
()
=>
onChange
(
item
.
value
)
}
>
{
item
.
label
}
</
Flex
>
))
}
</
Stack
>
);
};
export
default
Radio
;
src/pages/api/chat/vectorGpt.ts
View file @
e0b1a783
...
@@ -106,16 +106,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -106,16 +106,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
});
}
else
{
}
else
{
// 有匹配情况下,添加知识库内容。
// 有匹配情况下,添加知识库内容。
// 系统提示词过滤,最多
2
000 tokens
// 系统提示词过滤,最多
3
000 tokens
const
systemPrompt
=
systemPromptFilter
(
formatRedisPrompt
,
2
000
);
const
systemPrompt
=
systemPromptFilter
(
formatRedisPrompt
,
3
000
);
prompts
.
unshift
({
prompts
.
unshift
({
obj
:
'SYSTEM'
,
obj
:
'SYSTEM'
,
value
:
`
${
value
:
`
model
.
systemPrompt
||
'根据知识库内容回答'
${
model
.
systemPrompt
}
}
知识库是最新的,下面是知识库内容:当前时间为
${
dayjs
().
format
(
${
'YYYY/MM/DD HH:mm:ss'
model
.
search
.
mode
===
ModelVectorSearchModeEnum
.
hightSimilarity
)}
\n
${
systemPrompt
}
`
?
`你只能从知识库选择内容回答.不在知识库内容拒绝回复`
:
''
}
知识库内容为: 当前时间为
${
dayjs
().
format
(
'YYYY/MM/DD HH:mm:ss'
)}
\n
${
systemPrompt
}
'
`
});
});
}
}
...
...
src/pages/api/model/data/pushModelDataInput.ts
View file @
e0b1a783
...
@@ -4,7 +4,7 @@ import { connectToDatabase, Model } from '@/service/mongo';
...
@@ -4,7 +4,7 @@ import { connectToDatabase, Model } from '@/service/mongo';
import
{
authToken
}
from
'@/service/utils/tools'
;
import
{
authToken
}
from
'@/service/utils/tools'
;
import
{
ModelDataSchema
}
from
'@/types/mongoSchema'
;
import
{
ModelDataSchema
}
from
'@/types/mongoSchema'
;
import
{
generateVector
}
from
'@/service/events/generateVector'
;
import
{
generateVector
}
from
'@/service/events/generateVector'
;
import
{
connectPg
,
PgClient
}
from
'@/service/pg'
;
import
{
PgClient
}
from
'@/service/pg'
;
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
<
any
>
)
{
try
{
try
{
...
@@ -26,7 +26,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...
@@ -26,7 +26,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const
userId
=
await
authToken
(
authorization
);
const
userId
=
await
authToken
(
authorization
);
await
connectToDatabase
();
await
connectToDatabase
();
const
pg
=
await
connectPg
();
// 验证是否是该用户的 model
// 验证是否是该用户的 model
const
model
=
await
Model
.
findOne
({
const
model
=
await
Model
.
findOne
({
...
...
src/pages/api/model/data/splitData.ts
View file @
e0b1a783
...
@@ -2,14 +2,20 @@ import type { NextApiRequest, NextApiResponse } from 'next';
...
@@ -2,14 +2,20 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import
{
jsonRes
}
from
'@/service/response'
;
import
{
jsonRes
}
from
'@/service/response'
;
import
{
connectToDatabase
,
SplitData
,
Model
}
from
'@/service/mongo'
;
import
{
connectToDatabase
,
SplitData
,
Model
}
from
'@/service/mongo'
;
import
{
authToken
}
from
'@/service/utils/tools'
;
import
{
authToken
}
from
'@/service/utils/tools'
;
import
{
generateVector
}
from
'@/service/events/generateVector'
;
import
{
generateQA
}
from
'@/service/events/generateQA'
;
import
{
generateQA
}
from
'@/service/events/generateQA'
;
import
{
encode
}
from
'gpt-token-utils
'
;
import
{
PgClient
}
from
'@/service/pg
'
;
/* 拆分数据成QA */
/* 拆分数据成QA */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
try
{
try
{
const
{
text
,
modelId
,
prompt
}
=
req
.
body
as
{
text
:
string
;
modelId
:
string
;
prompt
:
string
};
const
{
chunks
,
modelId
,
prompt
,
mode
}
=
req
.
body
as
{
if
(
!
text
||
!
modelId
||
!
prompt
)
{
modelId
:
string
;
chunks
:
string
[];
prompt
:
string
;
mode
:
'qa'
|
'subsection'
;
};
if
(
!
chunks
||
!
modelId
||
!
prompt
)
{
throw
new
Error
(
'参数错误'
);
throw
new
Error
(
'参数错误'
);
}
}
await
connectToDatabase
();
await
connectToDatabase
();
...
@@ -28,46 +34,31 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -28,46 +34,31 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
throw
new
Error
(
'无权操作该模型'
);
throw
new
Error
(
'无权操作该模型'
);
}
}
const
replaceText
=
text
.
replace
(
/
\\
n/g
,
'\n'
);
if
(
mode
===
'qa'
)
{
// 批量QA拆分插入数据
await
SplitData
.
create
({
userId
,
modelId
,
textList
:
chunks
,
prompt
});
// 文本拆分成 chunk
generateQA
();
const
chunks
=
replaceText
.
split
(
'\n'
).
filter
((
item
)
=>
item
.
trim
());
}
else
if
(
mode
===
'subsection'
)
{
// 插入记录
await
PgClient
.
insert
(
'modelData'
,
{
values
:
chunks
.
map
((
item
)
=>
[
{
key
:
'user_id'
,
value
:
userId
},
{
key
:
'model_id'
,
value
:
modelId
},
{
key
:
'q'
,
value
:
item
},
{
key
:
'a'
,
value
:
''
},
{
key
:
'status'
,
value
:
'waiting'
}
])
});
const
textList
:
string
[]
=
[];
generateVector
();
let
splitText
=
''
;
/* 取 2.5k ~ 3.5K tokens 内容 */
chunks
.
forEach
((
chunk
)
=>
{
const
tokens
=
encode
(
splitText
+
chunk
).
length
;
if
(
tokens
>=
3500
)
{
// 超过 3500,不要这块内容
splitText
&&
textList
.
push
(
splitText
);
splitText
=
chunk
;
}
else
if
(
tokens
>=
2500
)
{
// 超过 3000,取内容
splitText
&&
textList
.
push
(
splitText
+
chunk
);
splitText
=
''
;
}
else
{
//没超过 3000,继续添加
splitText
+=
chunk
;
}
});
if
(
splitText
)
{
textList
.
push
(
splitText
);
}
}
// 批量插入数据
await
SplitData
.
create
({
userId
,
modelId
,
rawText
:
text
,
textList
,
prompt
});
generateQA
();
jsonRes
(
res
);
jsonRes
(
res
);
}
catch
(
err
)
{
}
catch
(
err
)
{
jsonRes
(
res
,
{
jsonRes
(
res
,
{
...
...
src/pages/api/openapi/chat/vectorGpt.ts
View file @
e0b1a783
...
@@ -126,16 +126,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -126,16 +126,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
});
}
else
{
}
else
{
// 有匹配或者低匹配度模式情况下,添加知识库内容。
// 有匹配或者低匹配度模式情况下,添加知识库内容。
// 系统提示词过滤,最多
2
000 tokens
// 系统提示词过滤,最多
3
000 tokens
const
systemPrompt
=
systemPromptFilter
(
formatRedisPrompt
,
2
000
);
const
systemPrompt
=
systemPromptFilter
(
formatRedisPrompt
,
3
000
);
prompts
.
unshift
({
prompts
.
unshift
({
obj
:
'SYSTEM'
,
obj
:
'SYSTEM'
,
value
:
`
${
value
:
`
model
.
systemPrompt
||
'根据知识库内容回答'
${
model
.
systemPrompt
}
}
知识库是最新的,下面是知识库内容:当前时间为
${
dayjs
().
format
(
${
'YYYY/MM/DD HH:mm:ss'
model
.
search
.
mode
===
ModelVectorSearchModeEnum
.
hightSimilarity
)}
\n
${
systemPrompt
}
`
?
`你只能从知识库选择内容回答.不在知识库内容拒绝回复`
:
''
}
知识库内容为: 当前时间为
${
dayjs
().
format
(
'YYYY/MM/DD HH:mm:ss'
)}
\n
${
systemPrompt
}
'
`
});
});
}
}
...
...
src/pages/chat/index.tsx
View file @
e0b1a783
...
@@ -133,7 +133,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
...
@@ -133,7 +133,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
if
(
isScroll
&&
res
.
history
.
length
>
0
)
{
if
(
isScroll
&&
res
.
history
.
length
>
0
)
{
setTimeout
(()
=>
{
setTimeout
(()
=>
{
scrollToBottom
(
'auto'
);
scrollToBottom
(
'auto'
);
},
20
00
);
},
12
00
);
}
}
}
catch
(
e
:
any
)
{
}
catch
(
e
:
any
)
{
toast
({
toast
({
...
...
src/pages/model/detail/components/InputDataModal.tsx
View file @
e0b1a783
...
@@ -122,9 +122,9 @@ const InputDataModal = ({
...
@@ -122,9 +122,9 @@ const InputDataModal = ({
<
Box
h=
{
'30px'
}
>
问题
</
Box
>
<
Box
h=
{
'30px'
}
>
问题
</
Box
>
<
Textarea
<
Textarea
placeholder=
{
placeholder=
{
'相关问题,可以输入多个问法, 最多
500
字。例如:\n1. laf 是什么?\n2. laf 可以做什么?\n3. laf怎么用'
'相关问题,可以输入多个问法, 最多
1000
字。例如:\n1. laf 是什么?\n2. laf 可以做什么?\n3. laf怎么用'
}
}
maxLength=
{
5
00
}
maxLength=
{
10
00
}
resize=
{
'none'
}
resize=
{
'none'
}
h=
{
'calc(100% - 30px)'
}
h=
{
'calc(100% - 30px)'
}
{
...
register
(`
q
`,
{
{
...
register
(`
q
`,
{
...
@@ -136,9 +136,9 @@ const InputDataModal = ({
...
@@ -136,9 +136,9 @@ const InputDataModal = ({
<
Box
h=
{
'30px'
}
>
知识点
</
Box
>
<
Box
h=
{
'30px'
}
>
知识点
</
Box
>
<
Textarea
<
Textarea
placeholder=
{
placeholder=
{
'知识点,最多
1000字。请保持主语的完整性,缺少主语会导致效果不佳
。例如:\n1. laf是一个云函数开发平台。\n2. laf 什么都能做。\n3. 下面是使用 laf 的例子: ……'
'知识点,最多
2000 字
。例如:\n1. laf是一个云函数开发平台。\n2. laf 什么都能做。\n3. 下面是使用 laf 的例子: ……'
}
}
maxLength=
{
1
000
}
maxLength=
{
2
000
}
resize=
{
'none'
}
resize=
{
'none'
}
h=
{
'calc(100% - 30px)'
}
h=
{
'calc(100% - 30px)'
}
{
...
register
(`
a
`,
{
{
...
register
(`
a
`,
{
...
...
src/pages/model/detail/components/ModelDataCard.tsx
View file @
e0b1a783
...
@@ -18,6 +18,7 @@ import {
...
@@ -18,6 +18,7 @@ import {
MenuItem
,
MenuItem
,
Input
Input
}
from
'@chakra-ui/react'
;
}
from
'@chakra-ui/react'
;
import
type
{
BoxProps
}
from
'@chakra-ui/react'
;
import
type
{
ModelSchema
}
from
'@/types/mongoSchema'
;
import
type
{
ModelSchema
}
from
'@/types/mongoSchema'
;
import
type
{
ModelDataItemType
}
from
'@/types/model'
;
import
type
{
ModelDataItemType
}
from
'@/types/model'
;
import
{
ModelDataStatusMap
}
from
'@/constants/model'
;
import
{
ModelDataStatusMap
}
from
'@/constants/model'
;
...
@@ -114,6 +115,14 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
...
@@ -114,6 +115,14 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
}
}
});
});
const
tdStyles
:
BoxProps
=
{
fontSize
:
'xs'
,
maxW
:
'500px'
,
whiteSpace
:
'pre-wrap'
,
maxH
:
'250px'
,
overflowY
:
'auto'
};
return
(
return
(
<>
<>
<
Flex
>
<
Flex
>
...
@@ -156,8 +165,8 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
...
@@ -156,8 +165,8 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
>
>
手动输入
手动输入
</
MenuItem
>
</
MenuItem
>
<
MenuItem
onClick=
{
onOpenSelectFileModal
}
>
文本/文件
QA
拆分
</
MenuItem
>
<
MenuItem
onClick=
{
onOpenSelectFileModal
}
>
文本/文件拆分
</
MenuItem
>
<
MenuItem
onClick=
{
onOpenSelectUrlModal
}
>
网站内容 QA 拆分
</
MenuItem
>
{
/* <MenuItem onClick={onOpenSelectUrlModal}>网站内容拆分</MenuItem> */
}
<
MenuItem
onClick=
{
onOpenSelectCsvModal
}
>
csv 问答对导入
</
MenuItem
>
<
MenuItem
onClick=
{
onOpenSelectCsvModal
}
>
csv 问答对导入
</
MenuItem
>
</
MenuList
>
</
MenuList
>
</
Menu
>
</
Menu
>
...
@@ -191,33 +200,23 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
...
@@ -191,33 +200,23 @@ const ModelDataCard = ({ model }: { model: ModelSchema }) => {
<
Box
mt=
{
4
}
>
<
Box
mt=
{
4
}
>
<
TableContainer
minH=
{
'500px'
}
>
<
TableContainer
minH=
{
'500px'
}
>
<
Table
variant=
{
'simple'
}
>
<
Table
variant=
{
'simple'
}
w=
{
'100%'
}
>
<
Thead
>
<
Thead
>
<
Tr
>
<
Tr
>
<
Th
>
Question
</
Th
>
<
Th
>
匹配内容(问题)
</
Th
>
<
Th
>
Text
</
Th
>
<
Th
>
对应答案
</
Th
>
<
Th
>
Status
</
Th
>
<
Th
>
状态
</
Th
>
<
Th
>
操作
</
Th
>
<
Th
>
操作
</
Th
>
</
Tr
>
</
Tr
>
</
Thead
>
</
Thead
>
<
Tbody
>
<
Tbody
>
{
modelDataList
.
map
((
item
)
=>
(
{
modelDataList
.
map
((
item
)
=>
(
<
Tr
key=
{
item
.
id
}
>
<
Tr
key=
{
item
.
id
}
>
<
Td
minW=
{
'200px'
}
>
<
Td
>
<
Box
fontSize=
{
'xs'
}
whiteSpace=
{
'pre-wrap'
}
>
<
Box
{
...
tdStyles
}
>
{
item
.
q
}
</
Box
>
{
item
.
q
}
</
Box
>
</
Td
>
</
Td
>
<
Td
minW=
{
'200px'
}
>
<
Td
>
<
Box
<
Box
{
...
tdStyles
}
>
{
item
.
a
||
'-'
}
</
Box
>
w=
{
'100%'
}
fontSize=
{
'xs'
}
whiteSpace=
{
'pre-wrap'
}
maxH=
{
'250px'
}
overflowY=
{
'auto'
}
>
{
item
.
a
}
</
Box
>
</
Td
>
</
Td
>
<
Td
>
{
ModelDataStatusMap
[
item
.
status
]
}
</
Td
>
<
Td
>
{
ModelDataStatusMap
[
item
.
status
]
}
</
Td
>
<
Td
>
<
Td
>
...
...
src/pages/model/detail/components/SelectFileModal.tsx
View file @
e0b1a783
import
React
,
{
useState
,
useCallback
}
from
'react'
;
import
React
,
{
useState
,
useCallback
,
useMemo
}
from
'react'
;
import
{
import
{
Box
,
Box
,
Flex
,
Flex
,
...
@@ -20,9 +20,26 @@ import { readTxtContent, readPdfContent, readDocContent } from '@/utils/file';
...
@@ -20,9 +20,26 @@ import { readTxtContent, readPdfContent, readDocContent } from '@/utils/file';
import
{
useMutation
}
from
'@tanstack/react-query'
;
import
{
useMutation
}
from
'@tanstack/react-query'
;
import
{
postModelDataSplitData
}
from
'@/api/model'
;
import
{
postModelDataSplitData
}
from
'@/api/model'
;
import
{
formatPrice
}
from
'@/utils/user'
;
import
{
formatPrice
}
from
'@/utils/user'
;
import
Radio
from
'@/components/Radio'
;
import
{
splitText
}
from
'@/utils/file'
;
const
fileExtension
=
'.txt,.doc,.docx,.pdf,.md'
;
const
fileExtension
=
'.txt,.doc,.docx,.pdf,.md'
;
const
modeMap
=
{
qa
:
{
maxLen
:
2800
,
slideLen
:
800
,
price
:
3
,
isPrompt
:
true
},
subsection
:
{
maxLen
:
1000
,
slideLen
:
300
,
price
:
0.4
,
isPrompt
:
false
}
};
const
SelectFileModal
=
({
const
SelectFileModal
=
({
onClose
,
onClose
,
onSuccess
,
onSuccess
,
...
@@ -36,38 +53,45 @@ const SelectFileModal = ({
...
@@ -36,38 +53,45 @@ const SelectFileModal = ({
const
{
toast
}
=
useToast
();
const
{
toast
}
=
useToast
();
const
[
prompt
,
setPrompt
]
=
useState
(
''
);
const
[
prompt
,
setPrompt
]
=
useState
(
''
);
const
{
File
,
onOpen
}
=
useSelectFile
({
fileType
:
fileExtension
,
multiple
:
true
});
const
{
File
,
onOpen
}
=
useSelectFile
({
fileType
:
fileExtension
,
multiple
:
true
});
const
[
fileText
,
setFileText
]
=
useState
(
''
);
const
[
mode
,
setMode
]
=
useState
<
'qa'
|
'subsection'
>
(
'qa'
);
const
[
fileTextArr
,
setFileTextArr
]
=
useState
<
string
[]
>
([
''
]);
const
{
openConfirm
,
ConfirmChild
}
=
useConfirm
({
const
{
openConfirm
,
ConfirmChild
}
=
useConfirm
({
content
:
'确认导入该文件,需要一定时间进行拆解,该任务无法终止!如果余额不足,任务讲被终止。'
content
:
'确认导入该文件,需要一定时间进行拆解,该任务无法终止!如果余额不足,任务讲被终止。'
});
});
const
fileText
=
useMemo
(()
=>
{
const
chunks
=
fileTextArr
.
map
((
item
)
=>
splitText
({
text
:
item
,
...
modeMap
[
mode
]
})
);
return
chunks
.
join
(
''
);
},
[
fileTextArr
,
mode
]);
const
onSelectFile
=
useCallback
(
const
onSelectFile
=
useCallback
(
async
(
e
:
File
[])
=>
{
async
(
e
:
File
[])
=>
{
setSelecting
(
true
);
setSelecting
(
true
);
try
{
try
{
const
fileTexts
=
(
const
fileTexts
=
await
Promise
.
all
(
await
Promise
.
all
(
e
.
map
((
file
)
=>
{
e
.
map
((
file
)
=>
{
// @ts-ignore
// @ts-ignore
const
extension
=
file
?.
name
?.
split
(
'.'
).
pop
().
toLowerCase
();
const
extension
=
file
?.
name
?.
split
(
'.'
).
pop
().
toLowerCase
();
switch
(
extension
)
{
switch
(
extension
)
{
case
'txt'
:
case
'txt'
:
case
'md'
:
case
'md'
:
return
readTxtContent
(
file
);
return
readTxtContent
(
file
);
case
'pdf'
:
case
'pdf'
:
return
readPdfContent
(
file
);
return
readPdfContent
(
file
);
case
'doc'
:
case
'doc'
:
case
'docx'
:
case
'docx'
:
return
readDocContent
(
file
);
return
readDocContent
(
file
);
default
:
default
:
return
''
;
return
''
;
}
}
})
})
);
)
setFileTextArr
(
fileTexts
);
)
.
join
(
' '
)
.
replace
(
/
(\\
n|
\n)
+/g
,
'\n'
);
setFileText
(
fileTexts
);
}
catch
(
error
:
any
)
{
}
catch
(
error
:
any
)
{
console
.
log
(
error
);
console
.
log
(
error
);
toast
({
toast
({
...
@@ -77,16 +101,25 @@ const SelectFileModal = ({
...
@@ -77,16 +101,25 @@ const SelectFileModal = ({
}
}
setSelecting
(
false
);
setSelecting
(
false
);
},
},
[
setSelecting
,
toast
]
[
toast
]
);
);
const
{
mutate
,
isLoading
}
=
useMutation
({
const
{
mutate
,
isLoading
}
=
useMutation
({
mutationFn
:
async
()
=>
{
mutationFn
:
async
()
=>
{
if
(
!
fileText
)
return
;
if
(
!
fileText
)
return
;
const
chunks
=
fileTextArr
.
map
((
item
)
=>
splitText
({
text
:
item
,
...
modeMap
[
mode
]
})
)
.
flat
();
await
postModelDataSplitData
({
await
postModelDataSplitData
({
modelId
,
modelId
,
text
:
fileText
.
replace
(
/
\\
n/g
,
'\n'
).
replace
(
/
\n
+/g
,
'\n'
),
chunks
,
prompt
:
`下面是"
${
prompt
||
'一段长文本'
}
"`
prompt
:
`下面是"
${
prompt
||
'一段长文本'
}
"`
,
mode
});
});
toast
({
toast
({
title
:
'导入数据成功,需要一段拆解和训练'
,
title
:
'导入数据成功,需要一段拆解和训练'
,
...
@@ -106,58 +139,82 @@ const SelectFileModal = ({
...
@@ -106,58 +139,82 @@ const SelectFileModal = ({
return
(
return
(
<
Modal
isOpen=
{
true
}
onClose=
{
onClose
}
isCentered
>
<
Modal
isOpen=
{
true
}
onClose=
{
onClose
}
isCentered
>
<
ModalOverlay
/>
<
ModalOverlay
/>
<
ModalContent
maxW=
{
'min(
9
00px, 90vw)'
}
m=
{
0
}
position=
{
'relative'
}
h=
{
'90vh'
}
>
<
ModalContent
maxW=
{
'min(
10
00px, 90vw)'
}
m=
{
0
}
position=
{
'relative'
}
h=
{
'90vh'
}
>
<
ModalHeader
>
文件导入
</
ModalHeader
>
<
ModalHeader
>
文件导入
</
ModalHeader
>
<
ModalCloseButton
/>
<
ModalCloseButton
/>
<
ModalBody
<
ModalBody
display=
{
'flex'
}
display=
{
'flex'
}
flexDirection=
{
'column'
}
flexDirection=
{
'column'
}
p=
{
4
}
p=
{
0
}
h=
{
'100%'
}
h=
{
'100%'
}
alignItems=
{
'center'
}
alignItems=
{
'center'
}
justifyContent=
{
'center'
}
justifyContent=
{
'center'
}
fontSize=
{
'sm'
}
fontSize=
{
'sm'
}
>
>
<
Button
isLoading=
{
selecting
}
onClick=
{
onOpen
}
>
<
Box
mt=
{
2
}
px=
{
4
}
maxW=
{
[
'100%'
]
}
textAlign=
{
'justify'
}
color=
{
'blackAlpha.600'
}
>
选择文件
</
Button
>
<
Box
mt=
{
2
}
maxW=
{
[
'100%'
,
'70%'
]
}
>
支持
{
fileExtension
}
文件。模型会自动对文本进行 QA 拆分,需要较长训练时间,拆分需要消耗
支持
{
fileExtension
}
文件。模型会自动对文本进行 QA 拆分,需要较长训练时间,拆分需要消耗
tokens,账号余额不足时,未拆分的数据会被删除。
tokens,账号余额不足时,未拆分的数据会被删除。当前一共
{
encode
(
fileText
).
length
}{
' '
}
</
Box
>
个tokens,大约
{
formatPrice
(
encode
(
fileText
).
length
*
modeMap
[
mode
].
price
)
}
元
<
Box
mt=
{
2
}
>
一共
{
encode
(
fileText
).
length
}
个tokens,大约
{
formatPrice
(
encode
(
fileText
).
length
*
3
)
}
元
</
Box
>
</
Box
>
<
Flex
w=
{
'100%'
}
alignItems=
{
'center'
}
my=
{
4
}
>
{
/* 拆分模式 */
}
<
Box
flex=
{
'0 0 auto'
}
mr=
{
2
}
>
<
Flex
w=
{
'100%'
}
px=
{
5
}
alignItems=
{
'center'
}
mt=
{
4
}
>
下面是
<
Box
flex=
{
'0 0 70px'
}
>
分段模式:
</
Box
>
</
Box
>
<
Radio
<
Input
ml=
{
3
}
placeholder=
"提示词,例如: Laf的介绍/关于gpt4的论文/一段长文本"
list=
{
[
value=
{
prompt
}
{
label
:
'QA拆分'
,
value
:
'qa'
},
onChange=
{
(
e
)
=>
setPrompt
(
e
.
target
.
value
)
}
{
label
:
'直接分段'
,
value
:
'subsection'
}
size=
{
'sm'
}
]
}
value=
{
mode
}
onChange=
{
(
e
)
=>
setMode
(
e
as
'subsection'
|
'qa'
)
}
/>
/>
</
Flex
>
</
Flex
>
<
Textarea
{
/* 内容介绍 */
}
flex=
{
'1 0 0'
}
{
modeMap
[
mode
].
isPrompt
&&
(
h=
{
0
}
<
Flex
w=
{
'100%'
}
px=
{
5
}
alignItems=
{
'center'
}
mt=
{
4
}
>
w=
{
'100%'
}
<
Box
flex=
{
'0 0 70px'
}
mr=
{
2
}
>
placeholder=
"文件内容"
下面是
maxLength=
{
-
1
}
</
Box
>
resize=
{
'none'
}
<
Input
fontSize=
{
'xs'
}
placeholder=
"提示词,例如: Laf的介绍/关于gpt4的论文/一段长文本"
whiteSpace=
{
'pre-wrap'
}
value=
{
prompt
}
value=
{
fileText
}
onChange=
{
(
e
)
=>
setPrompt
(
e
.
target
.
value
)
}
onChange=
{
(
e
)
=>
setFileText
(
e
.
target
.
value
)
}
size=
{
'sm'
}
/>
/>
</
Flex
>
)
}
{
/* 文本内容 */
}
<
Box
flex=
{
'1 0 0'
}
px=
{
5
}
h=
{
0
}
w=
{
'100%'
}
overflowY=
{
'auto'
}
mt=
{
4
}
>
{
fileTextArr
.
map
((
item
,
i
)
=>
(
<
Box
key=
{
i
}
mb=
{
5
}
>
<
Box
mb=
{
1
}
>
文本
{
i
+
1
}
</
Box
>
<
Textarea
placeholder=
"文件内容"
maxLength=
{
-
1
}
rows=
{
10
}
fontSize=
{
'xs'
}
whiteSpace=
{
'pre-wrap'
}
value=
{
item
}
onChange=
{
(
e
)
=>
{
setFileTextArr
([
...
fileTextArr
.
slice
(
0
,
i
),
e
.
target
.
value
,
...
fileTextArr
.
slice
(
i
+
1
)
]);
}
}
/>
</
Box
>
))
}
</
Box
>
</
ModalBody
>
</
ModalBody
>
<
Flex
px=
{
6
}
pt=
{
2
}
pb=
{
4
}
>
<
Flex
px=
{
6
}
pt=
{
2
}
pb=
{
4
}
>
<
Button
isLoading=
{
selecting
}
onClick=
{
onOpen
}
>
选择文件
</
Button
>
<
Box
flex=
{
1
}
></
Box
>
<
Box
flex=
{
1
}
></
Box
>
<
Button
variant=
{
'outline'
}
mr=
{
3
}
onClick=
{
onClose
}
>
<
Button
variant=
{
'outline'
}
colorScheme=
{
'gray'
}
mr=
{
3
}
onClick=
{
onClose
}
>
取消
取消
</
Button
>
</
Button
>
<
Button
isLoading=
{
isLoading
}
isDisabled=
{
fileText
===
''
}
onClick=
{
openConfirm
(
mutate
)
}
>
<
Button
isLoading=
{
isLoading
}
isDisabled=
{
fileText
===
''
}
onClick=
{
openConfirm
(
mutate
)
}
>
...
...
src/pages/model/detail/components/SelectUrlModal.tsx
View file @
e0b1a783
...
@@ -44,8 +44,9 @@ const SelectUrlModal = ({
...
@@ -44,8 +44,9 @@ const SelectUrlModal = ({
if
(
!
webText
)
return
;
if
(
!
webText
)
return
;
await
postModelDataSplitData
({
await
postModelDataSplitData
({
modelId
,
modelId
,
text
:
webText
,
chunks
:
[],
prompt
:
`下面是"
${
prompt
||
'一段长文本'
}
"`
prompt
:
`下面是"
${
prompt
||
'一段长文本'
}
"`
,
mode
:
'qa'
});
});
toast
({
toast
({
title
:
'导入数据成功,需要一段拆解和训练'
,
title
:
'导入数据成功,需要一段拆解和训练'
,
...
@@ -89,7 +90,7 @@ const SelectUrlModal = ({
...
@@ -89,7 +90,7 @@ const SelectUrlModal = ({
<
Modal
isOpen=
{
true
}
onClose=
{
onClose
}
isCentered
>
<
Modal
isOpen=
{
true
}
onClose=
{
onClose
}
isCentered
>
<
ModalOverlay
/>
<
ModalOverlay
/>
<
ModalContent
maxW=
{
'min(900px, 90vw)'
}
m=
{
0
}
position=
{
'relative'
}
h=
{
'90vh'
}
>
<
ModalContent
maxW=
{
'min(900px, 90vw)'
}
m=
{
0
}
position=
{
'relative'
}
h=
{
'90vh'
}
>
<
ModalHeader
>
网站地址
导入
</
ModalHeader
>
<
ModalHeader
>
静态网站内容
导入
</
ModalHeader
>
<
ModalCloseButton
/>
<
ModalCloseButton
/>
<
ModalBody
<
ModalBody
...
@@ -102,7 +103,7 @@ const SelectUrlModal = ({
...
@@ -102,7 +103,7 @@ const SelectUrlModal = ({
fontSize=
{
'sm'
}
fontSize=
{
'sm'
}
>
>
<
Box
mt=
{
2
}
maxW=
{
[
'100%'
,
'70%'
]
}
>
<
Box
mt=
{
2
}
maxW=
{
[
'100%'
,
'70%'
]
}
>
根据网站地址,获取网站文本内容(请注意
获取后的内容,不是每个网站内容都能获取到的
)。模型会对文本进行
根据网站地址,获取网站文本内容(请注意
仅能获取静态网站文本,注意看下获取后的内容是否正确
)。模型会对文本进行
QA 拆分,需要较长训练时间,拆分需要消耗 tokens,账号余额不足时,未拆分的数据会被删除。
QA 拆分,需要较长训练时间,拆分需要消耗 tokens,账号余额不足时,未拆分的数据会被删除。
</
Box
>
</
Box
>
<
Box
mt=
{
2
}
>
<
Box
mt=
{
2
}
>
...
...
src/service/events/generateQA.ts
View file @
e0b1a783
...
@@ -69,9 +69,13 @@ export async function generateQA(next = false): Promise<any> {
...
@@ -69,9 +69,13 @@ export async function generateQA(next = false): Promise<any> {
const
chatAPI
=
getOpenAIApi
(
userApiKey
||
systemKey
);
const
chatAPI
=
getOpenAIApi
(
userApiKey
||
systemKey
);
const
systemPrompt
:
ChatCompletionRequestMessage
=
{
const
systemPrompt
:
ChatCompletionRequestMessage
=
{
role
:
'system'
,
role
:
'system'
,
content
:
`你是出题官.
${
content
:
`你是出题人
dataItem
.
prompt
||
'下面是"一段长文本"'
${
dataItem
.
prompt
||
'下面是"一段长文本"'
}
}
,从中选出5至20个题目和答案,题目包含问答题,计算题,代码题等.答案要详细.按格式返回: Q1:\nA1:\nQ2:\nA2:\n`
从中选出5至20个题目和答案,题目包含问答题,计算题,代码题等.答案要详细.按格式返回: Q1:
A1:
Q2:
A2:
...`
};
};
// 请求 chatgpt 获取回答
// 请求 chatgpt 获取回答
...
...
src/service/models/splitData.ts
View file @
e0b1a783
...
@@ -18,10 +18,6 @@ const SplitDataSchema = new Schema({
...
@@ -18,10 +18,6 @@ const SplitDataSchema = new Schema({
ref
:
'model'
,
ref
:
'model'
,
required
:
true
required
:
true
},
},
rawText
:
{
type
:
String
,
required
:
true
},
textList
:
{
textList
:
{
type
:
[
String
],
type
:
[
String
],
default
:
[]
default
:
[]
...
...
src/types/mongoSchema.d.ts
View file @
e0b1a783
...
@@ -75,7 +75,6 @@ export interface ModelSplitDataSchema {
...
@@ -75,7 +75,6 @@ export interface ModelSplitDataSchema {
_id
:
string
;
_id
:
string
;
userId
:
string
;
userId
:
string
;
modelId
:
string
;
modelId
:
string
;
rawText
:
string
;
prompt
:
string
;
prompt
:
string
;
errorText
:
string
;
errorText
:
string
;
textList
:
string
[];
textList
:
string
[];
...
...
src/utils/file.ts
View file @
e0b1a783
import
mammoth
from
'mammoth'
;
import
mammoth
from
'mammoth'
;
import
Papa
from
'papaparse'
;
import
Papa
from
'papaparse'
;
import
{
encode
}
from
'gpt-token-utils'
;
/**
/**
* 读取 txt 文件内容
* 读取 txt 文件内容
...
@@ -137,3 +138,54 @@ export const fileDownload = ({
...
@@ -137,3 +138,54 @@ export const fileDownload = ({
downloadLink
.
click
();
downloadLink
.
click
();
document
.
body
.
removeChild
(
downloadLink
);
document
.
body
.
removeChild
(
downloadLink
);
};
};
/**
* text split into chunks
* maxLen - one chunk len. max: 3500
* slideLen - The size of the before and after Text
* maxLen > slideLen
*/
export
const
splitText
=
({
text
,
maxLen
,
slideLen
}:
{
text
:
string
;
maxLen
:
number
;
slideLen
:
number
;
})
=>
{
const
textArr
=
text
.
match
(
/
[
!?。
\n
.
]
+|
[^\s]
+/g
)?.
filter
((
item
)
=>
{
const
text
=
item
.
replace
(
/
(\\
n
)
/g
,
'\n'
).
trim
();
if
(
text
&&
text
!==
'\n'
)
return
true
;
return
false
;
})
||
[];
const
chunks
:
{
sum
:
number
;
arr
:
string
[]
}[]
=
[{
sum
:
0
,
arr
:
[]
}];
for
(
let
i
=
0
;
i
<
textArr
.
length
;
i
++
)
{
const
tokenLen
=
encode
(
textArr
[
i
]).
length
;
chunks
[
chunks
.
length
-
1
].
sum
+=
tokenLen
;
chunks
[
chunks
.
length
-
1
].
arr
.
push
(
textArr
[
i
]);
// current length is over maxLen. create new chunk
if
(
chunks
[
chunks
.
length
-
1
].
sum
+
tokenLen
>=
maxLen
)
{
// get slide len text as the initial value
const
chunk
:
{
sum
:
number
;
arr
:
string
[]
}
=
{
sum
:
0
,
arr
:
[]
};
for
(
let
j
=
chunks
[
chunks
.
length
-
1
].
arr
.
length
-
1
;
j
>=
0
;
j
--
)
{
const
chunkText
=
chunks
[
chunks
.
length
-
1
].
arr
[
j
];
const
tokenLen
=
encode
(
chunkText
).
length
;
chunk
.
sum
+=
tokenLen
;
chunk
.
arr
.
unshift
(
chunkText
);
if
(
chunk
.
sum
>=
slideLen
)
{
break
;
}
}
chunks
.
push
(
chunk
);
}
}
const
result
=
chunks
.
map
((
item
)
=>
item
.
arr
.
join
(
''
));
return
result
;
};
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