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
fdcf53ea
authored
Jul 21, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: overview setting
parent
b7b20a35
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
1919 additions
and
418 deletions
+1919
-418
client/src/components/ChatBox/index.tsx
+2
-2
client/src/constants/flow/ModuleTemplate.ts
+21
-17
client/src/constants/theme.ts
+3
-0
client/src/pages/api/admin/initv4.ts
+110
-92
client/src/pages/api/app/modules/kb/search.ts
+11
-13
client/src/pages/app/detail/components/BasicEdit/index.tsx
+527
-0
client/src/pages/app/detail/components/Charts/TotalUsage.tsx
+1
-1
client/src/pages/app/detail/components/Edit/components/Nodes/NodeKbSearch.tsx
+66
-5
client/src/pages/app/detail/components/Edit/components/Nodes/NodeUserGuide.tsx
+3
-7
client/src/pages/app/detail/components/Edit/components/Nodes/NodeVariable.tsx
+1
-5
client/src/pages/app/detail/components/Edit/components/Plugins/KBSelect.tsx
+0
-143
client/src/pages/app/detail/components/InfoModal.tsx
+27
-40
client/src/pages/app/detail/components/KBSelectModal.tsx
+214
-0
client/src/pages/app/detail/components/OverView.tsx
+89
-86
client/src/pages/app/detail/components/VariableEditModal.tsx
+214
-0
client/src/pages/app/detail/index.tsx
+2
-2
client/src/pages/chat/components/ChatHistorySlider.tsx
+1
-1
client/src/pages/chat/components/SliderApps.tsx
+1
-1
client/src/pages/chat/index.tsx
+1
-1
client/src/store/user.ts
+12
-2
client/src/types/plugin.d.ts
+2
-0
client/src/utils/app.ts
+611
-0
No files found.
client/src/components/ChatBox/index.tsx
View file @
fdcf53ea
...
...
@@ -650,9 +650,9 @@ const ChatBox = (
<
Box
py=
{
'18px'
}
position=
{
'relative'
}
boxShadow=
{
`0 0 10px rgba(0,0,0,0.
1
)`
}
boxShadow=
{
`0 0 10px rgba(0,0,0,0.
2
)`
}
borderTop=
{
[
'1px solid'
,
0
]
}
borderTopColor=
{
'
g
ray.200'
}
borderTopColor=
{
'
myG
ray.200'
}
borderRadius=
{
[
'none'
,
'md'
]
}
backgroundColor=
{
'white'
}
>
...
...
client/src/constants/flow/ModuleTemplate.ts
View file @
fdcf53ea
...
...
@@ -9,6 +9,14 @@ import {
}
from
'./inputTemplate'
;
import
{
rawSearchKey
}
from
'../chat'
;
export
const
ChatModelSystemTip
=
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}'
;
export
const
ChatModelLimitTip
=
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"'
;
export
const
userGuideTip
=
'可以添加特殊的对话前后引导模块,更好的让用户进行对话'
;
export
const
welcomeTextTip
=
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题'
;
export
const
VariableModule
:
AppModuleTemplateItemType
=
{
logo
:
'/imgs/module/variable.png'
,
name
:
'全局变量'
,
...
...
@@ -30,7 +38,7 @@ export const VariableModule: AppModuleTemplateItemType = {
export
const
UserGuideModule
:
AppModuleTemplateItemType
=
{
logo
:
'/imgs/module/userGuide.png'
,
name
:
'用户引导'
,
intro
:
'可以添加特殊的对话前后引导模块,更好的让用户进行对话'
,
intro
:
userGuideTip
,
type
:
AppModuleItemTypeEnum
.
userGuide
,
flowType
:
FlowModuleTypeEnum
.
userGuide
,
inputs
:
[
...
...
@@ -77,7 +85,7 @@ export const HistoryModule: AppModuleTemplateItemType = {
key
:
'maxContext'
,
type
:
FlowInputItemTypeEnum
.
numberInput
,
label
:
'最长记录数'
,
value
:
4
,
value
:
6
,
min
:
0
,
max
:
50
},
...
...
@@ -146,20 +154,16 @@ export const ChatModule: AppModuleTemplateItemType = {
key: 'systemPrompt',
type: FlowInputItemTypeEnum.textarea,
label: '系统提示词',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
description: ChatModelSystemTip,
placeholder: ChatModelSystemTip,
value: ''
},
{
key: 'limitPrompt',
type: FlowInputItemTypeEnum.textarea,
label: '限定词',
description:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
placeholder:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
description: ChatModelLimitTip,
placeholder: ChatModelLimitTip,
value: ''
},
// Input_Template_TFSwitch,
...
...
@@ -191,7 +195,7 @@ export const KBSearchModule: AppModuleTemplateItemType = {
url: '/app/modules/kb/search',
inputs: [
{
key: 'kb
_ids
',
key: 'kb
List
',
type: FlowInputItemTypeEnum.custom,
label: '关联的知识库',
value: [],
...
...
@@ -548,7 +552,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'maxContext',
type: 'numberInput',
label: '最长记录数',
value:
4
,
value:
10
,
min: 0,
max: 50,
connected: false
...
...
@@ -627,7 +631,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'maxContext',
type: 'numberInput',
label: '最长记录数',
value:
4
,
value:
10
,
min: 0,
max: 50,
connected: false
...
...
@@ -788,7 +792,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
...KBSearchModule,
inputs: [
{
key: 'kb
_ids
',
key: 'kb
List
',
type: 'custom',
label: '关联的知识库',
value: [],
...
...
@@ -1100,7 +1104,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'maxContext',
type: 'numberInput',
label: '最长记录数',
value:
4
,
value:
10
,
min: 0,
max: 50,
connected: false
...
...
@@ -1239,7 +1243,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'maxContext',
type: 'numberInput',
label: '最长记录数',
value:
4
,
value:
10
,
min: 0,
max: 50,
connected: false
...
...
@@ -1400,7 +1404,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
...KBSearchModule,
inputs: [
{
key: 'kb
_ids
',
key: 'kb
List
',
type: 'custom',
label: '关联的知识库',
value: [],
...
...
client/src/constants/theme.ts
View file @
fdcf53ea
...
...
@@ -163,6 +163,9 @@ const Textarea: ComponentStyleConfig = {
border
:
'1px solid'
,
borderRadius
:
'base'
,
borderColor
:
'myGray.200'
,
_hover
:
{
borderColor
:
''
},
_focus
:
{
borderColor
:
'myBlue.600'
,
boxShadow
:
'0px 0px 4px #A8DBFF'
,
...
...
client/src/pages/api/admin/initv4.ts
View file @
fdcf53ea
...
...
@@ -53,6 +53,49 @@ const chatTemplate = ({
moduleId
:
'7z5g5h'
},
{
logo
:
'/imgs/module/history.png'
,
name
:
'聊天记录'
,
intro
:
'用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。'
,
type
:
'initInput'
,
flowType
:
'historyNode'
,
url
:
'/app/modules/init/history'
,
inputs
:
[
{
key
:
'maxContext'
,
type
:
'numberInput'
,
label
:
'最长记录数'
,
value
:
4
,
min
:
0
,
max
:
50
,
connected
:
false
},
{
key
:
'history'
,
type
:
'hidden'
,
label
:
'聊天记录'
,
connected
:
false
}
],
outputs
:
[
{
key
:
'history'
,
label
:
'聊天记录'
,
type
:
'source'
,
targets
:
[
{
moduleId
:
'7pacf0'
,
key
:
'history'
}
]
}
],
position
:
{
x
:
452.5466249541586
,
y
:
1276.3930310334215
},
moduleId
:
'xj0c9p'
},
{
logo
:
'/imgs/module/AI.png'
,
name
:
'AI 对话'
,
intro
:
'AI 大模型对话'
,
...
...
@@ -176,49 +219,6 @@ const chatTemplate = ({
y
:
890.014595014464
},
moduleId
:
'7pacf0'
},
{
logo
:
'/imgs/module/history.png'
,
name
:
'聊天记录'
,
intro
:
'用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。'
,
type
:
'initInput'
,
flowType
:
'historyNode'
,
url
:
'/app/modules/init/history'
,
inputs
:
[
{
key
:
'maxContext'
,
type
:
'numberInput'
,
label
:
'最长记录数'
,
value
:
4
,
min
:
0
,
max
:
50
,
connected
:
false
},
{
key
:
'history'
,
type
:
'hidden'
,
label
:
'聊天记录'
,
connected
:
false
}
],
outputs
:
[
{
key
:
'history'
,
label
:
'聊天记录'
,
type
:
'source'
,
targets
:
[
{
moduleId
:
'7pacf0'
,
key
:
'history'
}
]
}
],
position
:
{
x
:
452.5466249541586
,
y
:
1276.3930310334215
},
moduleId
:
'xj0c9p'
}
];
};
...
...
@@ -228,7 +228,7 @@ const kbTemplate = ({
maxToken
,
systemPrompt
,
limitPrompt
,
kb
s
=
[],
kb
List
=
[],
searchSimilarity
,
searchLimit
,
searchEmptyText
...
...
@@ -238,7 +238,7 @@ const kbTemplate = ({
maxToken
:
number
;
systemPrompt
:
string
;
limitPrompt
:
string
;
kb
s
:
string
[];
kb
List
:
{
kbId
:
string
}
[];
searchSimilarity
:
number
;
searchLimit
:
number
;
searchEmptyText
:
string
;
...
...
@@ -446,10 +446,10 @@ const kbTemplate = ({
url
:
'/app/modules/kb/search'
,
inputs
:
[
{
key
:
'kb
_ids
'
,
key
:
'kb
List
'
,
type
:
'custom'
,
label
:
'关联的知识库'
,
value
:
kb
s
,
value
:
kb
List
,
list
:
[],
connected
:
false
},
...
...
@@ -588,54 +588,24 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await
connectToDatabase
();
const
{
limit
=
1000
}
=
req
.
body
as
{
limit
:
number
};
let
skip
=
0
;
const
total
=
await
App
.
countDocuments
();
let
promise
=
Promise
.
resolve
();
console
.
log
(
total
);
// 遍历所有的 app
const
apps
=
await
App
.
find
(
{
chat
:
{
$ne
:
null
},
modules
:
{
$exists
:
false
}
// userId: '63f9a14228d2a688d8dc9e1b'
},
'_id chat'
).
limit
(
limit
);
await
Promise
.
all
(
apps
.
map
(
async
(
app
)
=>
{
if
(
!
app
.
chat
)
return
app
;
const
modules
=
(()
=>
{
if
(
app
.
chat
.
relatedKbs
.
length
===
0
)
{
return
chatTemplate
({
model
:
app
.
chat
.
chatModel
,
temperature
:
app
.
chat
.
temperature
,
maxToken
:
app
.
chat
.
maxToken
,
systemPrompt
:
app
.
chat
.
systemPrompt
,
limitPrompt
:
app
.
chat
.
limitPrompt
});
}
else
{
return
kbTemplate
({
model
:
app
.
chat
.
chatModel
,
temperature
:
app
.
chat
.
temperature
,
maxToken
:
app
.
chat
.
maxToken
,
systemPrompt
:
app
.
chat
.
systemPrompt
,
limitPrompt
:
app
.
chat
.
limitPrompt
,
kbs
:
app
.
chat
.
relatedKbs
,
searchEmptyText
:
app
.
chat
.
searchEmptyText
,
searchLimit
:
app
.
chat
.
searchLimit
,
searchSimilarity
:
app
.
chat
.
searchSimilarity
});
}
})();
await
App
.
findByIdAndUpdate
(
app
.
id
,
{
modules
for
(
let
i
=
0
;
i
<
total
;
i
+=
limit
)
{
const
skipVal
=
skip
;
skip
+=
limit
;
promise
=
promise
.
then
(()
=>
init
(
limit
,
skipVal
))
.
then
(()
=>
{
console
.
log
(
skipVal
);
});
return
modules
;
})
);
}
jsonRes
(
res
,
{
data
:
apps
.
length
});
await
promise
;
jsonRes
(
res
,
{
});
}
catch
(
error
)
{
jsonRes
(
res
,
{
code
:
500
,
...
...
@@ -643,3 +613,51 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
}
async
function
init
(
limit
:
number
,
skip
:
number
)
{
// 遍历 app
const
apps
=
await
App
.
find
(
{
chat
:
{
$ne
:
null
}
// modules: { $exists: false },
// userId: '63f9a14228d2a688d8dc9e1b'
},
'_id chat'
)
.
limit
(
limit
)
.
skip
(
skip
);
return
Promise
.
all
(
apps
.
map
(
async
(
app
)
=>
{
if
(
!
app
.
chat
)
return
app
;
const
modules
=
(()
=>
{
if
(
app
.
chat
.
relatedKbs
.
length
===
0
)
{
return
chatTemplate
({
model
:
app
.
chat
.
chatModel
,
temperature
:
app
.
chat
.
temperature
,
maxToken
:
app
.
chat
.
maxToken
,
systemPrompt
:
app
.
chat
.
systemPrompt
,
limitPrompt
:
app
.
chat
.
limitPrompt
});
}
else
{
return
kbTemplate
({
model
:
app
.
chat
.
chatModel
,
temperature
:
app
.
chat
.
temperature
,
maxToken
:
app
.
chat
.
maxToken
,
systemPrompt
:
app
.
chat
.
systemPrompt
,
limitPrompt
:
app
.
chat
.
limitPrompt
,
kbList
:
app
.
chat
.
relatedKbs
.
map
((
id
)
=>
({
kbId
:
id
})),
searchEmptyText
:
app
.
chat
.
searchEmptyText
,
searchLimit
:
app
.
chat
.
searchLimit
,
searchSimilarity
:
app
.
chat
.
searchSimilarity
});
}
})();
await
App
.
findByIdAndUpdate
(
app
.
id
,
{
modules
});
return
modules
;
})
);
}
client/src/pages/api/app/modules/kb/search.ts
View file @
fdcf53ea
...
...
@@ -9,6 +9,7 @@ import { getVector } from '@/pages/api/openapi/plugin/vector';
import
{
countModelPrice
,
pushTaskBillListItem
}
from
'@/service/events/pushBill'
;
import
{
getModel
}
from
'@/service/utils/data'
;
import
{
authUser
}
from
'@/service/utils/auth'
;
import
type
{
SelectedKbType
}
from
'@/types/plugin'
;
export
type
QuoteItemType
=
{
kb_id
:
string
;
...
...
@@ -18,7 +19,7 @@ export type QuoteItemType = {
source
?:
string
;
};
type
Props
=
{
kb
_ids
:
string
[]
;
kb
List
:
SelectedKbType
;
history
:
ChatItemType
[];
similarity
:
number
;
limit
:
number
;
...
...
@@ -37,19 +38,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
try
{
await
authUser
({
req
,
authRoot
:
true
});
const
{
kb
_ids
=
[],
userChatInput
}
=
req
.
body
as
Props
;
const
{
kb
List
=
[],
userChatInput
}
=
req
.
body
as
Props
;
if
(
!
userChatInput
)
{
throw
new
Error
(
'用户输入为空'
);
}
if
(
!
Array
.
isArray
(
kb
_ids
)
||
kb_ids
.
length
===
0
)
{
if
(
!
Array
.
isArray
(
kb
List
)
||
kbList
.
length
===
0
)
{
throw
new
Error
(
'没有选择知识库'
);
}
const
result
=
await
kbSearch
({
...
req
.
body
,
kb
_ids
,
kb
List
,
userChatInput
});
...
...
@@ -66,7 +67,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
export
async
function
kbSearch
({
kb
_ids
=
[],
kb
List
=
[],
history
=
[],
similarity
=
0.8
,
limit
=
5
,
...
...
@@ -74,12 +75,9 @@ export async function kbSearch({
userChatInput
,
billId
}:
Props
):
Promise
<
Response
>
{
if
(
kb_ids
.
length
===
0
)
return
{
isEmpty
:
true
,
rawSearch
:
[],
quotePrompt
:
undefined
};
if
(
kbList
.
length
===
0
)
{
return
Promise
.
reject
(
'没有选择知识库'
);
}
// get vector
const
vectorModel
=
global
.
vectorModels
[
0
].
model
;
...
...
@@ -93,8 +91,8 @@ export async function kbSearch({
PgClient
.
query
(
`BEGIN;
SET LOCAL ivfflat.probes =
${
global
.
systemEnv
.
pgIvfflatProbe
||
10
}
;
select kb_id,id,q,a,source from modelData where kb_id IN (
${
kb
_ids
.
map
((
item
)
=>
`'
${
item
}
'`
)
select kb_id,id,q,a,source from modelData where kb_id IN (
${
kb
List
.
map
((
item
)
=>
`'
${
item
.
kbId
}
'`
)
.
join
(
','
)}
) AND vector <#> '[
${
vectors
[
0
]}
]' < -
${
similarity
}
order by vector <#> '[
${
vectors
[
0
]
}
]' limit
${
limit
}
;
...
...
client/src/pages/app/detail/components/BasicEdit/index.tsx
0 → 100644
View file @
fdcf53ea
import
React
,
{
useCallback
,
useEffect
,
useMemo
,
useRef
,
useState
}
from
'react'
;
import
{
Box
,
Flex
,
Grid
,
BoxProps
,
Textarea
,
useTheme
,
Table
,
Thead
,
Tbody
,
Tr
,
Th
,
Td
,
TableContainer
,
useDisclosure
,
Button
,
IconButton
}
from
'@chakra-ui/react'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
{
QuestionOutlineIcon
,
SmallAddIcon
}
from
'@chakra-ui/icons'
;
import
{
useForm
,
useFieldArray
}
from
'react-hook-form'
;
import
{
appModules2Form
,
getDefaultAppForm
,
appForm2Modules
,
type
EditFormType
}
from
'@/utils/app'
;
import
{
chatModelList
}
from
'@/store/static'
;
import
{
formatPrice
}
from
'@/utils/user'
;
import
{
ChatModelSystemTip
,
ChatModelLimitTip
,
welcomeTextTip
}
from
'@/constants/flow/ModuleTemplate'
;
import
{
AppModuleItemType
,
VariableItemType
}
from
'@/types/app'
;
import
{
useRequest
}
from
'@/hooks/useRequest'
;
import
{
useConfirm
}
from
'@/hooks/useConfirm'
;
import
{
FlowModuleTypeEnum
}
from
'@/constants/flow'
;
import
{
streamFetch
}
from
'@/api/fetch'
;
import
dynamic
from
'next/dynamic'
;
import
MySelect
from
'@/components/Select'
;
import
MySlider
from
'@/components/Slider'
;
import
MyTooltip
from
'@/components/MyTooltip'
;
import
Avatar
from
'@/components/Avatar'
;
import
MyIcon
from
'@/components/Icon'
;
import
ChatBox
,
{
getSpecialModule
,
type
ComponentRef
,
type
StartChatFnProps
}
from
'@/components/ChatBox'
;
import
{
addVariable
}
from
'../VariableEditModal'
;
import
{
KBSelectModal
,
KbParamsModal
}
from
'../KBSelectModal'
;
const
VariableEditModal
=
dynamic
(()
=>
import
(
'../VariableEditModal'
));
const
Settings
=
({
appId
}:
{
appId
:
string
})
=>
{
const
theme
=
useTheme
();
const
{
appDetail
,
updateAppDetail
,
loadKbList
,
myKbList
}
=
useUserStore
();
const
[
editVariable
,
setEditVariable
]
=
useState
<
VariableItemType
>
();
useQuery
([
'initkb'
,
appId
],
()
=>
loadKbList
());
const
[
refresh
,
setRefresh
]
=
useState
(
false
);
const
{
openConfirm
,
ConfirmChild
}
=
useConfirm
({
title
:
'警告'
,
content
:
'保存后将会覆盖高级编排配置,请确保该应用未使用高级编排功能。'
});
const
{
register
,
setValue
,
getValues
,
reset
,
handleSubmit
,
control
}
=
useForm
<
EditFormType
>
({
defaultValues
:
getDefaultAppForm
()
});
const
{
fields
:
variables
,
append
:
appendVariable
,
remove
:
removeVariable
,
replace
:
replaceVariables
}
=
useFieldArray
({
control
,
name
:
'variables'
});
const
{
fields
:
kbList
,
replace
:
replaceKbList
}
=
useFieldArray
({
control
,
name
:
'kb.list'
});
const
{
isOpen
:
isOpenKbSelect
,
onOpen
:
onOpenKbSelect
,
onClose
:
onCloseKbSelect
}
=
useDisclosure
();
const
{
isOpen
:
isOpenKbParams
,
onOpen
:
onOpenKbParams
,
onClose
:
onCloseKbParams
}
=
useDisclosure
();
const
chatModelSelectList
=
useMemo
(()
=>
{
return
chatModelList
.
map
((
item
)
=>
({
value
:
item
.
model
,
label
:
`
${
item
.
name
}
(
${
formatPrice
(
item
.
price
,
1000
)}
元/1k tokens)`
}));
},
[
refresh
]);
const
tokenLimit
=
useMemo
(()
=>
{
return
(
chatModelList
.
find
((
item
)
=>
item
.
model
===
getValues
(
'chatModel.model'
))?.
contextMaxToken
||
4000
);
},
[
getValues
,
refresh
]);
const
selectedKbList
=
useMemo
(
()
=>
myKbList
.
filter
((
item
)
=>
kbList
.
find
((
kb
)
=>
kb
.
kbId
===
item
.
_id
)),
[
myKbList
,
kbList
]
);
const
appModule2Form
=
useCallback
(()
=>
{
const
formVal
=
appModules2Form
(
appDetail
.
modules
);
reset
(
formVal
);
setRefresh
((
state
)
=>
!
state
);
},
[
appDetail
.
modules
,
reset
]);
const
{
mutate
:
onSubmitSave
,
isLoading
:
isSaving
}
=
useRequest
({
mutationFn
:
async
(
data
:
EditFormType
)
=>
{
const
modules
=
appForm2Modules
(
data
);
await
updateAppDetail
(
appDetail
.
_id
,
{
modules
});
},
successToast
:
'保存成功'
,
errorToast
:
'保存出现异常'
});
useEffect
(()
=>
{
appModule2Form
();
},
[
appModule2Form
]);
const
BoxStyles
:
BoxProps
=
{
bg
:
'myWhite.300'
,
px
:
4
,
py
:
3
,
borderRadius
:
'lg'
,
border
:
theme
.
borders
.
base
};
const
BoxBtnStyles
:
BoxProps
=
{
cursor
:
'pointer'
,
px
:
3
,
py
:
'2px'
,
borderRadius
:
'md'
,
_hover
:
{
bg
:
'myGray.200'
}
};
return
(
<
Flex
flexDirection=
{
'column'
}
h=
{
'100%'
}
borderRight=
{
'1.5px solid'
}
borderColor=
{
'myGray.200'
}
pt=
{
4
}
>
<
Flex
overflowY=
{
'auto'
}
pr=
{
4
}
justifyContent=
{
'space-between'
}
>
<
Box
fontSize=
{
[
'md'
,
'xl'
]
}
fontWeight=
{
'bold'
}
>
应用配置
<
MyTooltip
label=
{
'仅包含基础功能,复杂 agent 功能请使用高级编排。'
}
>
<
QuestionOutlineIcon
ml=
{
2
}
fontSize=
{
'md'
}
/>
</
MyTooltip
>
</
Box
>
<
Button
isLoading=
{
isSaving
}
fontSize=
{
'sm'
}
onClick=
{
openConfirm
(
handleSubmit
((
data
)
=>
onSubmitSave
(
data
)))
}
>
保存并预览
</
Button
>
</
Flex
>
<
Box
flex=
{
'1 0 0'
}
my=
{
4
}
pr=
{
4
}
overflowY=
{
'auto'
}
>
{
/* variable */
}
<
Box
{
...
BoxStyles
}
>
<
Flex
alignItems=
{
'center'
}
>
<
Avatar
src=
{
'/imgs/module/variable.png'
}
objectFit=
{
'contain'
}
w=
{
'18px'
}
/>
<
Box
ml=
{
2
}
flex=
{
1
}
>
变量
</
Box
>
<
Flex
{
...
BoxBtnStyles
}
onClick=
{
()
=>
setEditVariable
(
addVariable
())
}
>
+
 
新增
</
Flex
>
</
Flex
>
<
Box
mt=
{
2
}
borderRadius=
{
'lg'
}
overflow=
{
'hidden'
}
borderWidth=
{
'1px'
}
borderBottom=
"none"
>
<
TableContainer
>
<
Table
bg=
{
'white'
}
>
<
Thead
>
<
Tr
>
<
Th
>
变量名
</
Th
>
<
Th
>
变量 key
</
Th
>
<
Th
>
必填
</
Th
>
<
Th
></
Th
>
</
Tr
>
</
Thead
>
<
Tbody
>
{
variables
.
map
((
item
,
index
)
=>
(
<
Tr
key=
{
item
.
id
}
>
<
Td
>
{
item
.
label
}
</
Td
>
<
Td
>
{
item
.
key
}
</
Td
>
<
Td
>
{
item
.
required
?
'✔'
:
''
}
</
Td
>
<
Td
>
<
MyIcon
mr=
{
3
}
name=
{
'settingLight'
}
w=
{
'16px'
}
cursor=
{
'pointer'
}
onClick=
{
()
=>
setEditVariable
(
item
)
}
/>
<
MyIcon
name=
{
'delete'
}
w=
{
'16px'
}
cursor=
{
'pointer'
}
onClick=
{
()
=>
removeVariable
(
index
)
}
/>
</
Td
>
</
Tr
>
))
}
</
Tbody
>
</
Table
>
</
TableContainer
>
</
Box
>
</
Box
>
<
Box
mt=
{
5
}
{
...
BoxStyles
}
>
<
Flex
alignItems=
{
'center'
}
>
<
Avatar
src=
{
'/imgs/module/AI.png'
}
w=
{
'18px'
}
/>
<
Box
ml=
{
2
}
>
AI 配置
</
Box
>
</
Flex
>
<
Flex
alignItems=
{
'center'
}
mt=
{
5
}
>
<
Box
w=
{
[
'60px'
,
'100px'
]
}
flexShrink=
{
0
}
>
对话模型
</
Box
>
<
MySelect
width=
{
[
'100%'
,
'300px'
]
}
value=
{
getValues
(
'chatModel.model'
)
}
list=
{
chatModelSelectList
}
onchange=
{
(
val
:
any
)
=>
{
setValue
(
'chatModel.model'
,
val
);
const
maxToken
=
chatModelList
.
find
((
item
)
=>
item
.
model
===
getValues
(
'chatModel.model'
))
?.
contextMaxToken
||
4000
;
const
token
=
maxToken
/
2
;
setValue
(
'chatModel.maxToken'
,
token
);
setRefresh
(
!
refresh
);
}
}
/>
</
Flex
>
<
Flex
alignItems=
{
'center'
}
my=
{
10
}
>
<
Box
w=
{
[
'60px'
,
'100px'
]
}
flexShrink=
{
0
}
>
温度
</
Box
>
<
Box
flex=
{
1
}
ml=
{
'10px'
}
>
<
MySlider
markList=
{
[
{
label
:
'严谨'
,
value
:
0
},
{
label
:
'发散'
,
value
:
10
}
]
}
width=
{
'95%'
}
min=
{
0
}
max=
{
10
}
value=
{
getValues
(
'chatModel.temperature'
)
}
onChange=
{
(
e
)
=>
{
setValue
(
'chatModel.temperature'
,
e
);
setRefresh
(
!
refresh
);
}
}
/>
</
Box
>
</
Flex
>
<
Flex
alignItems=
{
'center'
}
mt=
{
12
}
mb=
{
10
}
>
<
Box
w=
{
[
'60px'
,
'100px'
]
}
flexShrink=
{
0
}
>
回复上限
</
Box
>
<
Box
flex=
{
1
}
ml=
{
'10px'
}
>
<
MySlider
markList=
{
[
{
label
:
'100'
,
value
:
100
},
{
label
:
`${tokenLimit}`
,
value
:
tokenLimit
}
]
}
width=
{
'95%'
}
min=
{
100
}
max=
{
tokenLimit
}
step=
{
50
}
value=
{
getValues
(
'chatModel.maxToken'
)
}
onChange=
{
(
val
)
=>
{
setValue
(
'chatModel.maxToken'
,
val
);
setRefresh
(
!
refresh
);
}
}
/>
</
Box
>
</
Flex
>
<
Flex
mt=
{
10
}
alignItems=
{
'flex-start'
}
>
<
Box
w=
{
[
'60px'
,
'100px'
]
}
flexShrink=
{
0
}
>
提示词
<
MyTooltip
label=
{
ChatModelSystemTip
}
>
<
QuestionOutlineIcon
display=
{
[
'none'
,
'inline'
]
}
ml=
{
1
}
/>
</
MyTooltip
>
</
Box
>
<
Textarea
rows=
{
5
}
placeholder=
{
ChatModelSystemTip
}
borderColor=
{
'myGray.100'
}
{
...
register
('
chatModel
.
systemPrompt
')}
></
Textarea
>
</
Flex
>
<
Flex
mt=
{
5
}
alignItems=
{
'flex-start'
}
>
<
Box
w=
{
[
'60px'
,
'100px'
]
}
flexShrink=
{
0
}
>
限定词
<
MyTooltip
label=
{
ChatModelLimitTip
}
>
<
QuestionOutlineIcon
display=
{
[
'none'
,
'inline'
]
}
ml=
{
1
}
/>
</
MyTooltip
>
</
Box
>
<
Textarea
rows=
{
5
}
placeholder=
{
ChatModelLimitTip
}
borderColor=
{
'myGray.100'
}
{
...
register
('
chatModel
.
limitPrompt
')}
></
Textarea
>
</
Flex
>
</
Box
>
{
/* kb */
}
<
Box
mt=
{
5
}
{
...
BoxStyles
}
>
<
Flex
alignItems=
{
'center'
}
>
<
Flex
alignItems=
{
'center'
}
flex=
{
1
}
>
<
Avatar
src=
{
'/imgs/module/db.png'
}
w=
{
'18px'
}
/>
<
Box
ml=
{
2
}
>
知识库
</
Box
>
</
Flex
>
<
Flex
alignItems=
{
'center'
}
mr=
{
3
}
{
...
BoxBtnStyles
}
onClick=
{
onOpenKbSelect
}
>
<
SmallAddIcon
/>
选择
</
Flex
>
<
Flex
alignItems=
{
'center'
}
{
...
BoxBtnStyles
}
onClick=
{
onOpenKbParams
}
>
<
MyIcon
name=
{
'edit'
}
w=
{
'14px'
}
mr=
{
1
}
/>
参数
</
Flex
>
</
Flex
>
<
Flex
mt=
{
1
}
color=
{
'myGray.600'
}
fontSize=
{
[
'sm'
,
'md'
]
}
>
相似度:
{
getValues
(
'kb.searchSimilarity'
)
}
, 单次搜索数量:
{
getValues
(
'kb.searchLimit'
)
}
,
空搜索时拒绝回复:
{
getValues
(
'kb.searchEmptyText'
)
!==
''
?
'true'
:
'false'
}
</
Flex
>
<
Grid
templateColumns=
{
[
'1fr'
,
'repeat(2,1fr)'
]
}
my=
{
2
}
gridGap=
{
[
2
,
4
]
}
>
{
selectedKbList
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
_id
}
alignItems=
{
'center'
}
p=
{
2
}
bg=
{
'white'
}
boxShadow=
{
'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'
}
borderRadius=
{
'md'
}
border=
{
theme
.
borders
.
base
}
>
<
Avatar
src=
{
item
.
avatar
}
w=
{
'18px'
}
mr=
{
1
}
/>
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
className=
{
'textEllipsis'
}
fontSize=
{
'sm'
}
>
{
item
.
name
}
</
Box
>
</
Flex
>
))
}
</
Grid
>
</
Box
>
{
/* welcome */
}
<
Box
mt=
{
5
}
{
...
BoxStyles
}
>
<
Flex
alignItems=
{
'center'
}
>
<
Avatar
src=
{
'/imgs/module/userGuide.png'
}
w=
{
'18px'
}
/>
<
Box
mx=
{
2
}
>
对话开场白
</
Box
>
<
MyTooltip
label=
{
welcomeTextTip
}
>
<
QuestionOutlineIcon
/>
</
MyTooltip
>
</
Flex
>
<
Textarea
mt=
{
2
}
rows=
{
5
}
placeholder=
{
welcomeTextTip
}
borderColor=
{
'myGray.100'
}
{
...
register
('
guide
.
welcome
.
text
')}
/>
</
Box
>
</
Box
>
<
ConfirmChild
/>
{
editVariable
&&
(
<
VariableEditModal
defaultVariable=
{
editVariable
}
onClose=
{
()
=>
setEditVariable
(
undefined
)
}
onSubmit=
{
({
variable
})
=>
{
const
record
=
variables
.
find
((
item
)
=>
item
.
id
===
variable
.
id
);
if
(
record
)
{
replaceVariables
(
variables
.
map
((
item
)
=>
(
item
.
id
===
variable
.
id
?
variable
:
item
))
);
}
else
{
appendVariable
(
variable
);
}
setEditVariable
(
undefined
);
}
}
/>
)
}
{
isOpenKbSelect
&&
(
<
KBSelectModal
kbList=
{
myKbList
}
activeKbs=
{
selectedKbList
.
map
((
item
)
=>
({
kbId
:
item
.
_id
}))
}
onClose=
{
onCloseKbSelect
}
onChange=
{
replaceKbList
}
/>
)
}
{
isOpenKbParams
&&
(
<
KbParamsModal
searchEmptyText=
{
getValues
(
'kb.searchEmptyText'
)
}
searchLimit=
{
getValues
(
'kb.searchLimit'
)
}
searchSimilarity=
{
getValues
(
'kb.searchSimilarity'
)
}
onClose=
{
onCloseKbParams
}
onChange=
{
({
searchEmptyText
,
searchLimit
,
searchSimilarity
})
=>
{
setValue
(
'kb.searchEmptyText'
,
searchEmptyText
);
setValue
(
'kb.searchLimit'
,
searchLimit
);
setValue
(
'kb.searchSimilarity'
,
searchSimilarity
);
setRefresh
((
state
)
=>
!
state
);
}
}
/>
)
}
</
Flex
>
);
};
const
ChatTest
=
({
appId
}:
{
appId
:
string
})
=>
{
const
{
appDetail
}
=
useUserStore
();
const
ChatBoxRef
=
useRef
<
ComponentRef
>
(
null
);
const
[
modules
,
setModules
]
=
useState
<
AppModuleItemType
[]
>
([]);
const
startChat
=
useCallback
(
async
({
messages
,
controller
,
generatingMessage
,
variables
}:
StartChatFnProps
)
=>
{
const
historyMaxLen
=
modules
?.
find
((
item
)
=>
item
.
flowType
===
FlowModuleTypeEnum
.
historyNode
)
?.
inputs
?.
find
((
item
)
=>
item
.
key
===
'maxContext'
)?.
value
||
0
;
const
history
=
messages
.
slice
(
-
historyMaxLen
-
2
,
-
2
);
// 流请求,获取数据
const
{
responseText
,
rawSearch
}
=
await
streamFetch
({
url
:
'/api/chat/chatTest'
,
data
:
{
history
,
prompt
:
messages
[
messages
.
length
-
2
].
content
,
modules
,
variables
,
appId
,
appName
:
`调试-
${
appDetail
.
name
}
`
},
onMessage
:
generatingMessage
,
abortSignal
:
controller
});
return
{
responseText
,
rawSearch
};
},
[
modules
,
appId
,
appDetail
.
name
]
);
const
resetChatBox
=
useCallback
(()
=>
{
ChatBoxRef
.
current
?.
resetHistory
([]);
ChatBoxRef
.
current
?.
resetVariables
();
},
[]);
useEffect
(()
=>
{
const
formVal
=
appModules2Form
(
appDetail
.
modules
);
setModules
(
appForm2Modules
(
formVal
));
resetChatBox
();
},
[
appDetail
,
resetChatBox
]);
return
(
<
Flex
flexDirection=
{
'column'
}
h=
{
'100%'
}
pl=
{
4
}
py=
{
4
}
>
<
Flex
>
<
Box
fontSize=
{
[
'md'
,
'xl'
]
}
fontWeight=
{
'bold'
}
flex=
{
1
}
>
调试预览
</
Box
>
<
MyTooltip
label=
{
'重置'
}
>
<
IconButton
className=
"chat"
size=
{
'sm'
}
icon=
{
<
MyIcon
name=
{
'clearLight'
}
w=
{
'14px'
}
/>
}
variant=
{
'base'
}
borderRadius=
{
'md'
}
aria
-
label=
{
'delete'
}
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
();
resetChatBox
();
}
}
/>
</
MyTooltip
>
</
Flex
>
<
Box
flex=
{
1
}
>
<
ChatBox
ref=
{
ChatBoxRef
}
appAvatar=
{
appDetail
.
avatar
}
{
...
getSpecialModule
(
modules
)}
onStartChat=
{
startChat
}
onDelMessage=
{
()
=>
{}
}
/>
</
Box
>
</
Flex
>
);
};
const
BasicEdit
=
({
appId
}:
{
appId
:
string
})
=>
{
return
(
<
Grid
gridTemplateColumns=
{
[
'1fr'
,
'550px 1fr'
]
}
h=
{
'100%'
}
>
<
Settings
appId=
{
appId
}
/>
<
ChatTest
appId=
{
appId
}
/>
</
Grid
>
);
};
export
default
BasicEdit
;
client/src/pages/app/detail/components/Charts/TotalUsage.tsx
View file @
fdcf53ea
...
...
@@ -192,7 +192,7 @@ const TokenUsage = ({ appId }: { appId: string }) => {
}, [screenWidth]);
return (
<Box ref={Dom} w={'100%'} flex={'1 0 0'} h={'100%'} position={'relative'}>
<Box ref={Dom} w={'100%'} flex={'1 0 0'} h={'100%'}
minH={'150px'}
position={'relative'}>
<Loading fixed={false} />
</Box>
);
...
...
client/src/pages/app/detail/components/Edit/components/Nodes/NodeKbSearch.tsx
View file @
fdcf53ea
import
React
from
'react'
;
import
React
,
{
useMemo
,
useState
}
from
'react'
;
import
{
NodeProps
}
from
'reactflow'
;
import
NodeCard
from
'../modules/NodeCard'
;
import
{
FlowModuleItemType
}
from
'@/types/flow'
;
import
{
Flex
,
Box
,
Button
,
useTheme
,
useDisclosure
,
Grid
}
from
'@chakra-ui/react'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
NodeCard
from
'../modules/NodeCard'
;
import
Divider
from
'../modules/Divider'
;
import
Container
from
'../modules/Container'
;
import
RenderInput
from
'../render/RenderInput'
;
import
RenderOutput
from
'../render/RenderOutput'
;
import
KBSelect
from
'../Plugins/KBSelect'
;
import
{
KBSelectModal
}
from
'../../../KBSelectModal'
;
import
type
{
SelectedKbType
}
from
'@/types/plugin'
;
import
Avatar
from
'@/components/Avatar'
;
const
KBSelect
=
({
activeKbs
=
[],
onChange
}:
{
activeKbs
:
SelectedKbType
;
onChange
:
(
e
:
SelectedKbType
)
=>
void
;
})
=>
{
const
theme
=
useTheme
();
const
{
myKbList
,
loadKbList
}
=
useUserStore
();
const
{
isOpen
:
isOpenKbSelect
,
onOpen
:
onOpenKbSelect
,
onClose
:
onCloseKbSelect
}
=
useDisclosure
();
const
showKbList
=
useMemo
(
()
=>
myKbList
.
filter
((
item
)
=>
activeKbs
.
find
((
kb
)
=>
kb
.
kbId
===
item
.
_id
)),
[
myKbList
,
activeKbs
]
);
useQuery
([
'initkb'
],
loadKbList
);
return
(
<>
<
Grid
gridTemplateColumns=
{
'1fr 1fr'
}
gridGap=
{
4
}
>
<
Button
h=
{
'36px'
}
onClick=
{
onOpenKbSelect
}
>
选择知识库
</
Button
>
{
showKbList
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
_id
}
alignItems=
{
'center'
}
h=
{
'36px'
}
border=
{
theme
.
borders
.
base
}
px=
{
2
}
borderRadius=
{
'md'
}
>
<
Avatar
src=
{
item
.
avatar
}
w=
{
'24px'
}
></
Avatar
>
<
Box
ml=
{
3
}
fontWeight=
{
'bold'
}
fontSize=
{
[
'md'
,
'lg'
,
'xl'
]
}
>
{
item
.
name
}
</
Box
>
</
Flex
>
))
}
</
Grid
>
{
isOpenKbSelect
&&
(
<
KBSelectModal
kbList=
{
myKbList
}
activeKbs=
{
activeKbs
}
onChange=
{
onChange
}
onClose=
{
onCloseKbSelect
}
/>
)
}
</>
);
};
const
NodeKbSearch
=
({
data
:
{
moduleId
,
inputs
,
outputs
,
onChangeNode
,
...
props
}
...
...
@@ -20,9 +81,9 @@ const NodeKbSearch = ({
onChangeNode=
{
onChangeNode
}
flowInputList=
{
inputs
}
CustomComponent=
{
{
kb
_ids
:
({
key
,
value
})
=>
(
kb
List
:
({
key
,
value
})
=>
(
<
KBSelect
related
Kbs=
{
value
}
active
Kbs=
{
value
}
onChange=
{
(
e
)
=>
{
onChangeNode
({
moduleId
,
...
...
client/src/pages/app/detail/components/Edit/components/Nodes/NodeUserGuide.tsx
View file @
fdcf53ea
...
...
@@ -7,12 +7,8 @@ import { FlowModuleItemType } from '@/types/flow';
import
Container
from
'../modules/Container'
;
import
{
SystemInputEnum
}
from
'@/constants/app'
;
import
MyIcon
from
'@/components/Icon'
;
import
{
customAlphabet
}
from
'nanoid'
;
const
nanoid
=
customAlphabet
(
'abcdefghijklmnopqrstuvwxyz1234567890'
,
6
);
import
MyTooltip
from
'@/components/MyTooltip'
;
const
welcomePlaceholder
=
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题'
;
import
{
welcomeTextTip
}
from
'@/constants/flow/ModuleTemplate'
;
const
NodeUserGuide
=
({
data
:
{
inputs
,
outputs
,
onChangeNode
,
...
props
}
...
...
@@ -30,7 +26,7 @@ const NodeUserGuide = ({
<
Flex
mb=
{
1
}
alignItems=
{
'center'
}
>
<
MyIcon
name=
{
'welcomeText'
}
mr=
{
2
}
w=
{
'16px'
}
color=
{
'#E74694'
}
/>
<
Box
>
开场白
</
Box
>
<
MyTooltip
label=
{
welcome
Placeholder
}
>
<
MyTooltip
label=
{
welcome
TextTip
}
>
<
QuestionOutlineIcon
display=
{
[
'none'
,
'inline'
]
}
ml=
{
1
}
/>
</
MyTooltip
>
</
Flex
>
...
...
@@ -40,7 +36,7 @@ const NodeUserGuide = ({
resize=
{
'both'
}
defaultValue=
{
welcomeText
}
bg=
{
'myWhite.500'
}
placeholder=
{
welcome
Placeholder
}
placeholder=
{
welcome
TextTip
}
onChange=
{
(
e
)
=>
{
onChangeNode
({
moduleId
:
props
.
moduleId
,
...
...
client/src/pages/app/detail/components/Edit/components/Nodes/NodeVariable.tsx
View file @
fdcf53ea
...
...
@@ -45,7 +45,7 @@ const VariableTypeList = [
{
label
:
'文本'
,
icon
:
'settingLight'
,
key
:
VariableInputEnum
.
input
},
{
label
:
'下拉单选'
,
icon
:
'settingLight'
,
key
:
VariableInputEnum
.
select
}
];
const
defaultVariable
:
VariableItemType
=
{
export
const
defaultVariable
:
VariableItemType
=
{
id
:
nanoid
(),
key
:
'key'
,
label
:
'label'
,
...
...
@@ -66,10 +66,6 @@ const NodeUserGuide = ({
?.
value
as
VariableItemType
[])
||
[],
[
inputs
]
);
const
welcomeText
=
useMemo
(
()
=>
inputs
.
find
((
item
)
=>
item
.
key
===
SystemInputEnum
.
welcomeText
)?.
value
,
[
inputs
]
);
const
[
refresh
,
setRefresh
]
=
useState
(
false
);
const
{
isOpen
,
onClose
,
onOpen
}
=
useDisclosure
();
...
...
client/src/pages/app/detail/components/Edit/components/Plugins/KBSelect.tsx
deleted
100644 → 0
View file @
b7b20a35
import
React
,
{
useState
,
useEffect
,
useMemo
}
from
'react'
;
import
{
Card
,
Flex
,
Box
,
Button
,
Modal
,
ModalOverlay
,
ModalContent
,
ModalBody
,
ModalHeader
,
ModalFooter
,
ModalCloseButton
,
useTheme
,
useDisclosure
,
Grid
}
from
'@chakra-ui/react'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
Avatar
from
'@/components/Avatar'
;
const
KBSelect
=
({
relatedKbs
=
[],
onChange
}:
{
relatedKbs
:
string
[];
onChange
:
(
e
:
string
[])
=>
void
;
})
=>
{
const
theme
=
useTheme
();
const
{
myKbList
,
loadKbList
}
=
useUserStore
();
const
[
selectedIdList
,
setSelectedIdList
]
=
useState
<
string
[]
>
(
relatedKbs
);
const
{
isOpen
:
isOpenKbSelect
,
onOpen
:
onOpenKbSelect
,
onClose
:
onCloseKbSelect
}
=
useDisclosure
();
const
showKbList
=
useMemo
(
()
=>
myKbList
.
filter
((
item
)
=>
relatedKbs
.
includes
(
item
.
_id
)),
[
myKbList
,
relatedKbs
]
);
useEffect
(()
=>
{
loadKbList
();
},
[]);
return
(
<>
<
Grid
gridTemplateColumns=
{
'1fr 1fr'
}
gridGap=
{
4
}
>
<
Button
h=
{
'36px'
}
onClick=
{
onOpenKbSelect
}
>
选择知识库
</
Button
>
{
showKbList
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
_id
}
alignItems=
{
'center'
}
h=
{
'36px'
}
border=
{
theme
.
borders
.
base
}
px=
{
2
}
borderRadius=
{
'md'
}
>
<
Avatar
src=
{
item
.
avatar
}
w=
{
'24px'
}
></
Avatar
>
<
Box
ml=
{
3
}
fontWeight=
{
'bold'
}
fontSize=
{
[
'md'
,
'lg'
,
'xl'
]
}
>
{
item
.
name
}
</
Box
>
</
Flex
>
))
}
</
Grid
>
<
Modal
isOpen=
{
isOpenKbSelect
}
onClose=
{
onCloseKbSelect
}
>
<
ModalOverlay
/>
<
ModalContent
display=
{
'flex'
}
flexDirection=
{
'column'
}
w=
{
'800px'
}
maxW=
{
'90vw'
}
h=
{
[
'90vh'
,
'auto'
]
}
>
<
ModalHeader
>
关联的知识库(
{
selectedIdList
.
length
}
)
</
ModalHeader
>
<
ModalCloseButton
/>
<
ModalBody
flex=
{
[
'1 0 0'
,
'0 0 auto'
]
}
maxH=
{
'80vh'
}
overflowY=
{
'auto'
}
display=
{
'grid'
}
gridTemplateColumns=
{
[
'repeat(1,1fr)'
,
'repeat(2,1fr)'
,
'repeat(3,1fr)'
]
}
gridGap=
{
3
}
>
{
myKbList
.
map
((
item
)
=>
(
<
Card
key=
{
item
.
_id
}
p=
{
3
}
border=
{
theme
.
borders
.
base
}
boxShadow=
{
'sm'
}
h=
{
'80px'
}
cursor=
{
'pointer'
}
order=
{
relatedKbs
.
includes
(
item
.
_id
)
?
0
:
1
}
_hover=
{
{
boxShadow
:
'md'
}
}
{
...
(
selectedIdList
.
includes
(
item
.
_id
)
?
{
bg
:
'
myBlue
.
300'
}
:
{})}
onClick=
{
()
=>
{
let
ids
=
[...
selectedIdList
];
if
(
!
selectedIdList
.
includes
(
item
.
_id
))
{
ids
=
ids
.
concat
(
item
.
_id
);
}
else
{
const
i
=
ids
.
findIndex
((
id
)
=>
id
===
item
.
_id
);
ids
.
splice
(
i
,
1
);
}
ids
=
ids
.
filter
((
id
)
=>
myKbList
.
find
((
item
)
=>
item
.
_id
===
id
));
setSelectedIdList
(
ids
);
}
}
>
<
Flex
alignItems=
{
'center'
}
h=
{
'38px'
}
>
<
Avatar
src=
{
item
.
avatar
}
w=
{
[
'24px'
,
'28px'
,
'32px'
]
}
></
Avatar
>
<
Box
ml=
{
3
}
fontWeight=
{
'bold'
}
fontSize=
{
[
'md'
,
'lg'
,
'xl'
]
}
>
{
item
.
name
}
</
Box
>
</
Flex
>
</
Card
>
))
}
</
ModalBody
>
<
ModalFooter
>
<
Button
onClick=
{
()
=>
{
onCloseKbSelect
();
onChange
(
selectedIdList
);
}
}
>
完成
</
Button
>
</
ModalFooter
>
</
ModalContent
>
</
Modal
>
</>
);
};
export
default
KBSelect
;
client/src/pages/app/detail/components/InfoModal.tsx
View file @
fdcf53ea
...
...
@@ -17,10 +17,11 @@ import {
import
{
useForm
}
from
'react-hook-form'
;
import
{
AppSchema
}
from
'@/types/mongoSchema'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
delModelById
,
putAppById
}
from
'@/api/app'
;
import
{
useSelectFile
}
from
'@/hooks/useSelectFile'
;
import
{
compressImg
}
from
'@/utils/file'
;
import
{
getErrText
}
from
'@/utils/tools'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
useRequest
}
from
'@/hooks/useRequest'
;
import
Avatar
from
'@/components/Avatar'
;
const
InfoModal
=
({
...
...
@@ -30,9 +31,11 @@ const InfoModal = ({
}:
{
defaultApp
:
AppSchema
;
onClose
:
()
=>
void
;
onSuccess
:
()
=>
void
;
onSuccess
?
:
()
=>
void
;
})
=>
{
const
{
toast
}
=
useToast
();
const
{
updateAppDetail
}
=
useUserStore
();
const
{
File
,
onOpen
:
onOpenSelectFile
}
=
useSelectFile
({
fileType
:
'.jpg,.png'
,
multiple
:
false
...
...
@@ -47,31 +50,30 @@ const InfoModal = ({
}
=
useForm
({
defaultValues
:
defaultApp
});
const
[
btnLoading
,
setBtnLoading
]
=
useState
(
false
);
const
[
refresh
,
setRefresh
]
=
useState
(
false
);
// 提交保存模型修改
const
saveSubmitSuccess
=
useCallback
(
async
(
data
:
AppSchema
)
=>
{
setBtnLoading
(
true
);
try
{
await
putAppById
(
data
.
_id
,
{
name
:
data
.
name
,
avatar
:
data
.
avatar
,
intro
:
data
.
intro
,
chat
:
data
.
chat
,
share
:
data
.
share
});
}
catch
(
err
:
any
)
{
toast
({
title
:
err
?.
message
||
'更新失败'
,
status
:
'error'
});
}
setBtnLoading
(
false
);
const
{
mutate
:
saveSubmitSuccess
,
isLoading
:
btnLoading
}
=
useRequest
({
mutationFn
:
async
(
data
:
AppSchema
)
=>
{
await
updateAppDetail
(
data
.
_id
,
{
name
:
data
.
name
,
avatar
:
data
.
avatar
,
intro
:
data
.
intro
,
chat
:
data
.
chat
,
share
:
data
.
share
});
},
[
toast
]
);
onSuccess
()
{
onSuccess
&&
onSuccess
();
onClose
();
toast
({
title
:
'更新成功'
,
status
:
'success'
});
},
errorToast
:
'更新失败'
});
// 提交保存表单失败
const
saveSubmitError
=
useCallback
(()
=>
{
// deep search message
...
...
@@ -91,7 +93,7 @@ const InfoModal = ({
},
[
errors
,
toast
]);
const
saveUpdateModel
=
useCallback
(
()
=>
handleSubmit
(
saveSubmitSuccess
,
saveSubmitError
)(),
()
=>
handleSubmit
(
(
data
)
=>
saveSubmitSuccess
(
data
)
,
saveSubmitError
)(),
[
handleSubmit
,
saveSubmitError
,
saveSubmitSuccess
]
);
...
...
@@ -165,22 +167,7 @@ const InfoModal = ({
<
Button
variant=
{
'base'
}
mr=
{
3
}
onClick=
{
onClose
}
>
取消
</
Button
>
<
Button
isLoading=
{
btnLoading
}
onClick=
{
async
()
=>
{
try
{
await
saveUpdateModel
();
onSuccess
();
onClose
();
toast
({
title
:
'更新成功'
,
status
:
'success'
});
}
catch
(
error
)
{
console
.
log
(
error
);
}
}
}
>
<
Button
isLoading=
{
btnLoading
}
onClick=
{
saveUpdateModel
}
>
保存
</
Button
>
</
ModalFooter
>
...
...
client/src/pages/app/detail/components/KBSelectModal.tsx
0 → 100644
View file @
fdcf53ea
import
React
,
{
useState
}
from
'react'
;
import
{
Card
,
Flex
,
Box
,
Button
,
Modal
,
ModalOverlay
,
ModalContent
,
ModalBody
,
ModalHeader
,
ModalFooter
,
ModalCloseButton
,
useTheme
,
Textarea
}
from
'@chakra-ui/react'
;
import
Avatar
from
'@/components/Avatar'
;
import
{
KbListItemType
}
from
'@/types/plugin'
;
import
{
useForm
}
from
'react-hook-form'
;
import
MyTooltip
from
'@/components/MyTooltip'
;
import
{
QuestionOutlineIcon
}
from
'@chakra-ui/icons'
;
import
type
{
SelectedKbType
}
from
'@/types/plugin'
;
import
MySlider
from
'@/components/Slider'
;
export
type
KbParamsType
=
{
searchSimilarity
:
number
;
searchLimit
:
number
;
searchEmptyText
:
string
;
};
export
const
KBSelectModal
=
({
kbList
,
activeKbs
=
[],
onChange
,
onClose
}:
{
kbList
:
KbListItemType
[];
activeKbs
:
SelectedKbType
;
onChange
:
(
e
:
SelectedKbType
)
=>
void
;
onClose
:
()
=>
void
;
})
=>
{
const
theme
=
useTheme
();
const
[
selectedKbList
,
setSelectedKbList
]
=
useState
<
SelectedKbType
>
(
activeKbs
);
return
(
<
Modal
isOpen=
{
true
}
onClose=
{
onClose
}
>
<
ModalOverlay
/>
<
ModalContent
display=
{
'flex'
}
flexDirection=
{
'column'
}
w=
{
'800px'
}
maxW=
{
'90vw'
}
h=
{
[
'90vh'
,
'auto'
]
}
>
<
ModalHeader
>
关联的知识库(
{
selectedKbList
.
length
}
)
</
ModalHeader
>
<
ModalCloseButton
/>
<
ModalBody
flex=
{
[
'1 0 0'
,
'0 0 auto'
]
}
maxH=
{
'80vh'
}
overflowY=
{
'auto'
}
display=
{
'grid'
}
gridTemplateColumns=
{
[
'repeat(1,1fr)'
,
'repeat(2,1fr)'
,
'repeat(3,1fr)'
]
}
gridGap=
{
3
}
>
{
kbList
.
map
((
item
)
=>
(()
=>
{
const
selected
=
!!
selectedKbList
.
find
((
kb
)
=>
kb
.
kbId
===
item
.
_id
);
const
active
=
!!
activeKbs
.
find
((
kb
)
=>
kb
.
kbId
===
item
.
_id
);
return
(
<
Card
key=
{
item
.
_id
}
p=
{
3
}
border=
{
theme
.
borders
.
base
}
boxShadow=
{
'sm'
}
h=
{
'80px'
}
cursor=
{
'pointer'
}
order=
{
active
?
0
:
1
}
_hover=
{
{
boxShadow
:
'md'
}
}
{
...
(
selected
?
{
bg
:
'
myBlue
.
300'
}
:
{})}
onClick=
{
()
=>
{
if
(
selected
)
{
setSelectedKbList
((
state
)
=>
state
.
filter
((
kb
)
=>
kb
.
kbId
!==
item
.
_id
));
}
else
{
setSelectedKbList
((
state
)
=>
[...
state
,
{
kbId
:
item
.
_id
}]);
}
}
}
>
<
Flex
alignItems=
{
'center'
}
h=
{
'38px'
}
>
<
Avatar
src=
{
item
.
avatar
}
w=
{
[
'24px'
,
'28px'
,
'32px'
]
}
></
Avatar
>
<
Box
ml=
{
3
}
fontWeight=
{
'bold'
}
fontSize=
{
[
'md'
,
'lg'
,
'xl'
]
}
>
{
item
.
name
}
</
Box
>
</
Flex
>
</
Card
>
);
})()
)
}
</
ModalBody
>
<
ModalFooter
>
<
Button
onClick=
{
()
=>
{
onClose
();
onChange
(
selectedKbList
);
}
}
>
完成
</
Button
>
</
ModalFooter
>
</
ModalContent
>
</
Modal
>
);
};
export
const
KbParamsModal
=
({
searchEmptyText
,
searchLimit
,
searchSimilarity
,
onClose
,
onChange
}:
KbParamsType
&
{
onClose
:
()
=>
void
;
onChange
:
(
e
:
KbParamsType
)
=>
void
})
=>
{
const
[
refresh
,
setRefresh
]
=
useState
(
false
);
const
{
register
,
setValue
,
getValues
,
handleSubmit
}
=
useForm
<
KbParamsType
>
({
defaultValues
:
{
searchEmptyText
,
searchLimit
,
searchSimilarity
}
});
return
(
<
Modal
isOpen=
{
true
}
onClose=
{
onClose
}
>
<
ModalOverlay
/>
<
ModalContent
display=
{
'flex'
}
flexDirection=
{
'column'
}
w=
{
'600px'
}
maxW=
{
'90vw'
}
>
<
ModalHeader
>
搜索参数调整
</
ModalHeader
>
<
ModalCloseButton
/>
<
ModalBody
>
<
Flex
pt=
{
3
}
pb=
{
5
}
>
<
Box
flex=
{
'0 0 100px'
}
>
相似度
<
MyTooltip
label=
{
'高相似度推荐0.8及以上。'
}
>
<
QuestionOutlineIcon
ml=
{
1
}
/>
</
MyTooltip
>
</
Box
>
<
MySlider
markList=
{
[
{
label
:
'0'
,
value
:
0
},
{
label
:
'1'
,
value
:
1
}
]
}
min=
{
0
}
max=
{
1
}
step=
{
0.01
}
value=
{
getValues
(
'searchSimilarity'
)
}
onChange=
{
(
val
)
=>
{
setValue
(
'searchSimilarity'
,
val
);
setRefresh
(
!
refresh
);
}
}
/>
</
Flex
>
<
Flex
py=
{
8
}
>
<
Box
flex=
{
'0 0 100px'
}
>
单次搜索数量
</
Box
>
<
Box
flex=
{
1
}
>
<
MySlider
markList=
{
[
{
label
:
'1'
,
value
:
1
},
{
label
:
'20'
,
value
:
20
}
]
}
min=
{
1
}
max=
{
20
}
value=
{
getValues
(
'searchLimit'
)
}
onChange=
{
(
val
)
=>
{
setValue
(
'searchLimit'
,
val
);
setRefresh
(
!
refresh
);
}
}
/>
</
Box
>
</
Flex
>
<
Flex
pt=
{
3
}
>
<
Box
flex=
{
'0 0 100px'
}
>
空搜索回复
</
Box
>
<
Box
flex=
{
1
}
>
<
Textarea
rows=
{
5
}
maxLength=
{
500
}
placeholder=
{
'若填写该内容,没有搜索到对应内容时,将直接回复填写的内容。\n为了连贯上下文,FastGpt 会取部分上一个聊天的搜索记录作为补充,因此在连续对话时,该功能可能会失效。'
}
{
...
register
('
searchEmptyText
')}
></
Textarea
>
</
Box
>
</
Flex
>
</
ModalBody
>
<
ModalFooter
>
<
Button
variant=
{
'base'
}
mr=
{
3
}
onClick=
{
onClose
}
>
取消
</
Button
>
<
Button
onClick=
{
()
=>
{
onClose
();
handleSubmit
(
onChange
)();
}
}
>
完成
</
Button
>
</
ModalFooter
>
</
ModalContent
>
</
Modal
>
);
};
client/src/pages/app/detail/components/OverView.tsx
View file @
fdcf53ea
...
...
@@ -7,12 +7,13 @@ import { useToast } from '@/hooks/useToast';
import
{
useLoading
}
from
'@/hooks/useLoading'
;
import
{
delModelById
}
from
'@/api/app'
;
import
{
useConfirm
}
from
'@/hooks/useConfirm'
;
import
dynamic
from
'next/dynamic'
;
import
{
AppSchema
}
from
'@/types/mongoSchema'
;
import
dynamic
from
'next/dynamic'
;
import
Avatar
from
'@/components/Avatar'
;
import
MyIcon
from
'@/components/Icon'
;
import
TotalUsage
from
'./Charts/TotalUsage'
;
import
BasicEdit
from
'./BasicEdit'
;
const
InfoModal
=
dynamic
(()
=>
import
(
'./InfoModal'
));
...
...
@@ -54,98 +55,100 @@ const OverView = ({ appId }: { appId: string }) => {
return
(
<
Flex
h=
{
'100%'
}
flexDirection=
{
'column'
}
position=
{
'relative'
}
>
<
Box
w=
{
'100%'
}
pt=
{
[
0
,
7
]
}
px=
{
[
3
,
5
,
8
]
}
>
<
Grid
gridTemplateColumns=
{
[
'1fr'
,
'repeat(2,1fr)'
]
}
gridGap=
{
[
2
,
4
,
6
]
}
>
<
Box
>
<
Box
mb=
{
2
}
fontSize=
{
[
'md'
,
'xl'
]
}
>
基本信息
</
Box
>
<
Box
border=
{
theme
.
borders
.
base
}
borderRadius=
{
'lg'
}
px=
{
5
}
py=
{
4
}
bg=
{
'myBlue.100'
}
position=
{
'relative'
}
>
<
Flex
alignItems=
{
'center'
}
py=
{
2
}
>
<
Avatar
src=
{
appDetail
.
avatar
}
borderRadius=
{
'md'
}
w=
{
'28px'
}
/>
<
Box
ml=
{
3
}
fontWeight=
{
'bold'
}
fontSize=
{
'lg'
}
>
{
appDetail
.
name
}
</
Box
>
<
IconButton
className=
"delete"
position=
{
'absolute'
}
top=
{
4
}
right=
{
4
}
size=
{
'sm'
}
icon=
{
<
MyIcon
name=
{
'delete'
}
w=
{
'14px'
}
/>
}
variant=
{
'base'
}
borderRadius=
{
'md'
}
aria
-
label=
{
'delete'
}
_hover=
{
{
bg
:
'myGray.100'
,
color
:
'red.600'
}
}
onClick=
{
openConfirm
(
handleDelModel
)
}
/>
</
Flex
>
<
Box
className=
{
'textEllipsis3'
}
py=
{
3
}
wordBreak=
{
'break-all'
}
color=
{
'myGray.600'
}
>
{
appDetail
.
intro
||
'快来给应用一个介绍~'
}
<
Grid
gridTemplateColumns=
{
[
'1fr'
,
'repeat(2,1fr)'
]
}
gridGap=
{
[
2
,
4
,
6
]
}
pt=
{
[
0
,
7
]
}
px=
{
[
3
,
5
,
8
]
}
>
<
Box
>
<
Box
mb=
{
2
}
fontSize=
{
[
'md'
,
'xl'
]
}
>
基本信息
</
Box
>
<
Box
border=
{
theme
.
borders
.
base
}
borderRadius=
{
'lg'
}
px=
{
5
}
py=
{
4
}
bg=
{
'myBlue.100'
}
position=
{
'relative'
}
>
<
Flex
alignItems=
{
'center'
}
py=
{
2
}
>
<
Avatar
src=
{
appDetail
.
avatar
}
borderRadius=
{
'md'
}
w=
{
'28px'
}
/>
<
Box
ml=
{
3
}
fontWeight=
{
'bold'
}
fontSize=
{
'lg'
}
>
{
appDetail
.
name
}
</
Box
>
<
Flex
>
<
Button
size=
{
[
'sm'
,
'md'
]
}
variant=
{
'base'
}
leftIcon=
{
<
MyIcon
name=
{
'chatLight'
}
w=
{
'16px'
}
/>
}
onClick=
{
()
=>
router
.
push
(
`/chat?appId=${appId}`
)
}
>
对话
</
Button
>
<
Button
mx=
{
3
}
size=
{
[
'sm'
,
'md'
]
}
variant=
{
'base'
}
leftIcon=
{
<
MyIcon
name=
{
'shareLight'
}
w=
{
'16px'
}
/>
}
onClick=
{
()
=>
{
router
.
replace
({
query
:
{
appId
,
currentTab
:
'share'
}
});
}
}
>
分享
</
Button
>
<
Button
size=
{
[
'sm'
,
'md'
]
}
variant=
{
'base'
}
leftIcon=
{
<
MyIcon
name=
{
'settingLight'
}
w=
{
'16px'
}
/>
}
onClick=
{
()
=>
setSettingAppInfo
(
appDetail
)
}
>
设置
</
Button
>
</
Flex
>
<
IconButton
className=
"delete"
position=
{
'absolute'
}
top=
{
4
}
right=
{
4
}
size=
{
'sm'
}
icon=
{
<
MyIcon
name=
{
'delete'
}
w=
{
'14px'
}
/>
}
variant=
{
'base'
}
borderRadius=
{
'md'
}
aria
-
label=
{
'delete'
}
_hover=
{
{
bg
:
'myGray.100'
,
color
:
'red.600'
}
}
onClick=
{
openConfirm
(
handleDelModel
)
}
/>
</
Flex
>
<
Box
className=
{
'textEllipsis3'
}
py=
{
3
}
wordBreak=
{
'break-all'
}
color=
{
'myGray.600'
}
>
{
appDetail
.
intro
||
'快来给应用一个介绍~'
}
</
Box
>
<
Flex
>
<
Button
size=
{
[
'sm'
,
'md'
]
}
variant=
{
'base'
}
leftIcon=
{
<
MyIcon
name=
{
'chatLight'
}
w=
{
'16px'
}
/>
}
onClick=
{
()
=>
router
.
push
(
`/chat?appId=${appId}`
)
}
>
对话
</
Button
>
<
Button
mx=
{
3
}
size=
{
[
'sm'
,
'md'
]
}
variant=
{
'base'
}
leftIcon=
{
<
MyIcon
name=
{
'shareLight'
}
w=
{
'16px'
}
/>
}
onClick=
{
()
=>
{
router
.
replace
({
query
:
{
appId
,
currentTab
:
'share'
}
});
}
}
>
分享
</
Button
>
<
Button
size=
{
[
'sm'
,
'md'
]
}
variant=
{
'base'
}
leftIcon=
{
<
MyIcon
name=
{
'settingLight'
}
w=
{
'16px'
}
/>
}
onClick=
{
()
=>
setSettingAppInfo
(
appDetail
)
}
>
设置
</
Button
>
</
Flex
>
</
Box
>
<
Flex
flexDirection=
{
'column'
}
>
<
Box
mb=
{
2
}
fontSize=
{
[
'md'
,
'xl'
]
}
>
近 14 日消费
</
Box
>
<
TotalUsage
appId=
{
appId
}
/>
</
Flex
>
</
Grid
>
</
Box
>
<
Flex
flexDirection=
{
'column'
}
>
<
Box
mb=
{
2
}
fontSize=
{
[
'md'
,
'xl'
]
}
>
近 14 日消费
</
Box
>
<
TotalUsage
appId=
{
appId
}
/>
</
Flex
>
</
Grid
>
<
Box
flex=
{
'1 0 0'
}
h=
{
0
}
mt=
{
4
}
borderTop=
{
theme
.
borders
.
base
}
px=
{
[
3
,
5
,
8
]
}
>
<
BasicEdit
appId=
{
appId
}
/>
</
Box
>
{
settingAppInfo
&&
(
<
InfoModal
defaultApp=
{
settingAppInfo
}
onClose=
{
()
=>
setSettingAppInfo
(
undefined
)
}
onSuccess=
{
refetch
}
/>
<
InfoModal
defaultApp=
{
settingAppInfo
}
onClose=
{
()
=>
setSettingAppInfo
(
undefined
)
}
/>
)
}
<
ConfirmChild
/>
<
Loading
fixed=
{
false
}
/>
</
Flex
>
...
...
client/src/pages/app/detail/components/VariableEditModal.tsx
0 → 100644
View file @
fdcf53ea
import
React
,
{
useState
}
from
'react'
;
import
{
Box
,
Button
,
Modal
,
ModalOverlay
,
ModalContent
,
ModalHeader
,
ModalFooter
,
ModalBody
,
NumberInput
,
NumberInputField
,
NumberInputStepper
,
NumberIncrementStepper
,
NumberDecrementStepper
,
Flex
,
Switch
,
Input
,
Grid
,
FormControl
,
useTheme
}
from
'@chakra-ui/react'
;
import
{
SmallAddIcon
}
from
'@chakra-ui/icons'
;
import
{
VariableInputEnum
}
from
'@/constants/app'
;
import
type
{
VariableItemType
}
from
'@/types/app'
;
import
MyIcon
from
'@/components/Icon'
;
import
{
useForm
}
from
'react-hook-form'
;
import
{
useFieldArray
}
from
'react-hook-form'
;
import
{
customAlphabet
}
from
'nanoid'
;
const
nanoid
=
customAlphabet
(
'abcdefghijklmnopqrstuvwxyz1234567890'
,
6
);
const
VariableTypeList
=
[
{
label
:
'文本'
,
icon
:
'settingLight'
,
key
:
VariableInputEnum
.
input
},
{
label
:
'下拉单选'
,
icon
:
'settingLight'
,
key
:
VariableInputEnum
.
select
}
];
export
type
VariableFormType
=
{
variable
:
VariableItemType
;
};
const
VariableEditModal
=
({
defaultVariable
,
onClose
,
onSubmit
}:
{
defaultVariable
:
VariableItemType
;
onClose
:
()
=>
void
;
onSubmit
:
(
data
:
VariableFormType
)
=>
void
;
})
=>
{
const
theme
=
useTheme
();
const
[
refresh
,
setRefresh
]
=
useState
(
false
);
const
{
reset
,
getValues
,
setValue
,
register
,
control
,
handleSubmit
}
=
useForm
<
VariableFormType
>
(
{
defaultValues
:
{
variable
:
defaultVariable
}
}
);
const
{
fields
:
selectEnums
,
append
:
appendEnums
,
remove
:
removeEnums
}
=
useFieldArray
({
control
,
name
:
'variable.enums'
});
return
(
<
Modal
isOpen=
{
true
}
onClose=
{
onClose
}
>
<
ModalOverlay
/>
<
ModalContent
maxW=
{
'Min(400px,90vw)'
}
>
<
ModalHeader
display=
{
'flex'
}
>
<
MyIcon
name=
{
'variable'
}
mr=
{
2
}
w=
{
'24px'
}
color=
{
'#FF8A4C'
}
/>
变量设置
</
ModalHeader
>
<
ModalBody
>
<
Flex
alignItems=
{
'center'
}
>
<
Box
w=
{
'70px'
}
>
必填
</
Box
>
<
Switch
{
...
register
('
variable
.
required
')}
/>
</
Flex
>
<
Flex
mt=
{
5
}
alignItems=
{
'center'
}
>
<
Box
w=
{
'80px'
}
>
变量名
</
Box
>
<
Input
{
...
register
('
variable
.
label
',
{
required
:
'变量名不能为空'
})}
/>
</
Flex
>
<
Flex
mt=
{
5
}
alignItems=
{
'center'
}
>
<
Box
w=
{
'80px'
}
>
变量 key
</
Box
>
<
Input
{
...
register
('
variable
.
key
',
{
required
:
'变量
key
不能为空'
})}
/>
</
Flex
>
<
Box
mt=
{
5
}
mb=
{
2
}
>
字段类型
</
Box
>
<
Grid
gridTemplateColumns=
{
'repeat(2,130px)'
}
gridGap=
{
4
}
>
{
VariableTypeList
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
key
}
px=
{
4
}
py=
{
1
}
border=
{
theme
.
borders
.
base
}
borderRadius=
{
'md'
}
cursor=
{
'pointer'
}
{
...
(
item
.
key
===
getValues
('
variable
.
type
')
?
{
bg
:
'
myWhite
.
600'
}
:
{
_hover
:
{
boxShadow
:
'
md
'
},
onClick
:
()
=
>
{
setValue
(
'variable.type'
,
item
.
key
);
setRefresh
(
!
refresh
);
}
})}
>
<
MyIcon
name=
{
item
.
icon
as
any
}
w=
{
'16px'
}
/>
<
Box
ml=
{
3
}
>
{
item
.
label
}
</
Box
>
</
Flex
>
))
}
</
Grid
>
{
getValues
(
'variable.type'
)
===
VariableInputEnum
.
input
&&
(
<>
<
Box
mt=
{
5
}
mb=
{
2
}
>
最大长度
</
Box
>
<
Box
>
<
NumberInput
max=
{
100
}
min=
{
1
}
step=
{
1
}
position=
{
'relative'
}
>
<
NumberInputField
{
...
register
('
variable
.
maxLen
',
{
min
:
1,
max
:
100,
valueAsNumber
:
true
})}
max=
{
100
}
/>
<
NumberInputStepper
>
<
NumberIncrementStepper
/>
<
NumberDecrementStepper
/>
</
NumberInputStepper
>
</
NumberInput
>
</
Box
>
</>
)
}
{
getValues
(
'variable.type'
)
===
VariableInputEnum
.
select
&&
(
<>
<
Box
mt=
{
5
}
mb=
{
2
}
>
选项
</
Box
>
<
Box
>
{
selectEnums
.
map
((
item
,
i
)
=>
(
<
Flex
key=
{
item
.
id
}
mb=
{
2
}
alignItems=
{
'center'
}
>
<
FormControl
>
<
Input
{
...
register
(`
variable
.
enums
.
$
{
i
}
.
value
`,
{
required
:
'选项内容不能为空'
})}
/>
</
FormControl
>
<
MyIcon
ml=
{
3
}
name=
{
'delete'
}
w=
{
'16px'
}
cursor=
{
'pointer'
}
p=
{
2
}
borderRadius=
{
'lg'
}
_hover=
{
{
bg
:
'red.100'
}
}
onClick=
{
()
=>
removeEnums
(
i
)
}
/>
</
Flex
>
))
}
</
Box
>
<
Button
variant=
{
'solid'
}
w=
{
'100%'
}
textAlign=
{
'left'
}
leftIcon=
{
<
SmallAddIcon
/>
}
bg=
{
'myGray.100 !important'
}
onClick=
{
()
=>
appendEnums
({
value
:
''
})
}
>
添加选项
</
Button
>
</>
)
}
</
ModalBody
>
<
ModalFooter
>
<
Button
variant=
{
'base'
}
mr=
{
3
}
onClick=
{
onClose
}
>
取消
</
Button
>
<
Button
onClick=
{
handleSubmit
(
onSubmit
)
}
>
确认
</
Button
>
</
ModalFooter
>
</
ModalContent
>
</
Modal
>
);
};
export
default
React
.
memo
(
VariableEditModal
);
export
const
defaultVariable
:
VariableItemType
=
{
id
:
nanoid
(),
key
:
'key'
,
label
:
'label'
,
type
:
VariableInputEnum
.
input
,
required
:
true
,
maxLen
:
50
,
enums
:
[{
value
:
''
}]
};
export
const
addVariable
=
()
=>
{
const
newVariable
=
{
...
defaultVariable
,
id
:
nanoid
()
};
return
newVariable
;
};
client/src/pages/app/detail/index.tsx
View file @
fdcf53ea
...
...
@@ -54,7 +54,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const
tabList
=
useMemo
(
()
=>
[
{
label
:
'
概览
'
,
id
:
TabEnum
.
overview
,
icon
:
'overviewLight'
},
{
label
:
'
基础
'
,
id
:
TabEnum
.
overview
,
icon
:
'overviewLight'
},
{
label
:
'高级编排'
,
id
:
TabEnum
.
settings
,
icon
:
'settingLight'
},
{
label
:
'链接分享'
,
id
:
TabEnum
.
share
,
icon
:
'shareLight'
},
{
label
:
'API访问'
,
id
:
TabEnum
.
API
,
icon
:
'apiLight'
},
...
...
@@ -150,7 +150,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<
Tabs
mx=
{
'auto'
}
mt=
{
2
}
w=
{
'
300px
'
}
w=
{
'
100%
'
}
list=
{
tabList
}
size=
{
'sm'
}
activeId=
{
currentTab
}
...
...
client/src/pages/chat/components/ChatHistorySlider.tsx
View file @
fdcf53ea
...
...
@@ -79,7 +79,7 @@ const ChatHistorySlider = ({
cursor=
{
appId
?
'pointer'
:
'default'
}
onClick=
{
()
=>
appId
&&
router
.
push
({
router
.
replace
({
pathname
:
'/app/detail'
,
query
:
{
appId
}
})
...
...
client/src/pages/chat/components/SliderApps.tsx
View file @
fdcf53ea
...
...
@@ -21,7 +21,7 @@ const SliderApps = ({ appId }: { appId: string }) => {
px=
{
3
}
borderRadius=
{
'md'
}
_hover=
{
{
bg
:
'myGray.200'
}
}
onClick=
{
()
=>
router
.
push
(
'/app/list'
)
}
onClick=
{
()
=>
router
.
back
(
)
}
>
<
IconButton
mr=
{
3
}
...
...
client/src/pages/chat/index.tsx
View file @
fdcf53ea
...
...
@@ -239,7 +239,7 @@ const Chat = () => {
top
:
item
.
top
}))
}
onChangeChat=
{
(
chatId
)
=>
{
router
.
push
({
router
.
replace
({
query
:
{
chatId
:
chatId
||
''
,
appId
...
...
client/src/store/user.ts
View file @
fdcf53ea
...
...
@@ -2,11 +2,11 @@ import { create } from 'zustand';
import
{
devtools
,
persist
}
from
'zustand/middleware'
;
import
{
immer
}
from
'zustand/middleware/immer'
;
import
type
{
UserType
,
UserUpdateParams
}
from
'@/types/user'
;
import
{
getMyModels
,
getModelById
}
from
'@/api/app'
;
import
{
getMyModels
,
getModelById
,
putAppById
}
from
'@/api/app'
;
import
{
formatPrice
}
from
'@/utils/user'
;
import
{
getTokenLogin
}
from
'@/api/user'
;
import
{
defaultApp
}
from
'@/constants/model'
;
import
{
AppListItemType
}
from
'@/types/app'
;
import
{
AppListItemType
,
AppUpdateParams
}
from
'@/types/app'
;
import
type
{
KbItemType
,
KbListItemType
}
from
'@/types/plugin'
;
import
{
getKbList
,
getKbById
}
from
'@/api/plugins/kb'
;
import
{
defaultKbDetail
}
from
'@/constants/kb'
;
...
...
@@ -22,6 +22,7 @@ type State = {
loadMyModels
:
()
=>
Promise
<
null
>
;
appDetail
:
AppSchema
;
loadAppDetail
:
(
id
:
string
,
init
?:
boolean
)
=>
Promise
<
AppSchema
>
;
updateAppDetail
(
appId
:
string
,
data
:
AppUpdateParams
):
Promise
<
void
>
;
clearAppModules
():
void
;
// kb
myKbList
:
KbListItemType
[];
...
...
@@ -79,6 +80,15 @@ export const useUserStore = create<State>()(
});
return
res
;
},
async
updateAppDetail
(
appId
:
string
,
data
:
AppUpdateParams
)
{
await
putAppById
(
appId
,
data
);
set
((
state
)
=>
{
state
.
appDetail
=
{
...
state
.
appDetail
,
...
data
};
});
},
clearAppModules
()
{
set
((
state
)
=>
{
state
.
appDetail
=
{
...
...
client/src/types/plugin.d.ts
View file @
fdcf53ea
import
type
{
kbSchema
}
from
'./mongoSchema'
;
export
type
SelectedKbType
=
{
kbId
:
string
}[];
export
type
KbListItemType
=
{
_id
:
string
;
avatar
:
string
;
...
...
client/src/utils/app.ts
0 → 100644
View file @
fdcf53ea
import
type
{
AppModuleItemType
,
VariableItemType
}
from
'@/types/app'
;
import
{
chatModelList
,
vectorModelList
}
from
'@/store/static'
;
import
{
FlowModuleTypeEnum
}
from
'@/constants/flow'
;
import
{
FlowInputItemType
}
from
'@/types/flow'
;
import
{
SystemInputEnum
}
from
'@/constants/app'
;
import
type
{
SelectedKbType
}
from
'@/types/plugin'
;
import
{
VariableModule
,
UserGuideModule
,
ChatModule
,
HistoryModule
,
UserInputModule
,
KBSearchModule
,
AnswerModule
}
from
'@/constants/flow/ModuleTemplate'
;
import
{
rawSearchKey
}
from
'@/constants/chat'
;
export
type
EditFormType
=
{
chatModel
:
{
model
:
string
;
systemPrompt
:
string
;
limitPrompt
:
string
;
temperature
:
number
;
maxToken
:
number
;
frequency
:
number
;
presence
:
number
;
};
kb
:
{
list
:
SelectedKbType
;
searchSimilarity
:
number
;
searchLimit
:
number
;
searchEmptyText
:
string
;
};
guide
:
{
welcome
:
{
text
:
string
;
};
};
variables
:
VariableItemType
[];
};
export
const
getDefaultAppForm
=
():
EditFormType
=>
{
const
defaultChatModel
=
chatModelList
[
0
];
const
defaultVectorModel
=
vectorModelList
[
0
];
return
{
chatModel
:
{
model
:
defaultChatModel
.
model
,
systemPrompt
:
''
,
limitPrompt
:
''
,
temperature
:
0
,
maxToken
:
defaultChatModel
.
contextMaxToken
/
2
,
frequency
:
0.5
,
presence
:
-
0.5
},
kb
:
{
list
:
[],
searchSimilarity
:
0.8
,
searchLimit
:
5
,
searchEmptyText
:
''
},
guide
:
{
welcome
:
{
text
:
''
}
},
variables
:
[]
};
};
export
const
appModules2Form
=
(
modules
:
AppModuleItemType
[])
=>
{
const
defaultAppForm
=
getDefaultAppForm
();
const
updateVal
=
({
formKey
,
inputs
,
key
}:
{
formKey
:
string
;
inputs
:
FlowInputItemType
[];
key
:
string
;
})
=>
{
const
propertyPath
=
formKey
.
split
(
'.'
);
let
currentObj
:
any
=
defaultAppForm
;
for
(
let
i
=
0
;
i
<
propertyPath
.
length
-
1
;
i
++
)
{
currentObj
=
currentObj
[
propertyPath
[
i
]];
}
const
val
=
inputs
.
find
((
item
)
=>
item
.
key
===
key
)?.
value
||
currentObj
[
propertyPath
[
propertyPath
.
length
-
1
]];
currentObj
[
propertyPath
[
propertyPath
.
length
-
1
]]
=
val
;
};
modules
.
forEach
((
module
)
=>
{
if
(
module
.
flowType
===
FlowModuleTypeEnum
.
chatNode
)
{
updateVal
({
formKey
:
'chatModel.model'
,
inputs
:
module
.
inputs
,
key
:
'model'
});
updateVal
({
formKey
:
'chatModel.temperature'
,
inputs
:
module
.
inputs
,
key
:
'temperature'
});
updateVal
({
formKey
:
'chatModel.maxToken'
,
inputs
:
module
.
inputs
,
key
:
'maxToken'
});
updateVal
({
formKey
:
'chatModel.systemPrompt'
,
inputs
:
module
.
inputs
,
key
:
'systemPrompt'
});
updateVal
({
formKey
:
'chatModel.limitPrompt'
,
inputs
:
module
.
inputs
,
key
:
'limitPrompt'
});
}
else
if
(
module
.
flowType
===
FlowModuleTypeEnum
.
kbSearchNode
)
{
updateVal
({
formKey
:
'kb.list'
,
inputs
:
module
.
inputs
,
key
:
'kbList'
});
updateVal
({
formKey
:
'kb.searchSimilarity'
,
inputs
:
module
.
inputs
,
key
:
'similarity'
});
updateVal
({
formKey
:
'kb.searchLimit'
,
inputs
:
module
.
inputs
,
key
:
'limit'
});
// empty text
const
emptyOutputs
=
module
.
outputs
.
find
((
item
)
=>
item
.
key
===
'isEmpty'
)?.
targets
||
[];
const
emptyOutput
=
emptyOutputs
[
0
];
if
(
emptyOutput
)
{
const
target
=
modules
.
find
((
item
)
=>
item
.
moduleId
===
emptyOutput
.
moduleId
);
defaultAppForm
.
kb
.
searchEmptyText
=
target
?.
inputs
?.
find
((
item
)
=>
item
.
key
===
'answerText'
)?.
value
||
''
;
}
}
else
if
(
module
.
flowType
===
FlowModuleTypeEnum
.
userGuide
)
{
const
val
=
module
.
inputs
.
find
((
item
)
=>
item
.
key
===
SystemInputEnum
.
welcomeText
)?.
value
||
''
;
if
(
val
)
{
defaultAppForm
.
guide
.
welcome
=
{
text
:
val
};
}
}
else
if
(
module
.
flowType
===
FlowModuleTypeEnum
.
variable
)
{
defaultAppForm
.
variables
=
module
.
inputs
.
find
((
item
)
=>
item
.
key
===
SystemInputEnum
.
variables
)?.
value
||
[];
}
});
return
defaultAppForm
;
};
const
chatModelInput
=
(
formData
:
EditFormType
)
=>
[
{
key
:
'model'
,
type
:
'custom'
,
label
:
'对话模型'
,
value
:
formData
.
chatModel
.
model
,
list
:
chatModelList
.
map
((
item
)
=>
({
label
:
item
.
name
,
value
:
item
.
model
})),
connected
:
false
},
{
key
:
'temperature'
,
type
:
'custom'
,
label
:
'温度'
,
value
:
formData
.
chatModel
.
temperature
,
min
:
0
,
max
:
10
,
step
:
1
,
markList
:
[
{
label
:
'严谨'
,
value
:
0
},
{
label
:
'发散'
,
value
:
10
}
],
connected
:
false
},
{
key
:
'maxToken'
,
type
:
'custom'
,
label
:
'回复上限'
,
value
:
formData
.
chatModel
.
maxToken
,
min
:
100
,
max
:
16000
,
step
:
50
,
markList
:
[
{
label
:
'0'
,
value
:
0
},
{
label
:
'16000'
,
value
:
16000
}
],
connected
:
false
},
{
key
:
'systemPrompt'
,
type
:
'textarea'
,
label
:
'系统提示词'
,
description
:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。'
,
placeholder
:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。'
,
value
:
formData
.
chatModel
.
systemPrompt
,
connected
:
false
},
{
key
:
'limitPrompt'
,
type
:
'textarea'
,
label
:
'限定词'
,
description
:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"'
,
placeholder
:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"'
,
value
:
formData
.
chatModel
.
limitPrompt
,
connected
:
false
},
{
key
:
'quotePrompt'
,
type
:
'target'
,
label
:
'引用内容'
,
connected
:
formData
.
kb
.
list
.
length
>
0
},
{
key
:
'history'
,
type
:
'target'
,
label
:
'聊天记录'
,
connected
:
true
},
{
key
:
'userChatInput'
,
type
:
'target'
,
label
:
'用户问题'
,
connected
:
true
}
];
const
welcomeTemplate
=
(
formData
:
EditFormType
)
=>
formData
.
guide
?.
welcome
?.
text
?
[
{
...
UserGuideModule
,
inputs
:
[
{
key
:
'welcomeText'
,
type
:
'input'
,
label
:
'开场白'
,
value
:
formData
.
guide
.
welcome
.
text
,
connected
:
false
}
],
outputs
:
[],
position
:
{
x
:
447.98520778293346
,
y
:
721.4016845336229
},
moduleId
:
'v7lq0x'
}
]
:
[];
const
variableTemplate
=
(
formData
:
EditFormType
)
=>
formData
.
variables
.
length
>
0
?
[
{
...
VariableModule
,
inputs
:
[
{
key
:
'variables'
,
type
:
'systemInput'
,
label
:
'变量输入'
,
value
:
formData
.
variables
,
connected
:
false
}
],
outputs
:
[],
position
:
{
x
:
444.0369195277651
,
y
:
1008.5185781784537
},
moduleId
:
'7blchb'
}
]
:
[];
const
simpleChatTemplate
=
(
formData
:
EditFormType
)
=>
[
{
...
UserInputModule
,
inputs
:
[
{
key
:
'userChatInput'
,
type
:
'systemInput'
,
label
:
'用户问题'
,
connected
:
false
}
],
outputs
:
[
{
key
:
'userChatInput'
,
label
:
'用户问题'
,
type
:
'source'
,
targets
:
[
{
moduleId
:
'chatModule'
,
key
:
'userChatInput'
}
]
}
],
position
:
{
x
:
464.32198615344566
,
y
:
1602.2698463081606
},
moduleId
:
'7z5g5h'
},
{
...
HistoryModule
,
inputs
:
[
{
key
:
'maxContext'
,
type
:
'numberInput'
,
label
:
'最长记录数'
,
value
:
10
,
min
:
0
,
max
:
50
,
connected
:
false
},
{
key
:
'history'
,
type
:
'hidden'
,
label
:
'聊天记录'
,
connected
:
false
}
],
outputs
:
[
{
key
:
'history'
,
label
:
'聊天记录'
,
type
:
'source'
,
targets
:
[
{
moduleId
:
'chatModule'
,
key
:
'history'
}
]
}
],
position
:
{
x
:
452.5466249541586
,
y
:
1276.3930310334215
},
moduleId
:
'xj0c9p'
},
{
...
ChatModule
,
inputs
:
chatModelInput
(
formData
),
outputs
:
[
{
key
:
'answerText'
,
label
:
'模型回复'
,
description
:
'直接响应,无需配置'
,
type
:
'hidden'
,
targets
:
[]
}
],
position
:
{
x
:
981.9682828103937
,
y
:
890.014595014464
},
moduleId
:
'chatModule'
}
];
const
kbTemplate
=
(
formData
:
EditFormType
)
=>
[
{
...
UserInputModule
,
inputs
:
[
{
key
:
'userChatInput'
,
type
:
'systemInput'
,
label
:
'用户问题'
,
connected
:
false
}
],
outputs
:
[
{
key
:
'userChatInput'
,
label
:
'用户问题'
,
type
:
'source'
,
targets
:
[
{
moduleId
:
'chatModule'
,
key
:
'userChatInput'
},
{
moduleId
:
'q9v14m'
,
key
:
'userChatInput'
}
]
}
],
position
:
{
x
:
464.32198615344566
,
y
:
1602.2698463081606
},
moduleId
:
'7z5g5h'
},
{
...
HistoryModule
,
inputs
:
[
{
key
:
'maxContext'
,
type
:
'numberInput'
,
label
:
'最长记录数'
,
value
:
10
,
min
:
0
,
max
:
50
,
connected
:
false
},
{
key
:
'history'
,
type
:
'hidden'
,
label
:
'聊天记录'
,
connected
:
false
}
],
outputs
:
[
{
key
:
'history'
,
label
:
'聊天记录'
,
type
:
'source'
,
targets
:
[
{
moduleId
:
'chatModule'
,
key
:
'history'
}
]
}
],
position
:
{
x
:
452.5466249541586
,
y
:
1276.3930310334215
},
moduleId
:
'xj0c9p'
},
{
...
KBSearchModule
,
inputs
:
[
{
key
:
'kbList'
,
type
:
'custom'
,
label
:
'关联的知识库'
,
value
:
formData
.
kb
.
list
,
list
:
[],
connected
:
true
},
{
key
:
'similarity'
,
type
:
'custom'
,
label
:
'相似度'
,
value
:
formData
.
kb
.
searchSimilarity
,
min
:
0
,
max
:
1
,
step
:
0.01
,
markList
:
[
{
label
:
'0'
,
value
:
0
},
{
label
:
'1'
,
value
:
1
}
],
connected
:
false
},
{
key
:
'limit'
,
type
:
'custom'
,
label
:
'单次搜索上限'
,
description
:
'最多取 n 条记录作为本次问题引用'
,
value
:
formData
.
kb
.
searchLimit
,
min
:
1
,
max
:
20
,
step
:
1
,
markList
:
[
{
label
:
'1'
,
value
:
1
},
{
label
:
'20'
,
value
:
20
}
],
connected
:
false
},
{
key
:
'switch'
,
type
:
'target'
,
label
:
'触发器'
,
connected
:
false
},
{
key
:
'userChatInput'
,
type
:
'target'
,
label
:
'用户问题'
,
connected
:
true
}
],
outputs
:
[
{
key
:
'isEmpty'
,
label
:
'搜索结果为空'
,
type
:
'source'
,
targets
:
[
{
moduleId
:
'emptyText'
,
key
:
'switch'
}
]
},
{
key
:
'quotePrompt'
,
label
:
'引用内容'
,
description
:
'搜索结果为空时不返回'
,
type
:
'source'
,
targets
:
[
{
moduleId
:
'chatModule'
,
key
:
'quotePrompt'
}
]
}
],
position
:
{
x
:
956.0838440206068
,
y
:
887.462827870246
},
moduleId
:
'q9v14m'
},
...(
formData
.
kb
.
searchEmptyText
?
[
{
...
AnswerModule
,
inputs
:
[
{
key
:
'switch'
,
type
:
'target'
,
label
:
'触发器'
,
connected
:
true
},
{
key
:
'answerText'
,
value
:
formData
.
kb
.
searchEmptyText
,
type
:
'input'
,
label
:
'回复的内容'
,
connected
:
false
}
],
outputs
:
[],
position
:
{
x
:
1570.7651822907549
,
y
:
637.8753731306779
},
moduleId
:
'emptyText'
}
]
:
[]),
{
...
ChatModule
,
inputs
:
chatModelInput
(
formData
),
outputs
:
[
{
key
:
'answerText'
,
label
:
'模型回复'
,
description
:
'直接响应,无需配置'
,
type
:
'hidden'
,
targets
:
[]
}
],
position
:
{
x
:
1551.71405495818
,
y
:
977.4911578918461
},
moduleId
:
'chatModule'
}
];
export
const
appForm2Modules
=
(
formData
:
EditFormType
)
=>
{
const
modules
=
[
...
welcomeTemplate
(
formData
),
...
variableTemplate
(
formData
),
...(
formData
.
kb
.
list
.
length
>
0
?
kbTemplate
(
formData
)
:
simpleChatTemplate
(
formData
))
];
return
modules
as
AppModuleItemType
[];
};
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