Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
d5361b8d
Unverified
Commit
d5361b8d
authored
Jan 25, 2024
by
zxhlyh
Committed by
GitHub
Jan 25, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: multiple model configuration (#2196)
Co-authored-by:
Joel
<
iamjoel007@gmail.com
>
parent
6bfdfab6
Changes
56
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
56 changed files
with
3209 additions
and
188 deletions
+3209
-188
index.tsx
web/app/components/app-sidebar/index.tsx
+10
-0
index.tsx
web/app/components/app/chat/citation/index.tsx
+3
-1
index.tsx
web/app/components/app/chat/log/index.tsx
+2
-1
chat-item.tsx
...nfiguration/debug/debug-with-multiple-model/chat-item.tsx
+188
-0
context.tsx
...configuration/debug/debug-with-multiple-model/context.tsx
+39
-0
debug-item.tsx
...figuration/debug/debug-with-multiple-model/debug-item.tsx
+124
-0
index.tsx
...p/configuration/debug/debug-with-multiple-model/index.tsx
+131
-0
model-parameter-trigger.tsx
...bug/debug-with-multiple-model/model-parameter-trigger.tsx
+125
-0
publish-with-multiple-model.tsx
...debug-with-multiple-model/publish-with-multiple-model.tsx
+103
-0
text-generation-item.tsx
.../debug/debug-with-multiple-model/text-generation-item.tsx
+153
-0
hooks.tsx
web/app/components/app/configuration/debug/hooks.tsx
+54
-0
index.tsx
web/app/components/app/configuration/debug/index.tsx
+209
-81
types.ts
web/app/components/app/configuration/debug/types.ts
+18
-0
index.tsx
web/app/components/app/configuration/index.tsx
+83
-19
index.tsx
web/app/components/app/text-generate/item/index.tsx
+8
-3
agent-content.tsx
web/app/components/base/chat/chat/answer/agent-content.tsx
+58
-0
basic-content.tsx
web/app/components/base/chat/chat/answer/basic-content.tsx
+22
-0
index.tsx
web/app/components/base/chat/chat/answer/index.tsx
+99
-0
more.tsx
web/app/components/base/chat/chat/answer/more.tsx
+45
-0
operation.tsx
web/app/components/base/chat/chat/answer/operation.tsx
+54
-0
suggested-questions.tsx
.../components/base/chat/chat/answer/suggested-questions.tsx
+35
-0
chat-input.tsx
web/app/components/base/chat/chat/chat-input.tsx
+220
-0
context.tsx
web/app/components/base/chat/chat/context.tsx
+60
-0
hooks.ts
web/app/components/base/chat/chat/hooks.ts
+395
-0
index.tsx
web/app/components/base/chat/chat/index.tsx
+129
-0
question.tsx
web/app/components/base/chat/chat/question.tsx
+62
-0
try-to-ask.tsx
web/app/components/base/chat/chat/try-to-ask.tsx
+54
-0
types.ts
web/app/components/base/chat/types.ts
+48
-0
index.tsx
web/app/components/base/dropdown/index.tsx
+97
-0
message-heart-circle.svg
...ssets/vender/solid/communication/message-heart-circle.svg
+5
-0
send-03.svg
.../base/icons/assets/vender/solid/communication/send-03.svg
+5
-0
answer-triangle.svg
...ase/icons/assets/vender/solid/general/answer-triangle.svg
+3
-0
question-triangle.svg
...e/icons/assets/vender/solid/general/question-triangle.svg
+6
-0
star-04.svg
...ponents/base/icons/assets/vender/solid/shapes/star-04.svg
+5
-0
MessageHeartCircle.json
...ns/src/vender/solid/communication/MessageHeartCircle.json
+38
-0
MessageHeartCircle.tsx
...ons/src/vender/solid/communication/MessageHeartCircle.tsx
+16
-0
Send03.json
...nts/base/icons/src/vender/solid/communication/Send03.json
+36
-0
Send03.tsx
...ents/base/icons/src/vender/solid/communication/Send03.tsx
+16
-0
index.ts
...onents/base/icons/src/vender/solid/communication/index.ts
+2
-0
AnswerTriangle.json
...s/base/icons/src/vender/solid/general/AnswerTriangle.json
+27
-0
AnswerTriangle.tsx
...ts/base/icons/src/vender/solid/general/AnswerTriangle.tsx
+16
-0
QuestionTriangle.json
...base/icons/src/vender/solid/general/QuestionTriangle.json
+45
-0
QuestionTriangle.tsx
.../base/icons/src/vender/solid/general/QuestionTriangle.tsx
+16
-0
index.ts
...p/components/base/icons/src/vender/solid/general/index.ts
+2
-0
Star04.json
...components/base/icons/src/vender/solid/shapes/Star04.json
+36
-0
Star04.tsx
.../components/base/icons/src/vender/solid/shapes/Star04.tsx
+16
-0
index.ts
...pp/components/base/icons/src/vender/solid/shapes/index.ts
+1
-0
hooks.ts
web/app/components/base/text-generation/hooks.ts
+61
-0
types.ts
web/app/components/base/text-generation/types.ts
+41
-0
index.tsx
...tting/model-provider-page/model-parameter-modal/index.tsx
+53
-79
trigger.tsx
...ing/model-provider-page/model-parameter-modal/trigger.tsx
+107
-0
debug-configuration.ts
web/context/debug-configuration.ts
+3
-1
app-debug.en.ts
web/i18n/lang/app-debug.en.ts
+4
-0
app-debug.zh.ts
web/i18n/lang/app-debug.zh.ts
+4
-0
base.ts
web/service/base.ts
+6
-0
debug.ts
web/service/debug.ts
+11
-3
No files found.
web/app/components/app-sidebar/index.tsx
View file @
d5361b8d
...
...
@@ -7,6 +7,8 @@ import {
AlignLeft01
,
AlignRight01
,
}
from
'@/app/components/base/icons/src/vender/line/layout'
import
{
useEventEmitterContextContext
}
from
'@/context/event-emitter'
import
{
APP_SIDEBAR_SHOULD_COLLAPSE
}
from
'@/app/components/app/configuration/debug/types'
export
type
IAppDetailNavProps
=
{
iconType
?:
'app'
|
'dataset'
|
'notion'
...
...
@@ -39,6 +41,14 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
})
},
[])
const
{
eventEmitter
}
=
useEventEmitterContextContext
()
eventEmitter
?.
useSubscription
((
v
:
any
)
=>
{
if
(
v
.
type
===
APP_SIDEBAR_SHOULD_COLLAPSE
)
{
setModeState
(
'collapse'
)
localStorage
.
setItem
(
'app-detail-collapse-or-expand'
,
'collapse'
)
}
})
return
(
<
div
className=
{
`
...
...
web/app/components/app/chat/citation/index.tsx
View file @
d5361b8d
...
...
@@ -15,10 +15,12 @@ export type Resources = {
type
CitationProps
=
{
data
:
CitationItem
[]
showHitInfo
?:
boolean
containerClassName
?:
string
}
const
Citation
:
FC
<
CitationProps
>
=
({
data
,
showHitInfo
,
containerClassName
=
'chat-answer-container'
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
elesRef
=
useRef
<
HTMLDivElement
[]
>
([])
...
...
@@ -46,7 +48,7 @@ const Citation: FC<CitationProps> = ({
},
[]),
[
data
])
const
handleAdjustResourcesLayout
=
()
=>
{
const
containerWidth
=
document
.
querySelector
(
'.chat-answer-container'
)
!
.
clientWidth
-
40
const
containerWidth
=
document
.
querySelector
(
`.
${
containerClassName
}
`
)
!
.
clientWidth
-
40
let
totalWidth
=
0
for
(
let
i
=
0
;
i
<
resources
.
length
;
i
++
)
{
totalWidth
+=
elesRef
.
current
[
i
].
clientWidth
...
...
web/app/components/app/chat/log/index.tsx
View file @
d5361b8d
import
type
{
Dispatch
,
FC
,
ReactNode
,
RefObject
,
SetStateAction
}
from
'react'
import
{
useEffect
,
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
uniqueId
}
from
'lodash-es'
import
{
File02
}
from
'@/app/components/base/icons/src/vender/line/files'
import
PromptLogModal
from
'@/app/components/base/prompt-log-modal'
import
Tooltip
from
'@/app/components/base/tooltip'
...
...
@@ -39,7 +40,7 @@ const Log: FC<LogProps> = ({
children
?
children
(
setShowModal
)
:
(
<
Tooltip
selector=
'prompt-log-modal-trigger'
content=
{
t
(
'common.operation.log'
)
||
''
}
>
<
Tooltip
selector=
{
`prompt-log-modal-trigger-${uniqueId()}`
}
content=
{
t
(
'common.operation.log'
)
||
''
}
>
<
div
className=
{
`
hidden absolute -left-[14px] -top-[14px] group-hover:block w-7 h-7
p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-md cursor-pointer
...
...
web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
memo
,
useMemo
,
}
from
'react'
import
type
{
ModelAndParameter
}
from
'../types'
import
{
APP_CHAT_WITH_MULTIPLE_MODEL
,
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART
,
}
from
'../types'
import
{
AgentStrategy
,
ModelModeType
,
}
from
'@/types/app'
import
Chat
from
'@/app/components/base/chat/chat'
import
{
useChat
}
from
'@/app/components/base/chat/chat/hooks'
import
{
useDebugConfigurationContext
}
from
'@/context/debug-configuration'
import
type
{
ChatConfig
,
OnSend
,
}
from
'@/app/components/base/chat/types'
import
{
useEventEmitterContextContext
}
from
'@/context/event-emitter'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
{
fetchConvesationMessages
,
fetchSuggestedQuestions
,
stopChatMessageResponding
,
}
from
'@/service/debug'
import
{
promptVariablesToUserInputsForm
}
from
'@/utils/model-config'
import
Avatar
from
'@/app/components/base/avatar'
import
{
useAppContext
}
from
'@/context/app-context'
import
{
ModelFeatureEnum
}
from
'@/app/components/header/account-setting/model-provider-page/declarations'
type
ChatItemProps
=
{
modelAndParameter
:
ModelAndParameter
}
const
ChatItem
:
FC
<
ChatItemProps
>
=
({
modelAndParameter
,
})
=>
{
const
{
userProfile
}
=
useAppContext
()
const
{
isAdvancedMode
,
modelConfig
,
appId
,
inputs
,
promptMode
,
speechToTextConfig
,
introduction
,
suggestedQuestions
:
openingSuggestedQuestions
,
suggestedQuestionsAfterAnswerConfig
,
citationConfig
,
moderationConfig
,
chatPromptConfig
,
completionPromptConfig
,
dataSets
,
datasetConfigs
,
visionConfig
,
annotationConfig
,
collectionList
,
textToSpeechConfig
,
}
=
useDebugConfigurationContext
()
const
{
textGenerationModelList
}
=
useProviderContext
()
const
postDatasets
=
dataSets
.
map
(({
id
})
=>
({
dataset
:
{
enabled
:
true
,
id
,
},
}))
const
contextVar
=
modelConfig
.
configs
.
prompt_variables
.
find
(
item
=>
item
.
is_context_var
)?.
key
const
config
:
ChatConfig
=
{
pre_prompt
:
!
isAdvancedMode
?
modelConfig
.
configs
.
prompt_template
:
''
,
prompt_type
:
promptMode
,
chat_prompt_config
:
isAdvancedMode
?
chatPromptConfig
:
{},
completion_prompt_config
:
isAdvancedMode
?
completionPromptConfig
:
{},
user_input_form
:
promptVariablesToUserInputsForm
(
modelConfig
.
configs
.
prompt_variables
),
dataset_query_variable
:
contextVar
||
''
,
opening_statement
:
introduction
,
more_like_this
:
{
enabled
:
false
,
},
suggested_questions
:
openingSuggestedQuestions
,
suggested_questions_after_answer
:
suggestedQuestionsAfterAnswerConfig
,
text_to_speech
:
textToSpeechConfig
,
speech_to_text
:
speechToTextConfig
,
retriever_resource
:
citationConfig
,
sensitive_word_avoidance
:
moderationConfig
,
agent_mode
:
{
...
modelConfig
.
agentConfig
,
strategy
:
(
modelAndParameter
.
provider
===
'openai'
&&
modelConfig
.
mode
===
ModelModeType
.
chat
)
?
AgentStrategy
.
functionCall
:
AgentStrategy
.
react
,
},
dataset_configs
:
{
...
datasetConfigs
,
datasets
:
{
datasets
:
[...
postDatasets
],
}
as
any
,
},
file_upload
:
{
image
:
visionConfig
,
},
annotation_reply
:
annotationConfig
,
}
const
{
chatList
,
isResponsing
,
handleSend
,
suggestedQuestions
,
handleRestart
,
}
=
useChat
(
config
,
{
inputs
,
promptVariables
:
modelConfig
.
configs
.
prompt_variables
,
},
[],
taskId
=>
stopChatMessageResponding
(
appId
,
taskId
),
)
const
doSend
:
OnSend
=
(
message
,
files
)
=>
{
const
currentProvider
=
textGenerationModelList
.
find
(
item
=>
item
.
provider
===
modelAndParameter
.
provider
)
const
currentModel
=
currentProvider
?.
models
.
find
(
model
=>
model
.
model
===
modelAndParameter
.
model
)
const
supportVision
=
currentModel
?.
features
?.
includes
(
ModelFeatureEnum
.
vision
)
const
configData
=
{
...
config
,
model
:
{
provider
:
modelAndParameter
.
provider
,
name
:
modelAndParameter
.
model
,
mode
:
currentModel
?.
model_properties
.
mode
,
completion_params
:
modelAndParameter
.
parameters
,
},
}
const
data
:
any
=
{
query
:
message
,
inputs
,
model_config
:
configData
,
}
if
(
visionConfig
.
enabled
&&
files
?.
length
&&
supportVision
)
data
.
files
=
files
handleSend
(
`apps/
${
appId
}
/chat-messages`
,
data
,
{
onGetConvesationMessages
:
(
conversationId
,
getAbortController
)
=>
fetchConvesationMessages
(
appId
,
conversationId
,
getAbortController
),
onGetSuggestedQuestions
:
(
responseItemId
,
getAbortController
)
=>
fetchSuggestedQuestions
(
appId
,
responseItemId
,
getAbortController
),
},
)
}
const
{
eventEmitter
}
=
useEventEmitterContextContext
()
eventEmitter
?.
useSubscription
((
v
:
any
)
=>
{
if
(
v
.
type
===
APP_CHAT_WITH_MULTIPLE_MODEL
)
doSend
(
v
.
payload
.
message
,
v
.
payload
.
files
)
if
(
v
.
type
===
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART
)
handleRestart
()
})
const
allToolIcons
=
useMemo
(()
=>
{
const
icons
:
Record
<
string
,
any
>
=
{}
modelConfig
.
agentConfig
.
tools
?.
forEach
((
item
:
any
)
=>
{
icons
[
item
.
tool_name
]
=
collectionList
.
find
((
collection
:
any
)
=>
collection
.
id
===
item
.
provider_id
)?.
icon
})
return
icons
},
[
collectionList
,
modelConfig
.
agentConfig
.
tools
])
if
(
!
chatList
.
length
)
return
null
return
(
<
Chat
config=
{
config
}
chatList=
{
chatList
}
isResponsing=
{
isResponsing
}
noChatInput
chatContainerclassName=
'p-4'
chatFooterClassName=
'!-bottom-4'
suggestedQuestions=
{
suggestedQuestions
}
onSend=
{
doSend
}
showPromptLog
questionIcon=
{
<
Avatar
name=
{
userProfile
.
name
}
size=
{
40
}
/>
}
allToolIcons=
{
allToolIcons
}
/>
)
}
export
default
memo
(
ChatItem
)
web/app/components/app/configuration/debug/debug-with-multiple-model/context.tsx
0 → 100644
View file @
d5361b8d
'use client'
import
{
createContext
,
useContext
}
from
'use-context-selector'
import
type
{
ModelAndParameter
}
from
'../types'
export
type
DebugWithMultipleModelContextType
=
{
multipleModelConfigs
:
ModelAndParameter
[]
onMultipleModelConfigsChange
:
(
multiple
:
boolean
,
modelConfigs
:
ModelAndParameter
[])
=>
void
onDebugWithMultipleModelChange
:
(
singleModelConfig
:
ModelAndParameter
)
=>
void
}
const
DebugWithMultipleModelContext
=
createContext
<
DebugWithMultipleModelContextType
>
({
multipleModelConfigs
:
[],
onMultipleModelConfigsChange
:
()
=>
{},
onDebugWithMultipleModelChange
:
()
=>
{},
})
export
const
useDebugWithMultipleModelContext
=
()
=>
useContext
(
DebugWithMultipleModelContext
)
type
DebugWithMultipleModelContextProviderProps
=
{
children
:
React
.
ReactNode
}
&
DebugWithMultipleModelContextType
export
const
DebugWithMultipleModelContextProvider
=
({
children
,
onMultipleModelConfigsChange
,
multipleModelConfigs
,
onDebugWithMultipleModelChange
,
}:
DebugWithMultipleModelContextProviderProps
)
=>
{
return
(
<
DebugWithMultipleModelContext
.
Provider
value=
{
{
onMultipleModelConfigsChange
,
multipleModelConfigs
,
onDebugWithMultipleModelChange
,
}
}
>
{
children
}
</
DebugWithMultipleModelContext
.
Provider
>
)
}
export
default
DebugWithMultipleModelContext
web/app/components/app/configuration/debug/debug-with-multiple-model/debug-item.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
memo
}
from
'react'
import
type
{
ModelAndParameter
}
from
'../types'
import
ModelParameterTrigger
from
'./model-parameter-trigger'
import
ChatItem
from
'./chat-item'
import
TextGenerationItem
from
'./text-generation-item'
import
{
useDebugWithMultipleModelContext
}
from
'./context'
import
{
useDebugConfigurationContext
}
from
'@/context/debug-configuration'
import
Dropdown
from
'@/app/components/base/dropdown'
import
type
{
Item
}
from
'@/app/components/base/dropdown'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
{
ModelStatusEnum
}
from
'@/app/components/header/account-setting/model-provider-page/declarations'
type
DebugItemProps
=
{
modelAndParameter
:
ModelAndParameter
className
?:
string
}
const
DebugItem
:
FC
<
DebugItemProps
>
=
({
modelAndParameter
,
className
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
mode
}
=
useDebugConfigurationContext
()
const
{
multipleModelConfigs
,
onMultipleModelConfigsChange
,
onDebugWithMultipleModelChange
,
}
=
useDebugWithMultipleModelContext
()
const
{
textGenerationModelList
}
=
useProviderContext
()
const
index
=
multipleModelConfigs
.
findIndex
(
v
=>
v
.
id
===
modelAndParameter
.
id
)
const
currentProvider
=
textGenerationModelList
.
find
(
item
=>
item
.
provider
===
modelAndParameter
.
provider
)
const
currentModel
=
currentProvider
?.
models
.
find
(
item
=>
item
.
model
===
modelAndParameter
.
model
)
const
handleSelect
=
(
item
:
Item
)
=>
{
if
(
item
.
value
===
'duplicate'
)
{
if
(
multipleModelConfigs
.
length
>=
4
)
return
onMultipleModelConfigsChange
(
true
,
[
...
multipleModelConfigs
.
slice
(
0
,
index
+
1
),
{
...
modelAndParameter
,
id
:
`
${
Date
.
now
()}
`
,
},
...
multipleModelConfigs
.
slice
(
index
+
1
),
],
)
}
if
(
item
.
value
===
'debug-as-single-model'
)
onDebugWithMultipleModelChange
(
modelAndParameter
)
if
(
item
.
value
===
'remove'
)
{
onMultipleModelConfigsChange
(
true
,
multipleModelConfigs
.
filter
(
item
=>
item
.
id
!==
modelAndParameter
.
id
),
)
}
}
return
(
<
div
className=
{
`flex flex-col min-w-[320px] rounded-xl bg-white border-[0.5px] border-black/5 ${className}`
}
>
<
div
className=
'shrink-0 flex items-center justify-between h-10 px-3 border-b-[0.5px] border-b-black/5'
>
<
div
className=
'flex items-center justify-center w-6 h-5 font-medium italic text-gray-500'
>
#
{
index
+
1
}
</
div
>
<
ModelParameterTrigger
modelAndParameter=
{
modelAndParameter
}
/>
<
Dropdown
onSelect=
{
handleSelect
}
items=
{
[
...(
multipleModelConfigs
.
length
<=
3
?
[
{
value
:
'duplicate'
,
text
:
t
(
'appDebug.duplicateModel'
),
},
]
:
[]
),
...(
(
modelAndParameter
.
provider
&&
modelAndParameter
.
model
)
?
[
{
value
:
'debug-as-single-model'
,
text
:
t
(
'appDebug.debugAsSingleModel'
),
},
]
:
[]
),
]
}
secondItems=
{
multipleModelConfigs
.
length
>
2
?
[
{
value
:
'remove'
,
text
:
t
(
'common.operation.remove'
),
},
]
:
undefined
}
/>
</
div
>
<
div
style=
{
{
height
:
'calc(100% - 40px)'
}
}
>
{
mode
===
'chat'
&&
currentProvider
&&
currentModel
&&
currentModel
.
status
===
ModelStatusEnum
.
active
&&
(
<
ChatItem
modelAndParameter=
{
modelAndParameter
}
/>
)
}
{
mode
===
'completion'
&&
currentProvider
&&
currentModel
&&
currentModel
.
status
===
ModelStatusEnum
.
active
&&
(
<
TextGenerationItem
modelAndParameter=
{
modelAndParameter
}
/>
)
}
</
div
>
</
div
>
)
}
export
default
memo
(
DebugItem
)
web/app/components/app/configuration/debug/debug-with-multiple-model/index.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
memo
,
useCallback
,
}
from
'react'
import
{
APP_CHAT_WITH_MULTIPLE_MODEL
}
from
'../types'
import
DebugItem
from
'./debug-item'
import
{
DebugWithMultipleModelContextProvider
,
useDebugWithMultipleModelContext
,
}
from
'./context'
import
type
{
DebugWithMultipleModelContextType
}
from
'./context'
import
{
useEventEmitterContextContext
}
from
'@/context/event-emitter'
import
ChatInput
from
'@/app/components/base/chat/chat/chat-input'
import
type
{
VisionFile
}
from
'@/app/components/base/chat/types'
import
{
useDebugConfigurationContext
}
from
'@/context/debug-configuration'
const
DebugWithMultipleModel
=
()
=>
{
const
{
mode
,
speechToTextConfig
,
visionConfig
,
}
=
useDebugConfigurationContext
()
const
{
multipleModelConfigs
}
=
useDebugWithMultipleModelContext
()
const
{
eventEmitter
}
=
useEventEmitterContextContext
()
const
handleSend
=
useCallback
((
message
:
string
,
files
?:
VisionFile
[])
=>
{
eventEmitter
?.
emit
({
type
:
APP_CHAT_WITH_MULTIPLE_MODEL
,
payload
:
{
message
,
files
,
},
}
as
any
)
},
[
eventEmitter
])
const
twoLine
=
multipleModelConfigs
.
length
===
2
const
threeLine
=
multipleModelConfigs
.
length
===
3
const
fourLine
=
multipleModelConfigs
.
length
===
4
return
(
<
div
className=
'flex flex-col h-full'
>
<
div
className=
{
`
mb-3 overflow-auto
${(twoLine || threeLine) && 'flex gap-2'}
`
}
style=
{
{
height
:
mode
===
'chat'
?
'calc(100% - 60px)'
:
'100%'
}
}
>
{
(
twoLine
||
threeLine
)
&&
multipleModelConfigs
.
map
(
modelConfig
=>
(
<
DebugItem
key=
{
modelConfig
.
id
}
modelAndParameter=
{
modelConfig
}
className=
{
`
h-full min-h-[200px]
${twoLine && 'w-1/2'}
${threeLine && 'w-1/3'}
`
}
/>
))
}
{
fourLine
&&
(
<>
<
div
className=
'flex space-x-2 mb-2 min-h-[200px]'
style=
{
{
height
:
'calc(50% - 4px)'
}
}
>
{
multipleModelConfigs
.
slice
(
0
,
2
).
map
(
modelConfig
=>
(
<
DebugItem
key=
{
modelConfig
.
id
}
modelAndParameter=
{
modelConfig
}
className=
'w-1/2 h-full'
/>
))
}
</
div
>
<
div
className=
'flex space-x-2 min-h-[200px]'
style=
{
{
height
:
'calc(50% - 4px)'
}
}
>
{
multipleModelConfigs
.
slice
(
2
,
4
).
map
(
modelConfig
=>
(
<
DebugItem
key=
{
modelConfig
.
id
}
modelAndParameter=
{
modelConfig
}
className=
'w-1/2 h-full'
/>
))
}
</
div
>
</>
)
}
</
div
>
{
mode
===
'chat'
&&
(
<
div
className=
'shrink-0'
>
<
ChatInput
onSend=
{
handleSend
}
speechToTextConfig=
{
speechToTextConfig
}
visionConfig=
{
visionConfig
}
/>
</
div
>
)
}
</
div
>
)
}
const
DebugWithMultipleModelMemoed
=
memo
(
DebugWithMultipleModel
)
const
DebugWithMultipleModelWrapper
:
FC
<
DebugWithMultipleModelContextType
>
=
({
onMultipleModelConfigsChange
,
multipleModelConfigs
,
onDebugWithMultipleModelChange
,
})
=>
{
return
(
<
DebugWithMultipleModelContextProvider
onMultipleModelConfigsChange=
{
onMultipleModelConfigsChange
}
multipleModelConfigs=
{
multipleModelConfigs
}
onDebugWithMultipleModelChange=
{
onDebugWithMultipleModelChange
}
>
<
DebugWithMultipleModelMemoed
/>
</
DebugWithMultipleModelContextProvider
>
)
}
export
default
memo
(
DebugWithMultipleModelWrapper
)
web/app/components/app/configuration/debug/debug-with-multiple-model/model-parameter-trigger.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
ModelAndParameter
}
from
'../types'
import
{
useDebugWithMultipleModelContext
}
from
'./context'
import
ModelParameterModal
from
'@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import
ModelIcon
from
'@/app/components/header/account-setting/model-provider-page/model-icon'
import
ModelName
from
'@/app/components/header/account-setting/model-provider-page/model-name'
import
{
MODEL_STATUS_TEXT
,
ModelStatusEnum
,
}
from
'@/app/components/header/account-setting/model-provider-page/declarations'
import
{
useDebugConfigurationContext
}
from
'@/context/debug-configuration'
import
{
ChevronDown
}
from
'@/app/components/base/icons/src/vender/line/arrows'
import
{
CubeOutline
}
from
'@/app/components/base/icons/src/vender/line/shapes'
import
TooltipPlus
from
'@/app/components/base/tooltip-plus'
import
{
AlertTriangle
}
from
'@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import
{
useLanguage
}
from
'@/app/components/header/account-setting/model-provider-page/hooks'
type
ModelParameterTriggerProps
=
{
modelAndParameter
:
ModelAndParameter
}
const
ModelParameterTrigger
:
FC
<
ModelParameterTriggerProps
>
=
({
modelAndParameter
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
mode
,
isAdvancedMode
,
}
=
useDebugConfigurationContext
()
const
{
multipleModelConfigs
,
onMultipleModelConfigsChange
,
onDebugWithMultipleModelChange
,
}
=
useDebugWithMultipleModelContext
()
const
language
=
useLanguage
()
const
index
=
multipleModelConfigs
.
findIndex
(
v
=>
v
.
id
===
modelAndParameter
.
id
)
const
handleSelectModel
=
({
modelId
,
provider
}:
{
modelId
:
string
;
provider
:
string
})
=>
{
const
newModelConfigs
=
[...
multipleModelConfigs
]
newModelConfigs
[
index
]
=
{
...
newModelConfigs
[
index
],
model
:
modelId
,
provider
,
}
onMultipleModelConfigsChange
(
true
,
newModelConfigs
)
}
const
handleParamsChange
=
(
params
:
any
)
=>
{
const
newModelConfigs
=
[...
multipleModelConfigs
]
newModelConfigs
[
index
]
=
{
...
newModelConfigs
[
index
],
parameters
:
params
,
}
onMultipleModelConfigsChange
(
true
,
newModelConfigs
)
}
return
(
<
ModelParameterModal
mode=
{
mode
}
isAdvancedMode=
{
isAdvancedMode
}
provider=
{
modelAndParameter
.
provider
}
modelId=
{
modelAndParameter
.
model
}
completionParams=
{
modelAndParameter
.
parameters
}
onCompletionParamsChange=
{
handleParamsChange
}
setModel=
{
handleSelectModel
}
debugWithMultipleModel
onDebugWithMultipleModelChange=
{
()
=>
onDebugWithMultipleModelChange
(
modelAndParameter
)
}
renderTrigger=
{
({
open
,
currentProvider
,
currentModel
,
})
=>
(
<
div
className=
{
`
flex items-center max-w-[200px] h-8 px-2 rounded-lg cursor-pointer
${open && 'bg-gray-100'}
${currentModel && currentModel.status !== ModelStatusEnum.active && '!bg-[#FFFAEB]'}
`
}
>
{
currentProvider
&&
(
<
ModelIcon
className=
'mr-1 !w-4 !h-4'
provider=
{
currentProvider
}
modelName=
{
currentModel
?.
model
}
/>
)
}
{
!
currentProvider
&&
(
<
div
className=
'flex items-center justify-center mr-1 w-4 h-4 rounded border border-dashed border-primary-100'
>
<
CubeOutline
className=
'w-[11px] h-[11px] text-primary-600'
/>
</
div
>
)
}
{
currentModel
&&
(
<
ModelName
className=
'mr-0.5 text-gray-800'
modelItem=
{
currentModel
}
/>
)
}
{
!
currentModel
&&
(
<
div
className=
'mr-0.5 text-[13px] font-medium text-primary-600 truncate'
>
{
t
(
'common.modelProvider.selectModel'
)
}
</
div
>
)
}
<
ChevronDown
className=
{
`w-3 h-3 ${(currentModel && currentProvider) ? 'text-gray-800' : 'text-primary-600'}`
}
/>
{
currentModel
&&
currentModel
.
status
!==
ModelStatusEnum
.
active
&&
(
<
TooltipPlus
popupContent=
{
MODEL_STATUS_TEXT
[
currentModel
.
status
][
language
]
}
>
<
AlertTriangle
className=
'w-4 h-4 text-[#F79009]'
/>
</
TooltipPlus
>
)
}
</
div
>
)
}
/>
)
}
export
default
memo
(
ModelParameterTrigger
)
web/app/components/app/configuration/debug/debug-with-multiple-model/publish-with-multiple-model.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
ModelAndParameter
}
from
'../types'
import
Button
from
'@/app/components/base/button'
import
{
PortalToFollowElem
,
PortalToFollowElemContent
,
PortalToFollowElemTrigger
,
}
from
'@/app/components/base/portal-to-follow-elem'
import
{
ChevronDown
}
from
'@/app/components/base/icons/src/vender/line/arrows'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
type
{
ModelItem
}
from
'@/app/components/header/account-setting/model-provider-page/declarations'
import
{
useLanguage
}
from
'@/app/components/header/account-setting/model-provider-page/hooks'
type
PublishWithMultipleModelProps
=
{
multipleModelConfigs
:
ModelAndParameter
[]
onSelect
:
(
v
:
ModelAndParameter
)
=>
void
}
const
PublishWithMultipleModel
:
FC
<
PublishWithMultipleModelProps
>
=
({
multipleModelConfigs
,
onSelect
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
language
=
useLanguage
()
const
{
textGenerationModelList
}
=
useProviderContext
()
const
[
open
,
setOpen
]
=
useState
(
false
)
const
validModelConfigs
:
(
ModelAndParameter
&
{
modelItem
:
ModelItem
})[]
=
[]
multipleModelConfigs
.
forEach
((
item
)
=>
{
const
provider
=
textGenerationModelList
.
find
(
model
=>
model
.
provider
===
item
.
provider
)
if
(
provider
)
{
const
model
=
provider
.
models
.
find
(
model
=>
model
.
model
===
item
.
model
)
if
(
model
)
{
validModelConfigs
.
push
({
id
:
item
.
id
,
model
:
item
.
model
,
provider
:
item
.
provider
,
modelItem
:
model
,
parameters
:
item
.
parameters
,
})
}
}
})
const
handleToggle
=
()
=>
{
if
(
validModelConfigs
.
length
)
setOpen
(
v
=>
!
v
)
}
const
handleSelect
=
(
item
:
ModelAndParameter
)
=>
{
onSelect
(
item
)
setOpen
(
false
)
}
return
(
<
PortalToFollowElem
open=
{
open
}
onOpenChange=
{
setOpen
}
placement=
'bottom-end'
>
<
PortalToFollowElemTrigger
onClick=
{
handleToggle
}
>
<
Button
type=
'primary'
disabled=
{
!
validModelConfigs
.
length
}
className=
'pl-3 pr-2 h-8 text-[13px]'
>
{
t
(
'appDebug.operation.applyConfig'
)
}
<
ChevronDown
className=
'ml-0.5 w-3 h-3'
/>
</
Button
>
</
PortalToFollowElemTrigger
>
<
PortalToFollowElemContent
>
<
div
className=
'p-1 w-[168px] rounded-lg border-[0.5px] border-gray-200 shadow-lg bg-white'
>
<
div
className=
'flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'
>
{
t
(
'appDebug.publishAs'
)
}
</
div
>
{
validModelConfigs
.
map
((
item
,
index
)
=>
(
<
div
key=
{
item
.
id
}
className=
'flex items-center px-3 h-8 rounded-lg hover:bg-gray-100 cursor-pointer text-sm text-gray-500'
onClick=
{
()
=>
handleSelect
(
item
)
}
>
#
{
index
+
1
}
<
div
className=
'ml-1 text-gray-700 truncate'
title=
{
item
.
modelItem
.
label
[
language
]
}
>
{
item
.
modelItem
.
label
[
language
]
}
</
div
>
</
div
>
))
}
</
div
>
</
PortalToFollowElemContent
>
</
PortalToFollowElem
>
)
}
export
default
PublishWithMultipleModel
web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
import
type
{
ModelAndParameter
}
from
'../types'
import
{
APP_CHAT_WITH_MULTIPLE_MODEL
}
from
'../types'
import
type
{
OnSend
,
TextGenerationConfig
,
}
from
'@/app/components/base/text-generation/types'
import
{
useTextGeneration
}
from
'@/app/components/base/text-generation/hooks'
import
TextGeneration
from
'@/app/components/app/text-generate/item'
import
{
useDebugConfigurationContext
}
from
'@/context/debug-configuration'
import
{
promptVariablesToUserInputsForm
}
from
'@/utils/model-config'
import
{
TransferMethod
}
from
'@/app/components/base/chat/types'
import
{
useEventEmitterContextContext
}
from
'@/context/event-emitter'
import
{
useProviderContext
}
from
'@/context/provider-context'
type
TextGenerationItemProps
=
{
modelAndParameter
:
ModelAndParameter
}
const
TextGenerationItem
:
FC
<
TextGenerationItemProps
>
=
({
modelAndParameter
,
})
=>
{
const
{
isAdvancedMode
,
modelConfig
,
appId
,
inputs
,
promptMode
,
speechToTextConfig
,
introduction
,
suggestedQuestionsAfterAnswerConfig
,
citationConfig
,
moderationConfig
,
externalDataToolsConfig
,
chatPromptConfig
,
completionPromptConfig
,
dataSets
,
datasetConfigs
,
visionConfig
,
moreLikeThisConfig
,
}
=
useDebugConfigurationContext
()
const
{
textGenerationModelList
}
=
useProviderContext
()
const
postDatasets
=
dataSets
.
map
(({
id
})
=>
({
dataset
:
{
enabled
:
true
,
id
,
},
}))
const
contextVar
=
modelConfig
.
configs
.
prompt_variables
.
find
(
item
=>
item
.
is_context_var
)?.
key
const
config
:
TextGenerationConfig
=
{
pre_prompt
:
!
isAdvancedMode
?
modelConfig
.
configs
.
prompt_template
:
''
,
prompt_type
:
promptMode
,
chat_prompt_config
:
isAdvancedMode
?
chatPromptConfig
:
{},
completion_prompt_config
:
isAdvancedMode
?
completionPromptConfig
:
{},
user_input_form
:
promptVariablesToUserInputsForm
(
modelConfig
.
configs
.
prompt_variables
),
dataset_query_variable
:
contextVar
||
''
,
opening_statement
:
introduction
,
suggested_questions_after_answer
:
suggestedQuestionsAfterAnswerConfig
,
speech_to_text
:
speechToTextConfig
,
retriever_resource
:
citationConfig
,
sensitive_word_avoidance
:
moderationConfig
,
external_data_tools
:
externalDataToolsConfig
,
more_like_this
:
moreLikeThisConfig
,
agent_mode
:
{
enabled
:
false
,
tools
:
[],
},
dataset_configs
:
{
...
datasetConfigs
,
datasets
:
{
datasets
:
[...
postDatasets
],
}
as
any
,
},
file_upload
:
{
image
:
visionConfig
,
},
}
const
{
completion
,
handleSend
,
isResponsing
,
messageId
,
}
=
useTextGeneration
()
const
doSend
:
OnSend
=
(
message
,
files
)
=>
{
const
currentProvider
=
textGenerationModelList
.
find
(
item
=>
item
.
provider
===
modelAndParameter
.
provider
)
const
currentModel
=
currentProvider
?.
models
.
find
(
model
=>
model
.
model
===
modelAndParameter
.
model
)
const
configData
=
{
...
config
,
model
:
{
provider
:
modelAndParameter
.
provider
,
name
:
modelAndParameter
.
model
,
mode
:
currentModel
?.
model_properties
.
mode
,
completion_params
:
modelAndParameter
.
parameters
,
},
}
const
data
:
any
=
{
inputs
,
model_config
:
configData
,
}
if
(
visionConfig
.
enabled
&&
files
&&
files
?.
length
>
0
)
{
data
.
files
=
files
.
map
((
item
)
=>
{
if
(
item
.
transfer_method
===
TransferMethod
.
local_file
)
{
return
{
...
item
,
url
:
''
,
}
}
return
item
})
}
handleSend
(
`apps/
${
appId
}
/completion-messages`
,
data
,
)
}
const
{
eventEmitter
}
=
useEventEmitterContextContext
()
eventEmitter
?.
useSubscription
((
v
:
any
)
=>
{
if
(
v
.
type
===
APP_CHAT_WITH_MULTIPLE_MODEL
)
doSend
(
v
.
payload
.
message
,
v
.
payload
.
files
)
})
const
varList
=
modelConfig
.
configs
.
prompt_variables
.
map
((
item
:
any
)
=>
{
return
{
label
:
item
.
key
,
value
:
inputs
[
item
.
key
],
}
})
return
(
<
TextGeneration
className=
'flex flex-col h-full overflow-y-auto border-none'
innerClassName=
'grow flex flex-col'
contentClassName=
'grow'
content=
{
completion
}
isLoading=
{
!
completion
&&
isResponsing
}
isResponsing=
{
isResponsing
}
isInstalledApp=
{
false
}
messageId=
{
messageId
}
isError=
{
false
}
onRetry=
{
()
=>
{
}
}
appId=
{
appId
}
varList=
{
varList
}
/>
)
}
export
default
memo
(
TextGenerationItem
)
web/app/components/app/configuration/debug/hooks.tsx
0 → 100644
View file @
d5361b8d
import
{
useCallback
,
useRef
,
useState
,
}
from
'react'
import
type
{
DebugWithSingleOrMultipleModelConfigs
,
ModelAndParameter
,
}
from
'./types'
export
const
useDebugWithSingleOrMultipleModel
=
(
appId
:
string
)
=>
{
const
localeDebugWithSingleOrMultipleModelConfigs
=
localStorage
.
getItem
(
'app-debug-with-single-or-multiple-models'
)
const
debugWithSingleOrMultipleModelConfigs
=
useRef
<
DebugWithSingleOrMultipleModelConfigs
>
({})
if
(
localeDebugWithSingleOrMultipleModelConfigs
)
{
try
{
debugWithSingleOrMultipleModelConfigs
.
current
=
JSON
.
parse
(
localeDebugWithSingleOrMultipleModelConfigs
)
||
{}
}
catch
(
e
)
{
console
.
error
(
e
)
}
}
const
[
debugWithMultipleModel
,
setDebugWithMultipleModel
,
]
=
useState
(
debugWithSingleOrMultipleModelConfigs
.
current
[
appId
]?.
multiple
||
false
)
const
[
multipleModelConfigs
,
setMultipleModelConfigs
,
]
=
useState
(
debugWithSingleOrMultipleModelConfigs
.
current
[
appId
]?.
configs
||
[])
const
handleMultipleModelConfigsChange
=
useCallback
((
multiple
:
boolean
,
modelConfigs
:
ModelAndParameter
[],
)
=>
{
const
value
=
{
multiple
,
configs
:
modelConfigs
,
}
debugWithSingleOrMultipleModelConfigs
.
current
[
appId
]
=
value
localStorage
.
setItem
(
'app-debug-with-single-or-multiple-models'
,
JSON
.
stringify
(
debugWithSingleOrMultipleModelConfigs
.
current
))
setDebugWithMultipleModel
(
value
.
multiple
)
setMultipleModelConfigs
(
value
.
configs
)
},
[
appId
])
return
{
debugWithMultipleModel
,
multipleModelConfigs
,
handleMultipleModelConfigsChange
,
}
}
web/app/components/app/configuration/debug/index.tsx
View file @
d5361b8d
...
...
@@ -12,6 +12,12 @@ import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api'
import
FormattingChanged
from
'../base/warning-mask/formatting-changed'
import
GroupName
from
'../base/group-name'
import
CannotQueryDataset
from
'../base/warning-mask/cannot-query-dataset'
import
DebugWithMultipleModel
from
'./debug-with-multiple-model'
import
type
{
ModelAndParameter
}
from
'./types'
import
{
APP_CHAT_WITH_MULTIPLE_MODEL
,
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART
,
}
from
'./types'
import
{
AgentStrategy
,
AppType
,
ModelModeType
,
TransferMethod
}
from
'@/types/app'
import
PromptValuePanel
,
{
replaceStringWithValues
}
from
'@/app/components/app/configuration/prompt-value-panel'
import
type
{
IChatItem
}
from
'@/app/components/app/chat/type'
...
...
@@ -28,17 +34,30 @@ import type { Inputs } from '@/models/debug'
import
{
fetchFileUploadConfig
}
from
'@/service/common'
import
type
{
Annotation
as
AnnotationType
}
from
'@/models/log'
import
{
useDefaultModel
}
from
'@/app/components/header/account-setting/model-provider-page/hooks'
import
{
ModelFeatureEnum
}
from
'@/app/components/header/account-setting/model-provider-page/declarations'
import
type
{
ModelParameterModalProps
}
from
'@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import
{
Plus
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
useEventEmitterContextContext
}
from
'@/context/event-emitter'
import
{
useProviderContext
}
from
'@/context/provider-context'
type
IDebug
=
{
hasSetAPIKEY
:
boolean
onSetting
:
()
=>
void
inputs
:
Inputs
modelParameterParams
:
Pick
<
ModelParameterModalProps
,
'setModel'
|
'onCompletionParamsChange'
>
debugWithMultipleModel
:
boolean
multipleModelConfigs
:
ModelAndParameter
[]
onMultipleModelConfigsChange
:
(
multiple
:
boolean
,
modelConfigs
:
ModelAndParameter
[])
=>
void
}
const
Debug
:
FC
<
IDebug
>
=
({
hasSetAPIKEY
=
true
,
onSetting
,
inputs
,
modelParameterParams
,
debugWithMultipleModel
,
multipleModelConfigs
,
onMultipleModelConfigsChange
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
...
...
@@ -72,7 +91,9 @@ const Debug: FC<IDebug> = ({
datasetConfigs
,
visionConfig
,
annotationConfig
,
setVisionConfig
,
}
=
useContext
(
ConfigContext
)
const
{
eventEmitter
}
=
useEventEmitterContextContext
()
const
{
data
:
speech2textDefaultModel
}
=
useDefaultModel
(
4
)
const
{
data
:
text2speechDefaultModel
}
=
useDefaultModel
(
5
)
const
[
chatList
,
setChatList
,
getChatList
]
=
useGetState
<
IChatItem
[]
>
([])
...
...
@@ -119,7 +140,7 @@ const Debug: FC<IDebug> = ({
setFormattingChanged
(
false
)
},
[
formattingChanged
])
const
clearConversation
=
async
()
=>
{
const
handleClearConversation
=
()
=>
{
setConversationId
(
null
)
abortController
?.
abort
()
setResponsingFalse
()
...
...
@@ -134,6 +155,16 @@ const Debug: FC<IDebug> = ({
:
[])
setIsShowSuggestion
(
false
)
}
const
clearConversation
=
async
()
=>
{
if
(
debugWithMultipleModel
)
{
eventEmitter
?.
emit
({
type
:
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART
,
}
as
any
)
return
}
handleClearConversation
()
}
const
handleConfirm
=
()
=>
{
clearConversation
()
...
...
@@ -601,6 +632,21 @@ const Debug: FC<IDebug> = ({
})
}
const
handleSendTextCompletion
=
()
=>
{
if
(
debugWithMultipleModel
)
{
eventEmitter
?.
emit
({
type
:
APP_CHAT_WITH_MULTIPLE_MODEL
,
payload
:
{
message
:
''
,
files
:
completionFiles
,
},
}
as
any
)
return
}
sendTextCompletion
()
}
const
varList
=
modelConfig
.
configs
.
prompt_variables
.
map
((
item
:
any
)
=>
{
return
{
label
:
item
.
key
,
...
...
@@ -608,6 +654,51 @@ const Debug: FC<IDebug> = ({
}
})
const
{
textGenerationModelList
}
=
useProviderContext
()
const
handleChangeToSingleModel
=
(
item
:
ModelAndParameter
)
=>
{
const
currentProvider
=
textGenerationModelList
.
find
(
modelItem
=>
modelItem
.
provider
===
item
.
provider
)
const
currentModel
=
currentProvider
?.
models
.
find
(
model
=>
model
.
model
===
item
.
model
)
modelParameterParams
.
setModel
({
modelId
:
item
.
model
,
provider
:
item
.
provider
,
mode
:
currentModel
?.
model_properties
.
mode
as
string
,
features
:
currentModel
?.
features
,
})
modelParameterParams
.
onCompletionParamsChange
(
item
.
parameters
)
onMultipleModelConfigsChange
(
false
,
[],
)
}
const
handleVisionConfigInMultipleModel
=
()
=>
{
if
(
debugWithMultipleModel
&&
!
visionConfig
.
enabled
)
{
const
supportedVision
=
multipleModelConfigs
.
some
((
modelConfig
)
=>
{
const
currentProvider
=
textGenerationModelList
.
find
(
modelItem
=>
modelItem
.
provider
===
modelConfig
.
provider
)
const
currentModel
=
currentProvider
?.
models
.
find
(
model
=>
model
.
model
===
modelConfig
.
model
)
return
currentModel
?.
features
?.
includes
(
ModelFeatureEnum
.
vision
)
})
if
(
supportedVision
)
{
setVisionConfig
({
...
visionConfig
,
enabled
:
true
,
})
}
else
{
setVisionConfig
({
...
visionConfig
,
enabled
:
false
,
})
}
}
}
useEffect
(()
=>
{
handleVisionConfigInMultipleModel
()
},
[
multipleModelConfigs
])
const
allToolIcons
=
(()
=>
{
const
icons
:
Record
<
string
,
any
>
=
{}
modelConfig
.
agentConfig
.
tools
?.
forEach
((
item
:
any
)
=>
{
...
...
@@ -621,6 +712,27 @@ const Debug: FC<IDebug> = ({
<
div
className=
"shrink-0"
>
<
div
className=
'flex items-center justify-between mb-2'
>
<
div
className=
'h2 '
>
{
t
(
'appDebug.inputs.title'
)
}
</
div
>
<
div
className=
'flex items-center'
>
{
debugWithMultipleModel
?
(
<>
<
Button
className=
{
`
h-8 px-2.5 text-[13px] font-medium text-primary-600 bg-white
${multipleModelConfigs.length >= 4 && 'opacity-30'}
`
}
onClick=
{
()
=>
onMultipleModelConfigsChange
(
true
,
[...
multipleModelConfigs
,
{
id
:
`${Date.now()}`
,
model
:
''
,
provider
:
''
,
parameters
:
{}
}])
}
disabled=
{
multipleModelConfigs
.
length
>=
4
}
>
<
Plus
className=
'mr-1 w-3.5 h-3.5'
/>
{
t
(
'common.modelProvider.addModel'
)
}
(
{
multipleModelConfigs
.
length
}
/4)
</
Button
>
<
div
className=
'mx-2 w-[1px] h-[14px] bg-gray-200'
/>
</>
)
:
null
}
{
mode
===
'chat'
&&
(
<
Button
className=
'flex items-center gap-1 !h-8 !bg-white'
onClick=
{
clearConversation
}
>
<
svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
...
...
@@ -630,9 +742,10 @@ const Debug: FC<IDebug> = ({
</
Button
>
)
}
</
div
>
</
div
>
<
PromptValuePanel
appType=
{
mode
as
AppType
}
onSend=
{
s
endTextCompletion
}
onSend=
{
handleS
endTextCompletion
}
inputs=
{
inputs
}
visionConfig=
{
{
...
visionConfig
,
...
...
@@ -641,6 +754,19 @@ const Debug: FC<IDebug> = ({
onVisionFilesChange=
{
setCompletionFiles
}
/>
</
div
>
{
debugWithMultipleModel
&&
(
<
div
className=
'grow mt-3 overflow-hidden'
>
<
DebugWithMultipleModel
multipleModelConfigs=
{
multipleModelConfigs
}
onMultipleModelConfigsChange=
{
onMultipleModelConfigsChange
}
onDebugWithMultipleModelChange=
{
handleChangeToSingleModel
}
/>
</
div
>
)
}
{
!
debugWithMultipleModel
&&
(
<
div
className=
"flex flex-col grow"
>
{
/* Chat */
}
{
mode
===
AppType
.
chat
&&
(
...
...
@@ -716,6 +842,8 @@ const Debug: FC<IDebug> = ({
/>
)
}
</
div
>
)
}
{
!
hasSetAPIKEY
&&
(<
HasNotSetAPIKEY
isTrailFinished=
{
!
IS_CE_EDITION
}
onSetting=
{
onSetting
}
/>)
}
</>
)
...
...
web/app/components/app/configuration/debug/types.ts
0 → 100644
View file @
d5361b8d
export
type
ModelAndParameter
=
{
id
:
string
model
:
string
provider
:
string
parameters
:
Record
<
string
,
any
>
}
export
type
MultipleAndConfigs
=
{
multiple
:
boolean
configs
:
ModelAndParameter
[]
}
export
type
DebugWithSingleOrMultipleModelConfigs
=
{
[
k
:
string
]:
MultipleAndConfigs
}
export
const
APP_CHAT_WITH_MULTIPLE_MODEL
=
'APP_CHAT_WITH_MULTIPLE_MODEL'
export
const
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART
=
'APP_CHAT_WITH_MULTIPLE_MODEL_RESTART'
export
const
APP_SIDEBAR_SHOULD_COLLAPSE
=
'APP_SIDEBAR_SHOULD_COLLAPSE'
web/app/components/app/configuration/index.tsx
View file @
d5361b8d
...
...
@@ -13,6 +13,10 @@ import Button from '../../base/button'
import
Loading
from
'../../base/loading'
import
useAdvancedPromptConfig
from
'./hooks/use-advanced-prompt-config'
import
EditHistoryModal
from
'./config-prompt/conversation-histroy/edit-modal'
import
{
useDebugWithSingleOrMultipleModel
}
from
'./debug/hooks'
import
type
{
ModelAndParameter
}
from
'./debug/types'
import
{
APP_SIDEBAR_SHOULD_COLLAPSE
}
from
'./debug/types'
import
PublishWithMultipleModel
from
'./debug/debug-with-multiple-model/publish-with-multiple-model'
import
AssistantTypePicker
from
'./config/assistant-type-picker'
import
type
{
AnnotationReplyConfig
,
...
...
@@ -50,6 +54,7 @@ import type { FormValue } from '@/app/components/header/account-setting/model-pr
import
{
useTextGenerationCurrentProviderAndModelAndModelList
}
from
'@/app/components/header/account-setting/model-provider-page/hooks'
import
{
fetchCollectionList
}
from
'@/service/tools'
import
{
type
Collection
}
from
'@/app/components/tools/types'
import
{
useEventEmitterContextContext
}
from
'@/context/event-emitter'
type
PublichConfig
=
{
modelConfig
:
ModelConfig
...
...
@@ -524,8 +529,8 @@ const Configuration: FC = () => {
else
{
return
promptEmpty
}
})()
const
contextVarEmpty
=
mode
===
AppType
.
completion
&&
dataSets
.
length
>
0
&&
!
hasSetContextVar
const
handlePublish
=
async
(
isSilence
?:
boolean
)
=>
{
const
modelId
=
modelConfig
.
model_id
const
handlePublish
=
async
(
isSilence
?:
boolean
,
modelAndParameter
?:
ModelAndParameter
)
=>
{
const
modelId
=
model
AndParameter
?.
model
||
model
Config
.
model_id
const
promptTemplate
=
modelConfig
.
configs
.
prompt_template
const
promptVariables
=
modelConfig
.
configs
.
prompt_variables
...
...
@@ -578,10 +583,10 @@ const Configuration: FC = () => {
strategy
:
isFunctionCall
?
AgentStrategy
.
functionCall
:
AgentStrategy
.
react
,
},
model
:
{
provider
:
modelConfig
.
provider
,
provider
:
model
AndParameter
?.
provider
||
model
Config
.
provider
,
name
:
modelId
,
mode
:
modelConfig
.
mode
,
completion_params
:
completionParams
as
any
,
completion_params
:
modelAndParameter
?.
parameters
||
completionParams
as
any
,
},
dataset_configs
:
{
...
datasetConfigs
,
...
...
@@ -629,6 +634,26 @@ const Configuration: FC = () => {
const
[
showUseGPT4Confirm
,
setShowUseGPT4Confirm
]
=
useState
(
false
)
const
{
locale
}
=
useContext
(
I18n
)
const
{
eventEmitter
}
=
useEventEmitterContextContext
()
const
{
debugWithMultipleModel
,
multipleModelConfigs
,
handleMultipleModelConfigsChange
,
}
=
useDebugWithSingleOrMultipleModel
(
appId
)
const
handleDebugWithMultipleModelChange
=
()
=>
{
handleMultipleModelConfigsChange
(
true
,
[
{
id
:
`
${
Date
.
now
()}
`
,
model
:
modelConfig
.
model_id
,
provider
:
modelConfig
.
provider
,
parameters
:
completionParams
},
{
id
:
`
${
Date
.
now
()}
-no-repeat`
,
model
:
''
,
provider
:
''
,
parameters
:
{}
},
],
)
eventEmitter
?.
emit
({
type
:
APP_SIDEBAR_SHOULD_COLLAPSE
,
}
as
any
)
}
if
(
isLoading
)
{
return
<
div
className=
'flex h-full items-center justify-center'
>
<
Loading
type=
'area'
/>
...
...
@@ -709,7 +734,7 @@ const Configuration: FC = () => {
<>
<
div
className=
"flex flex-col h-full"
>
<
div
className=
'flex grow h-[200px]'
>
<
div
className=
"w-full sm:w-1/2 shrink-0 flex flex-col h-full"
>
<
div
className=
{
`w-full sm:w-1/2 shrink-0 flex flex-col h-full ${debugWithMultipleModel && 'max-w-[560px]'}`
}
>
{
/* Header Left */
}
<
div
className=
'flex justify-between items-center px-6 h-14'
>
<
div
className=
'flex items-center'
>
...
...
@@ -743,10 +768,13 @@ const Configuration: FC = () => {
</
div
>
<
Config
/>
</
div
>
{
!
isMobile
&&
<
div
className=
"relative w-1/2 h-full overflow-y-auto flex flex-col "
style=
{
{
borderColor
:
'rgba(0, 0, 0, 0.02)'
}
}
>
{
!
isMobile
&&
<
div
className=
"
grow
relative w-1/2 h-full overflow-y-auto flex flex-col "
style=
{
{
borderColor
:
'rgba(0, 0, 0, 0.02)'
}
}
>
{
/* Header Right */
}
<
div
className=
'flex justify-end items-center flex-wrap px-6 h-14 space-x-2'
>
{
/* Model and Parameters */
}
{
!
debugWithMultipleModel
&&
(
<>
<
ModelParameterModal
isAdvancedMode=
{
isAdvancedMode
}
mode=
{
mode
}
...
...
@@ -757,8 +785,13 @@ const Configuration: FC = () => {
onCompletionParamsChange=
{
(
newParams
:
FormValue
)
=>
{
setCompletionParams
(
newParams
)
}
}
debugWithMultipleModel=
{
debugWithMultipleModel
}
onDebugWithMultipleModelChange=
{
handleDebugWithMultipleModelChange
}
/>
<
div
className=
'w-[1px] h-[14px] bg-gray-200'
></
div
>
</>
)
}
<
Button
onClick=
{
()
=>
setShowConfirm
(
true
)
}
className=
'shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'
>
{
t
(
'appDebug.operation.resetConfig'
)
}
</
Button
>
{
isMobile
&&
(
<
Button
className=
'!h-8 !text-[13px] font-medium'
onClick=
{
showDebugPanel
}
>
...
...
@@ -766,13 +799,37 @@ const Configuration: FC = () => {
<
CodeBracketIcon
className=
"h-4 w-4 text-gray-500"
/>
</
Button
>
)
}
<
Button
type=
'primary'
onClick=
{
()
=>
handlePublish
(
false
)
}
className=
{
cn
(
cannotPublish
&&
'!bg-primary-200 !cursor-not-allowed'
,
'shrink-0 w-[70px] !h-8 !text-[13px] font-medium'
)
}
>
{
t
(
'appDebug.operation.applyConfig'
)
}
</
Button
>
{
debugWithMultipleModel
?
(
<
PublishWithMultipleModel
multipleModelConfigs=
{
multipleModelConfigs
}
onSelect=
{
item
=>
handlePublish
(
false
,
item
)
}
/>
)
:
(
<
Button
type=
'primary'
onClick=
{
()
=>
handlePublish
(
false
)
}
className=
{
cn
(
cannotPublish
&&
'!bg-primary-200 !cursor-not-allowed'
,
'shrink-0 w-[70px] !h-8 !text-[13px] font-medium'
)
}
>
{
t
(
'appDebug.operation.applyConfig'
)
}
</
Button
>
)
}
</
div
>
<
div
className=
'flex flex-col grow h-0 px-6 py-4 rounded-tl-2xl border-t border-l bg-gray-50 '
>
<
Debug
hasSetAPIKEY=
{
hasSettedApiKey
}
onSetting=
{
()
=>
setShowAccountSettingModal
({
payload
:
'provider'
})
}
inputs=
{
inputs
}
modelParameterParams=
{
{
setModel
:
setModel
as
any
,
onCompletionParamsChange
:
setCompletionParams
,
}
}
debugWithMultipleModel=
{
debugWithMultipleModel
}
multipleModelConfigs=
{
multipleModelConfigs
}
onMultipleModelConfigsChange=
{
handleMultipleModelConfigsChange
}
/>
</
div
>
</
div
>
}
...
...
@@ -829,6 +886,13 @@ const Configuration: FC = () => {
hasSetAPIKEY=
{
hasSettedApiKey
}
onSetting=
{
()
=>
setShowAccountSettingModal
({
payload
:
'provider'
})
}
inputs=
{
inputs
}
modelParameterParams=
{
{
setModel
:
setModel
as
any
,
onCompletionParamsChange
:
setCompletionParams
,
}
}
debugWithMultipleModel=
{
debugWithMultipleModel
}
multipleModelConfigs=
{
multipleModelConfigs
}
onMultipleModelConfigsChange=
{
handleMultipleModelConfigsChange
}
/>
</
Drawer
>
)
}
...
...
web/app/components/app/text-generate/item/index.tsx
View file @
d5361b8d
...
...
@@ -49,6 +49,9 @@ export type IGenerationItemProps = {
isShowTextToSpeech
?:
boolean
appId
?:
string
varList
?:
{
label
:
string
;
value
:
string
|
number
|
object
}[]
innerClassName
?:
string
contentClassName
?:
string
footerClassName
?:
string
}
export
const
SimpleBtn
=
({
className
,
isDisabled
,
onClick
,
children
}:
{
...
...
@@ -95,6 +98,8 @@ const GenerationItem: FC<IGenerationItemProps> = ({
isShowTextToSpeech
,
appId
,
varList
,
innerClassName
,
contentClassName
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
params
=
useParams
()
...
...
@@ -177,7 +182,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
const
handleOpenLogModal
=
async
(
setModal
:
Dispatch
<
SetStateAction
<
boolean
>>
)
=>
{
const
data
=
await
fetchTextGenerationMessge
({
appId
:
params
.
appId
,
appId
:
params
.
appId
as
string
,
messageId
:
messageId
!
,
})
setPromptLog
(
data
.
message
as
any
||
[])
...
...
@@ -249,7 +254,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
)
:
(
<
div
className=
{
cn
(
!
isTop
&&
'rounded-br-xl border-l-2 border-primary-400'
,
'p-4'
)
}
className=
{
cn
(
!
isTop
&&
'rounded-br-xl border-l-2 border-primary-400'
,
'p-4'
,
innerClassName
)
}
style=
{
mainStyle
}
>
{
(
isTop
&&
taskId
)
&&
(
...
...
@@ -258,7 +263,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
{
taskId
}
</
div
>)
}
<
div
className=
'flex'
>
<
div
className=
{
`flex ${contentClassName}`
}
>
<
div
className=
'grow w-0'
>
{
isError
?
<
div
className=
'text-gray-400 text-sm'
>
{
t
(
'share.generation.batchFailed.outputPlaceholder'
)
}
</
div
>
...
...
web/app/components/base/chat/chat/answer/agent-content.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
type
{
ChatItem
,
VisionFile
,
}
from
'../../types'
import
{
useChatContext
}
from
'../context'
import
{
Markdown
}
from
'@/app/components/base/markdown'
import
Thought
from
'@/app/components/app/chat/thought'
import
ImageGallery
from
'@/app/components/base/image-gallery'
type
AgentContentProps
=
{
item
:
ChatItem
}
const
AgentContent
:
FC
<
AgentContentProps
>
=
({
item
,
})
=>
{
const
{
allToolIcons
}
=
useChatContext
()
const
{
annotation
,
agent_thoughts
,
}
=
item
const
getImgs
=
(
list
?:
VisionFile
[])
=>
{
if
(
!
list
)
return
[]
return
list
.
filter
(
file
=>
file
.
type
===
'image'
&&
file
.
belongs_to
===
'assistant'
)
}
if
(
annotation
?.
logAnnotation
)
return
<
Markdown
content=
{
annotation
?.
logAnnotation
.
content
||
''
}
/>
return
(
<
div
>
{
agent_thoughts
?.
map
((
thought
,
index
)
=>
(
<
div
key=
{
index
}
>
{
thought
.
thought
&&
(
<
Markdown
content=
{
thought
.
thought
}
/>
)
}
{
/* {item.tool} */
}
{
/* perhaps not use tool */
}
{
!!
thought
.
tool
&&
(
<
Thought
thought=
{
thought
}
allToolIcons=
{
allToolIcons
||
{}
}
isFinished=
{
!!
thought
.
observation
}
/>
)
}
{
getImgs
(
thought
.
message_files
).
length
>
0
&&
(
<
ImageGallery
srcs=
{
getImgs
(
thought
.
message_files
).
map
(
file
=>
file
.
url
)
}
/>
)
}
</
div
>
))
}
</
div
>
)
}
export
default
AgentContent
web/app/components/base/chat/chat/answer/basic-content.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
type
{
ChatItem
}
from
'../../types'
import
{
Markdown
}
from
'@/app/components/base/markdown'
type
BasicContentProps
=
{
item
:
ChatItem
}
const
BasicContent
:
FC
<
BasicContentProps
>
=
({
item
,
})
=>
{
const
{
annotation
,
content
,
}
=
item
if
(
annotation
?.
logAnnotation
)
return
<
Markdown
content=
{
annotation
?.
logAnnotation
.
content
||
''
}
/>
return
<
Markdown
content=
{
content
}
/>
}
export
default
BasicContent
web/app/components/base/chat/chat/answer/index.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
ChatItem
}
from
'../../types'
import
{
useChatContext
}
from
'../context'
import
{
useCurrentAnswerIsResponsing
}
from
'../hooks'
import
Operation
from
'./operation'
import
AgentContent
from
'./agent-content'
import
BasicContent
from
'./basic-content'
import
SuggestedQuestions
from
'./suggested-questions'
import
More
from
'./more'
import
{
AnswerTriangle
}
from
'@/app/components/base/icons/src/vender/solid/general'
import
LoadingAnim
from
'@/app/components/app/chat/loading-anim'
import
Citation
from
'@/app/components/app/chat/citation'
import
{
EditTitle
}
from
'@/app/components/app/annotation/edit-annotation-modal/edit-item'
type
AnswerProps
=
{
item
:
ChatItem
}
const
Answer
:
FC
<
AnswerProps
>
=
({
item
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
config
,
answerIcon
,
}
=
useChatContext
()
const
responsing
=
useCurrentAnswerIsResponsing
(
item
.
id
)
const
{
content
,
citation
,
agent_thoughts
,
more
,
annotation
,
}
=
item
const
hasAgentThoughts
=
!!
agent_thoughts
?.
length
return
(
<
div
className=
'flex mb-2 last:mb-0'
>
<
div
className=
'shrink-0 relative w-10 h-10'
>
{
answerIcon
||
(
<
div
className=
'flex items-center justify-center w-full h-full rounded-full bg-[#d5f5f6] border-[0.5px] border-black/5 text-xl'
>
🤖
</
div
>
)
}
{
responsing
&&
(
<
div
className=
'absolute -top-[3px] -left-[3px] pl-[6px] flex items-center w-4 h-4 bg-white rounded-full shadow-xs border-[0.5px] border-gray-50'
>
<
LoadingAnim
type=
'avatar'
/>
</
div
>
)
}
</
div
>
<
div
className=
'chat-answer-container grow w-0 group ml-4'
>
<
div
className=
'relative pr-10'
>
<
AnswerTriangle
className=
'absolute -left-2 top-0 w-2 h-3 text-gray-100'
/>
<
div
className=
'group relative inline-block px-4 py-3 max-w-full bg-gray-100 rounded-b-2xl rounded-tr-2xl text-sm text-gray-900'
>
<
Operation
item=
{
item
}
/>
{
responsing
&&
!
content
&&
!
hasAgentThoughts
&&
(
<
div
className=
'flex items-center justify-center w-6 h-5'
>
<
LoadingAnim
type=
'text'
/>
</
div
>
)
}
{
content
&&
!
hasAgentThoughts
&&
(
<
BasicContent
item=
{
item
}
/>
)
}
{
hasAgentThoughts
&&
!
content
&&
(
<
AgentContent
item=
{
item
}
/>
)
}
{
annotation
?.
id
&&
!
annotation
?.
logAnnotation
&&
(
<
EditTitle
className=
'mt-1'
title=
{
t
(
'appAnnotation.editBy'
,
{
author
:
annotation
.
authorName
})
}
/>
)
}
<
SuggestedQuestions
item=
{
item
}
/>
{
!!
citation
?.
length
&&
config
?.
retriever_resource
?.
enabled
&&
!
responsing
&&
(
<
Citation
data=
{
citation
}
showHitInfo
/>
)
}
</
div
>
</
div
>
<
More
more=
{
more
}
/>
</
div
>
</
div
>
)
}
export
default
Answer
web/app/components/base/chat/chat/answer/more.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
ChatItem
}
from
'../../types'
import
{
formatNumber
}
from
'@/utils/format'
type
MoreProps
=
{
more
:
ChatItem
[
'more'
]
}
const
More
:
FC
<
MoreProps
>
=
({
more
,
})
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
div
className=
'flex items-center mt-1 h-[18px] text-xs text-gray-400 opacity-0 group-hover:opacity-100'
>
{
more
&&
(
<>
<
div
className=
'mr-2 shrink-0 truncate max-w-[33.3%]'
title=
{
`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`
}
>
{
`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`
}
</
div
>
<
div
className=
'shrink-0 truncate max-w-[33.3%]'
title=
{
`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`
}
>
{
`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`
}
</
div
>
<
div
className=
'shrink-0 mx-2'
>
·
</
div
>
<
div
className=
'shrink-0 truncate max-w-[33.3%]'
title=
{
more
.
time
}
>
{
more
.
time
}
</
div
>
</>
)
}
</
div
>
)
}
export
default
More
web/app/components/base/chat/chat/answer/operation.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
type
{
ChatItem
}
from
'../../types'
import
{
useCurrentAnswerIsResponsing
}
from
'../hooks'
import
{
useChatContext
}
from
'../context'
import
CopyBtn
from
'@/app/components/app/chat/copy-btn'
import
{
MessageFast
}
from
'@/app/components/base/icons/src/vender/solid/communication'
import
AudioBtn
from
'@/app/components/base/audio-btn'
type
OperationProps
=
{
item
:
ChatItem
}
const
Operation
:
FC
<
OperationProps
>
=
({
item
,
})
=>
{
const
{
config
}
=
useChatContext
()
const
responsing
=
useCurrentAnswerIsResponsing
(
item
.
id
)
const
{
isOpeningStatement
,
content
,
annotation
,
}
=
item
return
(
<
div
className=
'absolute top-[-14px] right-[-14px] flex justify-end gap-1'
>
{
!
isOpeningStatement
&&
!
responsing
&&
(
<
CopyBtn
value=
{
content
}
className=
'hidden group-hover:block'
/>
)
}
{
!
isOpeningStatement
&&
config
?.
text_to_speech
&&
(
<
AudioBtn
value=
{
content
}
className=
'hidden group-hover:block'
/>
)
}
{
annotation
?.
id
&&
(
<
div
className=
'relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-[#444CE7] shadow-md'
>
<
div
className=
'p-1 rounded-lg bg-[#EEF4FF] '
>
<
MessageFast
className=
'w-4 h-4'
/>
</
div
>
</
div
>
)
}
</
div
>
)
}
export
default
Operation
web/app/components/base/chat/chat/answer/suggested-questions.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
type
{
ChatItem
}
from
'../../types'
import
{
useChatContext
}
from
'../context'
type
SuggestedQuestionsProps
=
{
item
:
ChatItem
}
const
SuggestedQuestions
:
FC
<
SuggestedQuestionsProps
>
=
({
item
,
})
=>
{
const
{
onSend
}
=
useChatContext
()
const
{
isOpeningStatement
,
suggestedQuestions
,
}
=
item
if
(
!
isOpeningStatement
||
!
suggestedQuestions
?.
length
)
return
null
return
(
<
div
className=
'flex flex-wrap'
>
{
suggestedQuestions
.
map
((
question
,
index
)
=>
(
<
div
key=
{
index
}
className=
'mt-1 mr-1 max-w-full last:mr-0 shrink-0 py-[5px] leading-[18px] items-center px-4 rounded-lg border border-gray-200 shadow-xs bg-white text-xs font-medium text-primary-600 cursor-pointer'
onClick=
{
()
=>
onSend
?.(
question
)
}
>
{
question
}
</
div
>),
)
}
</
div
>
)
}
export
default
SuggestedQuestions
web/app/components/base/chat/chat/chat-input.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
useRef
,
useState
,
}
from
'react'
import
{
useContext
}
from
'use-context-selector'
import
Recorder
from
'js-audio-recorder'
import
{
useTranslation
}
from
'react-i18next'
import
Textarea
from
'rc-textarea'
import
type
{
EnableType
,
OnSend
,
VisionConfig
,
}
from
'../types'
import
{
TransferMethod
}
from
'../types'
import
TooltipPlus
from
'@/app/components/base/tooltip-plus'
import
{
ToastContext
}
from
'@/app/components/base/toast'
import
useBreakpoints
,
{
MediaType
}
from
'@/hooks/use-breakpoints'
import
VoiceInput
from
'@/app/components/base/voice-input'
import
{
Microphone01
}
from
'@/app/components/base/icons/src/vender/line/mediaAndDevices'
import
{
Microphone01
as
Microphone01Solid
}
from
'@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import
{
XCircle
}
from
'@/app/components/base/icons/src/vender/solid/general'
import
{
Send03
}
from
'@/app/components/base/icons/src/vender/solid/communication'
import
ChatImageUploader
from
'@/app/components/base/image-uploader/chat-image-uploader'
import
ImageList
from
'@/app/components/base/image-uploader/image-list'
import
{
useClipboardUploader
,
useDraggableUploader
,
useImageFiles
,
}
from
'@/app/components/base/image-uploader/hooks'
type
ChatInputProps
=
{
visionConfig
?:
VisionConfig
speechToTextConfig
?:
EnableType
onSend
?:
OnSend
}
const
ChatInput
:
FC
<
ChatInputProps
>
=
({
visionConfig
,
speechToTextConfig
,
onSend
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
notify
}
=
useContext
(
ToastContext
)
const
[
voiceInputShow
,
setVoiceInputShow
]
=
useState
(
false
)
const
{
files
,
onUpload
,
onRemove
,
onReUpload
,
onImageLinkLoadError
,
onImageLinkLoadSuccess
,
onClear
,
}
=
useImageFiles
()
const
{
onPaste
}
=
useClipboardUploader
({
onUpload
,
visionConfig
,
files
})
const
{
onDragEnter
,
onDragLeave
,
onDragOver
,
onDrop
,
isDragActive
}
=
useDraggableUploader
<
HTMLTextAreaElement
>
({
onUpload
,
files
,
visionConfig
})
const
isUseInputMethod
=
useRef
(
false
)
const
[
query
,
setQuery
]
=
useState
(
''
)
const
handleContentChange
=
(
e
:
React
.
ChangeEvent
<
HTMLTextAreaElement
>
)
=>
{
const
value
=
e
.
target
.
value
setQuery
(
value
)
}
const
handleSend
=
()
=>
{
if
(
onSend
)
{
onSend
(
query
,
files
.
filter
(
file
=>
file
.
progress
!==
-
1
).
map
(
fileItem
=>
({
type
:
'image'
,
transfer_method
:
fileItem
.
type
,
url
:
fileItem
.
url
,
upload_file_id
:
fileItem
.
fileId
,
})))
setQuery
(
''
)
}
if
(
!
files
.
find
(
item
=>
item
.
type
===
TransferMethod
.
local_file
&&
!
item
.
fileId
))
{
if
(
files
.
length
)
onClear
()
}
}
const
handleKeyUp
=
(
e
:
React
.
KeyboardEvent
<
HTMLTextAreaElement
>
)
=>
{
if
(
e
.
code
===
'Enter'
)
{
e
.
preventDefault
()
// prevent send message when using input method enter
if
(
!
e
.
shiftKey
&&
!
isUseInputMethod
.
current
)
handleSend
()
}
}
const
handleKeyDown
=
(
e
:
React
.
KeyboardEvent
<
HTMLTextAreaElement
>
)
=>
{
isUseInputMethod
.
current
=
e
.
nativeEvent
.
isComposing
if
(
e
.
code
===
'Enter'
&&
!
e
.
shiftKey
)
{
setQuery
(
query
.
replace
(
/
\n
$/
,
''
))
e
.
preventDefault
()
}
}
const
logError
=
(
message
:
string
)
=>
{
notify
({
type
:
'error'
,
message
,
duration
:
3000
})
}
const
handleVoiceInputShow
=
()
=>
{
(
Recorder
as
any
).
getPermission
().
then
(()
=>
{
setVoiceInputShow
(
true
)
},
()
=>
{
logError
(
t
(
'common.voiceInput.notAllow'
))
})
}
const
media
=
useBreakpoints
()
const
isMobile
=
media
===
MediaType
.
mobile
const
sendBtn
=
(
<
div
className=
'group flex items-center justify-center w-8 h-8 rounded-lg hover:bg-[#EBF5FF] cursor-pointer'
onClick=
{
handleSend
}
>
<
Send03
className=
{
`
w-5 h-5 text-gray-300 group-hover:text-primary-600
${!!query.trim() && 'text-primary-600'}
`
}
/>
</
div
>
)
return
(
<
div
className=
{
`
relative p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto
${isDragActive && 'border-primary-600'}
`
}
>
{
visionConfig
?.
enabled
&&
(
<>
<
div
className=
'absolute bottom-2 left-2 flex items-center'
>
<
ChatImageUploader
settings=
{
visionConfig
}
onUpload=
{
onUpload
}
disabled=
{
files
.
length
>=
visionConfig
.
number_limits
}
/>
<
div
className=
'mx-1 w-[1px] h-4 bg-black/5'
/>
</
div
>
<
div
className=
'pl-[52px]'
>
<
ImageList
list=
{
files
}
onRemove=
{
onRemove
}
onReUpload=
{
onReUpload
}
onImageLinkLoadSuccess=
{
onImageLinkLoadSuccess
}
onImageLinkLoadError=
{
onImageLinkLoadError
}
/>
</
div
>
</>
)
}
<
Textarea
className=
{
`
block w-full px-2 pr-[118px] py-[7px] leading-5 max-h-none text-sm text-gray-700 outline-none appearance-none resize-none
${visionConfig?.enabled && 'pl-12'}
`
}
value=
{
query
}
onChange=
{
handleContentChange
}
onKeyUp=
{
handleKeyUp
}
onKeyDown=
{
handleKeyDown
}
onPaste=
{
onPaste
}
onDragEnter=
{
onDragEnter
}
onDragLeave=
{
onDragLeave
}
onDragOver=
{
onDragOver
}
onDrop=
{
onDrop
}
autoSize
/>
<
div
className=
'absolute bottom-[7px] right-2 flex items-center h-8'
>
<
div
className=
'flex items-center px-1 h-5 rounded-md bg-gray-100 text-xs font-medium text-gray-500'
>
{
query
.
trim
().
length
}
</
div
>
{
query
?
(
<
div
className=
'flex justify-center items-center ml-2 w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg'
onClick=
{
()
=>
setQuery
(
''
)
}
>
<
XCircle
className=
'w-4 h-4 text-[#98A2B3]'
/>
</
div
>
)
:
speechToTextConfig
?.
enabled
?
(
<
div
className=
'group flex justify-center items-center ml-2 w-8 h-8 hover:bg-primary-50 rounded-lg cursor-pointer'
onClick=
{
handleVoiceInputShow
}
>
<
Microphone01
className=
'block w-4 h-4 text-gray-500 group-hover:hidden'
/>
<
Microphone01Solid
className=
'hidden w-4 h-4 text-primary-600 group-hover:block'
/>
</
div
>
)
:
null
}
<
div
className=
'mx-2 w-[1px] h-4 bg-black opacity-5'
/>
{
isMobile
?
sendBtn
:
(
<
TooltipPlus
popupContent=
{
<
div
>
<
div
>
{
t
(
'common.operation.send'
)
}
Enter
</
div
>
<
div
>
{
t
(
'common.operation.lineBreak'
)
}
Shift Enter
</
div
>
</
div
>
}
>
{
sendBtn
}
</
TooltipPlus
>
)
}
</
div
>
{
voiceInputShow
&&
(
<
VoiceInput
onCancel=
{
()
=>
setVoiceInputShow
(
false
)
}
onConverted=
{
text
=>
setQuery
(
text
)
}
/>
)
}
</
div
>
)
}
export
default
ChatInput
web/app/components/base/chat/chat/context.tsx
0 → 100644
View file @
d5361b8d
'use client'
import
type
{
ReactNode
}
from
'react'
import
{
createContext
,
useContext
}
from
'use-context-selector'
import
type
{
ChatConfig
,
ChatItem
,
OnSend
,
}
from
'../types'
import
type
{
Emoji
}
from
'@/app/components/tools/types'
export
type
ChatContextValue
=
{
config
?:
ChatConfig
isResponsing
?:
boolean
chatList
:
ChatItem
[]
showPromptLog
?:
boolean
questionIcon
?:
ReactNode
answerIcon
?:
ReactNode
allToolIcons
?:
Record
<
string
,
string
|
Emoji
>
onSend
?:
OnSend
}
const
ChatContext
=
createContext
<
ChatContextValue
>
({
chatList
:
[],
})
type
ChatContextProviderProps
=
{
children
:
ReactNode
}
&
ChatContextValue
export
const
ChatContextProvider
=
({
children
,
config
,
isResponsing
,
chatList
,
showPromptLog
,
questionIcon
,
answerIcon
,
allToolIcons
,
onSend
,
}:
ChatContextProviderProps
)
=>
{
return
(
<
ChatContext
.
Provider
value=
{
{
config
,
isResponsing
,
chatList
:
chatList
||
[],
showPromptLog
,
questionIcon
,
answerIcon
,
allToolIcons
,
onSend
,
}
}
>
{
children
}
</
ChatContext
.
Provider
>
)
}
export
const
useChatContext
=
()
=>
useContext
(
ChatContext
)
export
default
ChatContext
web/app/components/base/chat/chat/hooks.ts
0 → 100644
View file @
d5361b8d
This diff is collapsed.
Click to expand it.
web/app/components/base/chat/chat/index.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
,
ReactNode
,
}
from
'react'
import
{
memo
,
useRef
,
}
from
'react'
import
{
useThrottleEffect
}
from
'ahooks'
import
type
{
ChatConfig
,
ChatItem
,
OnSend
,
}
from
'../types'
import
Question
from
'./question'
import
Answer
from
'./answer'
import
ChatInput
from
'./chat-input'
import
TryToAsk
from
'./try-to-ask'
import
{
ChatContextProvider
}
from
'./context'
import
type
{
Emoji
}
from
'@/app/components/tools/types'
export
type
ChatProps
=
{
config
:
ChatConfig
onSend
?:
OnSend
chatList
:
ChatItem
[]
isResponsing
:
boolean
noChatInput
?:
boolean
chatContainerclassName
?:
string
chatFooterClassName
?:
string
suggestedQuestions
?:
string
[]
showPromptLog
?:
boolean
questionIcon
?:
ReactNode
answerIcon
?:
ReactNode
allToolIcons
?:
Record
<
string
,
string
|
Emoji
>
}
const
Chat
:
FC
<
ChatProps
>
=
({
config
,
onSend
,
chatList
,
isResponsing
,
noChatInput
,
chatContainerclassName
,
chatFooterClassName
,
suggestedQuestions
,
showPromptLog
,
questionIcon
,
answerIcon
,
allToolIcons
,
})
=>
{
const
ref
=
useRef
<
HTMLDivElement
>
(
null
)
const
chatFooterRef
=
useRef
<
HTMLDivElement
>
(
null
)
useThrottleEffect
(()
=>
{
if
(
ref
.
current
)
ref
.
current
.
scrollTop
=
ref
.
current
.
scrollHeight
},
[
chatList
],
{
wait
:
500
})
const
hasTryToAsk
=
config
.
suggested_questions_after_answer
?.
enabled
&&
!!
suggestedQuestions
?.
length
&&
onSend
return
(
<
ChatContextProvider
config=
{
config
}
chatList=
{
chatList
}
isResponsing=
{
isResponsing
}
showPromptLog=
{
showPromptLog
}
questionIcon=
{
questionIcon
}
answerIcon=
{
answerIcon
}
allToolIcons=
{
allToolIcons
}
onSend=
{
onSend
}
>
<
div
className=
'relative h-full'
>
<
div
ref=
{
ref
}
className=
{
`relative h-full overflow-y-auto ${chatContainerclassName}`
}
>
{
chatList
.
map
((
item
)
=>
{
if
(
item
.
isAnswer
)
{
return
(
<
Answer
key=
{
item
.
id
}
item=
{
item
}
/>
)
}
return
(
<
Question
key=
{
item
.
id
}
item=
{
item
}
/>
)
})
}
{
(
hasTryToAsk
||
!
noChatInput
)
&&
(
<
div
className=
{
`sticky bottom-0 w-full backdrop-blur-[20px] ${chatFooterClassName}`
}
ref=
{
chatFooterRef
}
style=
{
{
background
:
'linear-gradient(0deg, #FFF 0%, rgba(255, 255, 255, 0.40) 100%)'
,
}
}
>
{
hasTryToAsk
&&
(
<
TryToAsk
suggestedQuestions=
{
suggestedQuestions
}
onSend=
{
onSend
}
/>
)
}
{
!
noChatInput
&&
(
<
ChatInput
visionConfig=
{
config
?.
file_upload
?.
image
}
speechToTextConfig=
{
config
.
speech_to_text
}
onSend=
{
onSend
}
/>
)
}
</
div
>
)
}
</
div
>
</
div
>
</
ChatContextProvider
>
)
}
export
default
memo
(
Chat
)
web/app/components/base/chat/chat/question.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
useRef
}
from
'react'
import
type
{
ChatItem
}
from
'../types'
import
{
useChatContext
}
from
'./context'
import
{
QuestionTriangle
}
from
'@/app/components/base/icons/src/vender/solid/general'
import
{
User
}
from
'@/app/components/base/icons/src/public/avatar'
import
Log
from
'@/app/components/app/chat/log'
import
{
Markdown
}
from
'@/app/components/base/markdown'
import
ImageGallery
from
'@/app/components/base/image-gallery'
type
QuestionProps
=
{
item
:
ChatItem
}
const
Question
:
FC
<
QuestionProps
>
=
({
item
,
})
=>
{
const
ref
=
useRef
(
null
)
const
{
showPromptLog
,
isResponsing
,
questionIcon
,
}
=
useChatContext
()
const
{
content
,
message_files
,
}
=
item
const
imgSrcs
=
message_files
?.
length
?
message_files
.
map
(
item
=>
item
.
url
)
:
[]
return
(
<
div
className=
'flex justify-end mb-2 last:mb-0 pl-10'
ref=
{
ref
}
>
<
div
className=
'group relative mr-4'
>
<
QuestionTriangle
className=
'absolute -right-2 top-0 w-2 h-3 text-[#D1E9FF]/50'
/>
{
showPromptLog
&&
!
isResponsing
&&
(
<
Log
log=
{
item
.
log
!
}
containerRef=
{
ref
}
/>
)
}
<
div
className=
'px-4 py-3 bg-[#D1E9FF]/50 rounded-b-2xl rounded-tl-2xl text-sm text-gray-900'
>
{
!!
imgSrcs
.
length
&&
(
<
ImageGallery
srcs=
{
imgSrcs
}
/>
)
}
<
Markdown
content=
{
content
}
/>
</
div
>
<
div
className=
'mt-1 h-[18px]'
/>
</
div
>
<
div
className=
'shrink-0 w-10 h-10'
>
{
questionIcon
||
(
<
div
className=
'w-full h-full rounded-full border-[0.5px] border-black/5'
>
<
User
className=
'w-full h-full'
/>
</
div
>
)
}
</
div
>
</
div
>
)
}
export
default
Question
web/app/components/base/chat/chat/try-to-ask.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
OnSend
}
from
'../types'
import
{
Star04
}
from
'@/app/components/base/icons/src/vender/solid/shapes'
import
Button
from
'@/app/components/base/button'
type
TryToAskProps
=
{
suggestedQuestions
:
string
[]
onSend
:
OnSend
}
const
TryToAsk
:
FC
<
TryToAskProps
>
=
({
suggestedQuestions
,
onSend
,
})
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
div
>
<
div
className=
'flex items-center mb-2.5 py-2'
>
<
div
className=
'grow h-[1px]'
style=
{
{
background
:
'linear-gradient(270deg, #F3F4F6 0%, rgba(243, 244, 246, 0) 100%)'
,
}
}
/>
<
div
className=
'shrink-0 flex items-center px-3 text-gray-500'
>
<
Star04
className=
'mr-1 w-2.5 h-2.5'
/>
<
span
className=
'text-xs text-gray-500 font-medium'
>
{
t
(
'appDebug.feature.suggestedQuestionsAfterAnswer.tryToAsk'
)
}
</
span
>
</
div
>
<
div
className=
'grow h-[1px]'
style=
{
{
background
:
'linear-gradient(270deg, rgba(243, 244, 246, 0) 0%, #F3F4F6 100%)'
,
}
}
/>
</
div
>
<
div
className=
'flex flex-wrap'
>
{
suggestedQuestions
.
map
((
suggestQuestion
,
index
)
=>
(
<
Button
key=
{
index
}
className=
'mb-2 mr-2 last:mr-0 px-3 py-[5px] bg-white text-primary-600 text-xs font-medium'
onClick=
{
()
=>
onSend
(
suggestQuestion
)
}
>
{
suggestQuestion
}
</
Button
>
))
}
</
div
>
</
div
>
)
}
export
default
TryToAsk
web/app/components/base/chat/types.ts
0 → 100644
View file @
d5361b8d
import
type
{
ModelConfig
,
VisionFile
,
VisionSettings
,
}
from
'@/types/app'
import
type
{
IChatItem
}
from
'@/app/components/app/chat/type'
export
type
{
VisionFile
}
from
'@/types/app'
export
{
TransferMethod
}
from
'@/types/app'
export
type
{
Inputs
,
PromptVariable
,
}
from
'@/models/debug'
export
type
UserInputForm
=
{
default
:
string
label
:
string
required
:
boolean
variable
:
string
}
export
type
UserInputFormTextInput
=
{
'text-inpput'
:
UserInputForm
&
{
max_length
:
number
}
}
export
type
UserInputFormSelect
=
{
'select'
:
UserInputForm
&
{
options
:
string
[]
}
}
export
type
UserInputFormParagraph
=
{
'paragraph'
:
UserInputForm
}
export
type
VisionConfig
=
VisionSettings
export
type
EnableType
=
{
enabled
:
boolean
}
export
type
ChatConfig
=
Omit
<
ModelConfig
,
'model'
>
export
type
ChatItem
=
IChatItem
export
type
OnSend
=
(
message
:
string
,
files
?:
VisionFile
[])
=>
void
web/app/components/base/dropdown/index.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
useState
}
from
'react'
import
{
DotsHorizontal
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
PortalToFollowElem
,
PortalToFollowElemContent
,
PortalToFollowElemTrigger
,
}
from
'@/app/components/base/portal-to-follow-elem'
export
type
Item
=
{
value
:
string
text
:
string
}
type
DropdownProps
=
{
items
:
Item
[]
secondItems
?:
Item
[]
onSelect
:
(
item
:
Item
)
=>
void
renderTrigger
?:
(
open
:
boolean
)
=>
React
.
ReactNode
}
const
Dropdown
:
FC
<
DropdownProps
>
=
({
items
,
onSelect
,
secondItems
,
renderTrigger
,
})
=>
{
const
[
open
,
setOpen
]
=
useState
(
false
)
return
(
<
PortalToFollowElem
open=
{
open
}
onOpenChange=
{
setOpen
}
placement=
'bottom-end'
>
<
PortalToFollowElemTrigger
onClick=
{
()
=>
setOpen
(
v
=>
!
v
)
}
>
{
renderTrigger
?
renderTrigger
(
open
)
:
(
<
div
className=
{
`
flex items-center justify-center w-6 h-6 cursor-pointer rounded-md
${open && 'bg-black/5'}
`
}
>
<
DotsHorizontal
className=
'w-4 h-4 text-gray-500'
/>
</
div
>
)
}
</
PortalToFollowElemTrigger
>
<
PortalToFollowElemContent
>
<
div
className=
'rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg text-sm text-gray-700'
>
{
!!
items
.
length
&&
(
<
div
className=
'p-1'
>
{
items
.
map
(
item
=>
(
<
div
key=
{
item
.
value
}
className=
'flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100'
onClick=
{
()
=>
onSelect
(
item
)
}
>
{
item
.
text
}
</
div
>
))
}
</
div
>
)
}
{
(
!!
items
.
length
&&
!!
secondItems
?.
length
)
&&
(
<
div
className=
'h-[1px] bg-gray-100'
/>
)
}
{
!!
secondItems
?.
length
&&
(
<
div
className=
'p-1'
>
{
secondItems
.
map
(
item
=>
(
<
div
key=
{
item
.
value
}
className=
'flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100'
onClick=
{
()
=>
onSelect
(
item
)
}
>
{
item
.
text
}
</
div
>
))
}
</
div
>
)
}
</
div
>
</
PortalToFollowElemContent
>
</
PortalToFollowElem
>
)
}
export
default
Dropdown
web/app/components/base/icons/assets/vender/solid/communication/message-heart-circle.svg
0 → 100644
View file @
d5361b8d
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"message-heart-circle"
>
<path
id=
"Solid"
fill-rule=
"evenodd"
clip-rule=
"evenodd"
d=
"M8.33334 1.3335C4.83554 1.3335 2.00001 4.16903 2.00001 7.66683C2.00001 8.3735 2.116 9.05444 2.33051 9.69084C2.36824 9.80278 2.39045 9.86902 2.40488 9.91786L2.40961 9.93431L2.40711 9.93952C2.38997 9.97486 2.36451 10.0223 2.31687 10.1105L1.21562 12.1489C1.14736 12.2751 1.07614 12.4069 1.02717 12.5214C0.978485 12.6353 0.89963 12.8442 0.93843 13.0919C0.983911 13.3822 1.15477 13.6378 1.40562 13.7908C1.61963 13.9213 1.84282 13.9283 1.96665 13.9269C2.09123 13.9254 2.24018 13.91 2.38296 13.8952L5.8196 13.54C5.87464 13.5343 5.90342 13.5314 5.92449 13.5297L5.92721 13.5295L5.93545 13.5325C5.96135 13.5418 5.99648 13.5553 6.05711 13.5786C6.76441 13.8511 7.53226 14.0002 8.33334 14.0002C11.8311 14.0002 14.6667 11.1646 14.6667 7.66683C14.6667 4.16903 11.8311 1.3335 8.33334 1.3335ZM5.97972 5.72165C6.73124 5.08746 7.73145 5.27376 8.33126 5.96633C8.93106 5.27376 9.91836 5.09414 10.6828 5.72165C11.4472 6.34916 11.5401 7.41616 10.9499 8.16621C10.5843 8.63089 9.66661 9.4796 9.02123 10.0581C8.78417 10.2706 8.66564 10.3769 8.52339 10.4197C8.40136 10.4564 8.26116 10.4564 8.13913 10.4197C7.99688 10.3769 7.87835 10.2706 7.64128 10.0581C6.9959 9.4796 6.0782 8.63089 5.71257 8.16621C5.1224 7.41616 5.22821 6.35583 5.97972 5.72165Z"
fill=
"#DD2590"
/>
</g>
</svg>
web/app/components/base/icons/assets/vender/solid/communication/send-03.svg
0 → 100644
View file @
d5361b8d
<svg
width=
"20"
height=
"20"
viewBox=
"0 0 20 20"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"send-03"
>
<path
id=
"Solid"
d=
"M18.4385 10.5535C18.6111 10.2043 18.6111 9.79465 18.4385 9.44548C18.2865 9.13803 18.0197 8.97682 17.8815 8.89905C17.7327 8.81532 17.542 8.72955 17.3519 8.64403L3.36539 2.35014C3.17087 2.26257 2.97694 2.17526 2.81335 2.11859C2.66315 2.06656 2.36076 1.97151 2.02596 2.06467C1.64761 2.16994 1.34073 2.4469 1.19734 2.81251C1.07045 3.13604 1.13411 3.44656 1.17051 3.60129C1.21017 3.76983 1.27721 3.9717 1.34445 4.17418L2.69818 8.25278C2.80718 8.58118 2.86168 8.74537 2.96302 8.86678C3.05252 8.97399 3.16752 9.05699 3.29746 9.10816C3.44462 9.1661 3.61762 9.1661 3.96363 9.1661H10.0001C10.4603 9.1661 10.8334 9.53919 10.8334 9.99943C10.8334 10.4597 10.4603 10.8328 10.0001 10.8328H3.97939C3.63425 10.8328 3.46168 10.8328 3.3148 10.8905C3.18508 10.9414 3.07022 11.0241 2.98072 11.1309C2.87937 11.2519 2.82459 11.4155 2.71502 11.7428L1.3504 15.8191C1.28243 16.0221 1.21472 16.2242 1.17455 16.3929C1.13773 16.5476 1.07301 16.8587 1.19956 17.1831C1.34245 17.5493 1.64936 17.827 2.02806 17.9327C2.36342 18.0263 2.6665 17.9309 2.81674 17.8789C2.98066 17.8221 3.17507 17.7346 3.37023 17.6467L17.3518 11.355C17.542 11.2695 17.7327 11.1837 17.8815 11.0999C18.0197 11.0222 18.2865 10.861 18.4385 10.5535Z"
fill=
"#D0D5DD"
/>
</g>
</svg>
web/app/components/base/icons/assets/vender/solid/general/answer-triangle.svg
0 → 100644
View file @
d5361b8d
<svg
width=
"8"
height=
"12"
viewBox=
"0 0 8 12"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<path
id=
"Rectangle 1"
d=
"M1.03647 1.5547C0.59343 0.890144 1.06982 0 1.86852 0H8V12L1.03647 1.5547Z"
fill=
"#F2F4F7"
/>
</svg>
web/app/components/base/icons/assets/vender/solid/general/question-triangle.svg
0 → 100644
View file @
d5361b8d
<svg
width=
"8"
height=
"12"
viewBox=
"0 0 8 12"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"Rectangle 2"
>
<path
d=
"M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z"
fill=
"white"
/>
<path
d=
"M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z"
fill=
"#D1E9FF"
fill-opacity=
"0.5"
/>
</g>
</svg>
web/app/components/base/icons/assets/vender/solid/shapes/star-04.svg
0 → 100644
View file @
d5361b8d
<svg
width=
"11"
height=
"10"
viewBox=
"0 0 11 10"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"star-04"
>
<path
id=
"Solid"
d=
"M5.88897 0.683596C5.82708 0.522683 5.67249 0.416504 5.50008 0.416504C5.32768 0.416504 5.17308 0.522683 5.11119 0.683596L4.27287 2.86321C4.1477 3.18865 4.10837 3.28243 4.05457 3.35809C4.00059 3.43401 3.93426 3.50034 3.85834 3.55433C3.78267 3.60813 3.68889 3.64746 3.36346 3.77263L1.18384 4.61094C1.02293 4.67283 0.916748 4.82743 0.916748 4.99984C0.916748 5.17224 1.02293 5.32684 1.18384 5.38873L3.36346 6.22705C3.68889 6.35221 3.78267 6.39155 3.85834 6.44535C3.93426 6.49933 4.00059 6.56566 4.05457 6.64158C4.10837 6.71724 4.1477 6.81102 4.27287 7.13646L5.11119 9.31608C5.17308 9.47699 5.32768 9.58317 5.50008 9.58317C5.67249 9.58317 5.82709 9.47699 5.88898 9.31608L6.72729 7.13646C6.85246 6.81102 6.89179 6.71724 6.94559 6.64158C6.99957 6.56566 7.06591 6.49933 7.14183 6.44535C7.21749 6.39155 7.31127 6.35221 7.6367 6.22705L9.81632 5.38873C9.97723 5.32684 10.0834 5.17224 10.0834 4.99984C10.0834 4.82743 9.97723 4.67283 9.81632 4.61094L7.6367 3.77263C7.31127 3.64746 7.21749 3.60813 7.14183 3.55433C7.06591 3.50034 6.99957 3.43401 6.94559 3.35809C6.89179 3.28243 6.85246 3.18865 6.72729 2.86321L5.88897 0.683596Z"
fill=
"#667085"
/>
</g>
</svg>
web/app/components/base/icons/src/vender/solid/communication/MessageHeartCircle.json
0 → 100644
View file @
d5361b8d
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"16"
,
"height"
:
"16"
,
"viewBox"
:
"0 0 16 16"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"message-heart-circle"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Solid"
,
"fill-rule"
:
"evenodd"
,
"clip-rule"
:
"evenodd"
,
"d"
:
"M8.33334 1.3335C4.83554 1.3335 2.00001 4.16903 2.00001 7.66683C2.00001 8.3735 2.116 9.05444 2.33051 9.69084C2.36824 9.80278 2.39045 9.86902 2.40488 9.91786L2.40961 9.93431L2.40711 9.93952C2.38997 9.97486 2.36451 10.0223 2.31687 10.1105L1.21562 12.1489C1.14736 12.2751 1.07614 12.4069 1.02717 12.5214C0.978485 12.6353 0.89963 12.8442 0.93843 13.0919C0.983911 13.3822 1.15477 13.6378 1.40562 13.7908C1.61963 13.9213 1.84282 13.9283 1.96665 13.9269C2.09123 13.9254 2.24018 13.91 2.38296 13.8952L5.8196 13.54C5.87464 13.5343 5.90342 13.5314 5.92449 13.5297L5.92721 13.5295L5.93545 13.5325C5.96135 13.5418 5.99648 13.5553 6.05711 13.5786C6.76441 13.8511 7.53226 14.0002 8.33334 14.0002C11.8311 14.0002 14.6667 11.1646 14.6667 7.66683C14.6667 4.16903 11.8311 1.3335 8.33334 1.3335ZM5.97972 5.72165C6.73124 5.08746 7.73145 5.27376 8.33126 5.96633C8.93106 5.27376 9.91836 5.09414 10.6828 5.72165C11.4472 6.34916 11.5401 7.41616 10.9499 8.16621C10.5843 8.63089 9.66661 9.4796 9.02123 10.0581C8.78417 10.2706 8.66564 10.3769 8.52339 10.4197C8.40136 10.4564 8.26116 10.4564 8.13913 10.4197C7.99688 10.3769 7.87835 10.2706 7.64128 10.0581C6.9959 9.4796 6.0782 8.63089 5.71257 8.16621C5.1224 7.41616 5.22821 6.35583 5.97972 5.72165Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"MessageHeartCircle"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/communication/MessageHeartCircle.tsx
0 → 100644
View file @
d5361b8d
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./MessageHeartCircle.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
Icon
.
displayName
=
'MessageHeartCircle'
export
default
Icon
web/app/components/base/icons/src/vender/solid/communication/Send03.json
0 → 100644
View file @
d5361b8d
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"20"
,
"height"
:
"20"
,
"viewBox"
:
"0 0 20 20"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"send-03"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Solid"
,
"d"
:
"M18.4385 10.5535C18.6111 10.2043 18.6111 9.79465 18.4385 9.44548C18.2865 9.13803 18.0197 8.97682 17.8815 8.89905C17.7327 8.81532 17.542 8.72955 17.3519 8.64403L3.36539 2.35014C3.17087 2.26257 2.97694 2.17526 2.81335 2.11859C2.66315 2.06656 2.36076 1.97151 2.02596 2.06467C1.64761 2.16994 1.34073 2.4469 1.19734 2.81251C1.07045 3.13604 1.13411 3.44656 1.17051 3.60129C1.21017 3.76983 1.27721 3.9717 1.34445 4.17418L2.69818 8.25278C2.80718 8.58118 2.86168 8.74537 2.96302 8.86678C3.05252 8.97399 3.16752 9.05699 3.29746 9.10816C3.44462 9.1661 3.61762 9.1661 3.96363 9.1661H10.0001C10.4603 9.1661 10.8334 9.53919 10.8334 9.99943C10.8334 10.4597 10.4603 10.8328 10.0001 10.8328H3.97939C3.63425 10.8328 3.46168 10.8328 3.3148 10.8905C3.18508 10.9414 3.07022 11.0241 2.98072 11.1309C2.87937 11.2519 2.82459 11.4155 2.71502 11.7428L1.3504 15.8191C1.28243 16.0221 1.21472 16.2242 1.17455 16.3929C1.13773 16.5476 1.07301 16.8587 1.19956 17.1831C1.34245 17.5493 1.64936 17.827 2.02806 17.9327C2.36342 18.0263 2.6665 17.9309 2.81674 17.8789C2.98066 17.8221 3.17507 17.7346 3.37023 17.6467L17.3518 11.355C17.542 11.2695 17.7327 11.1837 17.8815 11.0999C18.0197 11.0222 18.2865 10.861 18.4385 10.5535Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"Send03"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/communication/Send03.tsx
0 → 100644
View file @
d5361b8d
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./Send03.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
Icon
.
displayName
=
'Send03'
export
default
Icon
web/app/components/base/icons/src/vender/solid/communication/index.ts
View file @
d5361b8d
...
...
@@ -2,3 +2,5 @@ export { default as AiText } from './AiText'
export
{
default
as
CuteRobote
}
from
'./CuteRobote'
export
{
default
as
MessageDotsCircle
}
from
'./MessageDotsCircle'
export
{
default
as
MessageFast
}
from
'./MessageFast'
export
{
default
as
MessageHeartCircle
}
from
'./MessageHeartCircle'
export
{
default
as
Send03
}
from
'./Send03'
web/app/components/base/icons/src/vender/solid/general/AnswerTriangle.json
0 → 100644
View file @
d5361b8d
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"8"
,
"height"
:
"12"
,
"viewBox"
:
"0 0 8 12"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Rectangle 1"
,
"d"
:
"M1.03647 1.5547C0.59343 0.890144 1.06982 0 1.86852 0H8V12L1.03647 1.5547Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
}
]
},
"name"
:
"AnswerTriangle"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/general/AnswerTriangle.tsx
0 → 100644
View file @
d5361b8d
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./AnswerTriangle.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
Icon
.
displayName
=
'AnswerTriangle'
export
default
Icon
web/app/components/base/icons/src/vender/solid/general/QuestionTriangle.json
0 → 100644
View file @
d5361b8d
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"8"
,
"height"
:
"12"
,
"viewBox"
:
"0 0 8 12"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"Rectangle 2"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
},
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z"
,
"fill"
:
"currentColor"
,
"fill-opacity"
:
"0.5"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"QuestionTriangle"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/general/QuestionTriangle.tsx
0 → 100644
View file @
d5361b8d
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./QuestionTriangle.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
Icon
.
displayName
=
'QuestionTriangle'
export
default
Icon
web/app/components/base/icons/src/vender/solid/general/index.ts
View file @
d5361b8d
export
{
default
as
AnswerTriangle
}
from
'./AnswerTriangle'
export
{
default
as
CheckCircle
}
from
'./CheckCircle'
export
{
default
as
CheckDone01
}
from
'./CheckDone01'
export
{
default
as
Download02
}
from
'./Download02'
...
...
@@ -5,6 +6,7 @@ export { default as Edit04 } from './Edit04'
export
{
default
as
Eye
}
from
'./Eye'
export
{
default
as
MessageClockCircle
}
from
'./MessageClockCircle'
export
{
default
as
PlusCircle
}
from
'./PlusCircle'
export
{
default
as
QuestionTriangle
}
from
'./QuestionTriangle'
export
{
default
as
SearchMd
}
from
'./SearchMd'
export
{
default
as
Target04
}
from
'./Target04'
export
{
default
as
Tool03
}
from
'./Tool03'
...
...
web/app/components/base/icons/src/vender/solid/shapes/Star04.json
0 → 100644
View file @
d5361b8d
{
"icon"
:
{
"type"
:
"element"
,
"isRootNode"
:
true
,
"name"
:
"svg"
,
"attributes"
:
{
"width"
:
"11"
,
"height"
:
"10"
,
"viewBox"
:
"0 0 11 10"
,
"fill"
:
"none"
,
"xmlns"
:
"http://www.w3.org/2000/svg"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"star-04"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Solid"
,
"d"
:
"M5.88897 0.683596C5.82708 0.522683 5.67249 0.416504 5.50008 0.416504C5.32768 0.416504 5.17308 0.522683 5.11119 0.683596L4.27287 2.86321C4.1477 3.18865 4.10837 3.28243 4.05457 3.35809C4.00059 3.43401 3.93426 3.50034 3.85834 3.55433C3.78267 3.60813 3.68889 3.64746 3.36346 3.77263L1.18384 4.61094C1.02293 4.67283 0.916748 4.82743 0.916748 4.99984C0.916748 5.17224 1.02293 5.32684 1.18384 5.38873L3.36346 6.22705C3.68889 6.35221 3.78267 6.39155 3.85834 6.44535C3.93426 6.49933 4.00059 6.56566 4.05457 6.64158C4.10837 6.71724 4.1477 6.81102 4.27287 7.13646L5.11119 9.31608C5.17308 9.47699 5.32768 9.58317 5.50008 9.58317C5.67249 9.58317 5.82709 9.47699 5.88898 9.31608L6.72729 7.13646C6.85246 6.81102 6.89179 6.71724 6.94559 6.64158C6.99957 6.56566 7.06591 6.49933 7.14183 6.44535C7.21749 6.39155 7.31127 6.35221 7.6367 6.22705L9.81632 5.38873C9.97723 5.32684 10.0834 5.17224 10.0834 4.99984C10.0834 4.82743 9.97723 4.67283 9.81632 4.61094L7.6367 3.77263C7.31127 3.64746 7.21749 3.60813 7.14183 3.55433C7.06591 3.50034 6.99957 3.43401 6.94559 3.35809C6.89179 3.28243 6.85246 3.18865 6.72729 2.86321L5.88897 0.683596Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"Star04"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/shapes/Star04.tsx
0 → 100644
View file @
d5361b8d
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./Star04.json'
import
IconBase
from
'@/app/components/base/icons/IconBase'
import
type
{
IconBaseProps
,
IconData
}
from
'@/app/components/base/icons/IconBase'
const
Icon
=
React
.
forwardRef
<
React
.
MutableRefObject
<
SVGElement
>
,
Omit
<
IconBaseProps
,
'data'
>>
((
props
,
ref
,
)
=>
<
IconBase
{
...
props
}
ref=
{
ref
}
data=
{
data
as
IconData
}
/>)
Icon
.
displayName
=
'Star04'
export
default
Icon
web/app/components/base/icons/src/vender/solid/shapes/index.ts
0 → 100644
View file @
d5361b8d
export
{
default
as
Star04
}
from
'./Star04'
web/app/components/base/text-generation/hooks.ts
0 → 100644
View file @
d5361b8d
import
{
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useToastContext
}
from
'@/app/components/base/toast'
import
{
ssePost
}
from
'@/service/base'
export
const
useTextGeneration
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
notify
}
=
useToastContext
()
const
[
isResponsing
,
setIsResponsing
]
=
useState
(
false
)
const
[
completion
,
setCompletion
]
=
useState
(
''
)
const
[
messageId
,
setMessageId
]
=
useState
<
string
|
null
>
(
null
)
const
handleSend
=
async
(
url
:
string
,
data
:
any
,
)
=>
{
if
(
isResponsing
)
{
notify
({
type
:
'info'
,
message
:
t
(
'appDebug.errorMessage.waitForResponse'
)
})
return
false
}
setIsResponsing
(
true
)
setCompletion
(
''
)
setMessageId
(
''
)
let
res
:
string
[]
=
[]
ssePost
(
url
,
{
body
:
{
response_mode
:
'streaming'
,
...
data
,
},
},
{
onData
:
(
data
:
string
,
_isFirstMessage
:
boolean
,
{
messageId
})
=>
{
res
.
push
(
data
)
setCompletion
(
res
.
join
(
''
))
setMessageId
(
messageId
)
},
onMessageReplace
:
(
messageReplace
)
=>
{
res
=
[
messageReplace
.
answer
]
setCompletion
(
res
.
join
(
''
))
},
onCompleted
()
{
setIsResponsing
(
false
)
},
onError
()
{
setIsResponsing
(
false
)
},
})
return
true
}
return
{
completion
,
isResponsing
,
setIsResponsing
,
handleSend
,
messageId
,
}
}
web/app/components/base/text-generation/types.ts
0 → 100644
View file @
d5361b8d
import
type
{
ModelConfig
,
VisionFile
,
VisionSettings
,
}
from
'@/types/app'
export
type
{
VisionFile
}
from
'@/types/app'
export
{
TransferMethod
}
from
'@/types/app'
export
type
UserInputForm
=
{
default
:
string
label
:
string
required
:
boolean
variable
:
string
}
export
type
UserInputFormTextInput
=
{
'text-inpput'
:
UserInputForm
&
{
max_length
:
number
}
}
export
type
UserInputFormSelect
=
{
'select'
:
UserInputForm
&
{
options
:
string
[]
}
}
export
type
UserInputFormParagraph
=
{
'paragraph'
:
UserInputForm
}
export
type
VisionConfig
=
VisionSettings
export
type
EnableType
=
{
enabled
:
boolean
}
export
type
TextGenerationConfig
=
Omit
<
ModelConfig
,
'model'
>
export
type
OnSend
=
(
message
:
string
,
files
?:
VisionFile
[])
=>
void
web/app/components/header/account-setting/model-provider-page/model-parameter-modal/index.tsx
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
type
{
FC
,
ReactNode
,
}
from
'react'
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
import
useSWR
from
'swr'
import
cn
from
'classnames'
...
...
@@ -8,32 +11,25 @@ import type {
FormValue
,
ModelParameterRule
,
}
from
'../declarations'
import
{
MODEL_STATUS_TEXT
,
ModelStatusEnum
,
}
from
'../declarations'
import
ModelIcon
from
'../model-icon'
import
ModelName
from
'../model-name'
import
{
ModelStatusEnum
}
from
'../declarations'
import
ModelSelector
from
'../model-selector'
import
{
useLanguage
,
useTextGenerationCurrentProviderAndModelAndModelList
,
}
from
'../hooks'
import
{
isNullOrUndefined
}
from
'../utils'
import
ParameterItem
from
'./parameter-item'
import
type
{
ParameterValue
}
from
'./parameter-item'
import
Trigger
from
'./trigger'
import
type
{
TriggerProps
}
from
'./trigger'
import
{
PortalToFollowElem
,
PortalToFollowElemContent
,
PortalToFollowElemTrigger
,
}
from
'@/app/components/base/portal-to-follow-elem'
import
{
SlidersH
}
from
'@/app/components/base/icons/src/vender/line/mediaAndDevices'
import
{
AlertTriangle
}
from
'@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import
{
CubeOutline
}
from
'@/app/components/base/icons/src/vender/line/shapes'
import
{
fetchModelParameterRules
}
from
'@/service/common'
import
Loading
from
'@/app/components/base/loading'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
TooltipPlus
from
'@/app/components/base/tooltip-plus'
import
Radio
from
'@/app/components/base/radio'
import
{
TONE_LIST
}
from
'@/config'
import
{
Brush01
}
from
'@/app/components/base/icons/src/vender/solid/editor'
...
...
@@ -41,15 +37,19 @@ import { Scales02 } from '@/app/components/base/icons/src/vender/solid/FinanceAn
import
{
Target04
}
from
'@/app/components/base/icons/src/vender/solid/general'
import
{
Sliders02
}
from
'@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import
useBreakpoints
,
{
MediaType
}
from
'@/hooks/use-breakpoints'
import
{
ArrowNarrowLeft
}
from
'@/app/components/base/icons/src/vender/line/arrows'
type
ModelParameterModalProps
=
{
export
type
ModelParameterModalProps
=
{
isAdvancedMode
:
boolean
mode
:
string
modelId
:
string
provider
:
string
setModel
:
(
model
:
{
modelId
:
string
;
provider
:
string
;
mode
?:
string
;
features
:
string
[]
})
=>
void
setModel
:
(
model
:
{
modelId
:
string
;
provider
:
string
;
mode
?:
string
;
features
?
:
string
[]
})
=>
void
completionParams
:
FormValue
onCompletionParamsChange
:
(
newParams
:
FormValue
)
=>
void
debugWithMultipleModel
:
boolean
onDebugWithMultipleModelChange
:
()
=>
void
renderTrigger
?:
(
v
:
TriggerProps
)
=>
ReactNode
}
const
stopParameerRule
:
ModelParameterRule
=
{
default
:
[],
...
...
@@ -78,14 +78,16 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
setModel
,
completionParams
,
onCompletionParamsChange
,
debugWithMultipleModel
,
onDebugWithMultipleModelChange
,
renderTrigger
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
language
=
useLanguage
()
const
{
hasSettedApiKey
,
modelProviders
}
=
useProviderContext
()
const
{
hasSettedApiKey
}
=
useProviderContext
()
const
media
=
useBreakpoints
()
const
isMobile
=
media
===
MediaType
.
mobile
const
[
open
,
setOpen
]
=
useState
(
false
)
const
{
data
:
parameterRulesData
,
isLoading
}
=
useSWR
(
`/workspaces/current/model-providers/
${
provider
}
/models/parameter-rules?model=
${
modelId
}
`
,
fetchModelParameterRules
)
const
{
data
:
parameterRulesData
,
isLoading
}
=
useSWR
(
(
provider
&&
modelId
)
?
`/workspaces/current/model-providers/
${
provider
}
/models/parameter-rules?model=
${
modelId
}
`
:
null
,
fetchModelParameterRules
)
const
{
currentProvider
,
currentModel
,
...
...
@@ -220,71 +222,32 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
onClick=
{
()
=>
setOpen
(
v
=>
!
v
)
}
className=
'block'
>
<
div
className=
{
`
flex items-center px-2 h-8 rounded-lg border cursor-pointer hover:border-[1.5px]
${disabled ? 'border-[#F79009] bg-[#FFFAEB]' : 'border-[#444CE7] bg-primary-50'}
`
}
>
{
currentProvider
&&
(
<
ModelIcon
className=
'mr-1.5 !w-5 !h-5'
provider=
{
currentProvider
}
modelName=
{
currentModel
?.
model
}
/>
)
}
{
!
currentProvider
&&
(
<
ModelIcon
className=
'mr-1.5 !w-5 !h-5'
provider=
{
modelProviders
.
find
(
item
=>
item
.
provider
===
provider
)
}
modelName=
{
modelId
}
/>
)
}
{
currentModel
&&
(
<
ModelName
className=
'mr-1.5 text-gray-900'
modelItem=
{
currentModel
}
showMode
modeClassName=
'!text-[#444CE7] !border-[#A4BCFD]'
showFeatures
featuresClassName=
'!text-[#444CE7] !border-[#A4BCFD]'
/>
)
}
{
!
currentModel
&&
(
<
div
className=
'mr-1 text-[13px] font-medium text-gray-900 truncate'
>
{
modelId
}
</
div
>
)
}
{
disabled
?
(
<
TooltipPlus
popupContent=
{
hasDeprecated
?
t
(
'common.modelProvider.deprecated'
)
:
(
modelDisabled
&&
currentModel
)
?
MODEL_STATUS_TEXT
[
currentModel
.
status
as
string
][
language
]
:
''
}
>
<
AlertTriangle
className=
'w-4 h-4 text-[#F79009]'
/>
</
TooltipPlus
>
)
renderTrigger
?
renderTrigger
({
open
,
disabled
,
modelDisabled
,
hasDeprecated
,
currentProvider
,
currentModel
,
providerName
:
provider
,
modelId
,
})
:
(
<
SlidersH
className=
'w-4 h-4 text-indigo-600'
/>
<
Trigger
disabled=
{
disabled
}
modelDisabled=
{
modelDisabled
}
hasDeprecated=
{
hasDeprecated
}
currentProvider=
{
currentProvider
}
currentModel=
{
currentModel
}
providerName=
{
provider
}
modelId=
{
modelId
}
/>
)
}
</
div
>
</
PortalToFollowElemTrigger
>
<
PortalToFollowElemContent
>
<
PortalToFollowElemContent
className=
'z-[60]'
>
<
div
className=
'w-[496px] rounded-xl border border-gray-100 bg-white shadow-xl'
>
<
div
className=
'flex items-center px-4 h-12 rounded-t-xl border-b border-gray-100 bg-gray-50 text-md font-medium text-gray-900'
>
<
CubeOutline
className=
'mr-2 w-4 h-4 text-primary-600'
/>
...
...
@@ -296,7 +259,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
{
t
(
'common.modelProvider.model'
)
}
</
div
>
<
ModelSelector
defaultModel=
{
{
provider
,
model
:
modelId
}
}
defaultModel=
{
(
provider
||
modelId
)
?
{
provider
,
model
:
modelId
}
:
undefined
}
modelList=
{
textGenerationModelList
}
onSelect=
{
handleChangeModel
}
/>
...
...
@@ -370,6 +333,17 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
)
}
</
div
>
<
div
className=
'flex items-center justify-between px-6 h-[50px] bg-gray-50 border-t border-t-gray-100 text-xs font-medium text-primary-600 cursor-pointer rounded-b-xl'
onClick=
{
()
=>
onDebugWithMultipleModelChange
()
}
>
{
debugWithMultipleModel
?
t
(
'appDebug.debugAsSingleModel'
)
:
t
(
'appDebug.debugAsMultipleModel'
)
}
<
ArrowNarrowLeft
className=
'w-3 h-3 rotate-180'
/>
</
div
>
</
div
>
</
PortalToFollowElemContent
>
</
div
>
...
...
web/app/components/header/account-setting/model-provider-page/model-parameter-modal/trigger.tsx
0 → 100644
View file @
d5361b8d
import
type
{
FC
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
Model
,
ModelItem
,
ModelProvider
,
}
from
'../declarations'
import
{
MODEL_STATUS_TEXT
}
from
'../declarations'
import
{
useLanguage
}
from
'../hooks'
import
ModelIcon
from
'../model-icon'
import
ModelName
from
'../model-name'
import
{
useProviderContext
}
from
'@/context/provider-context'
import
{
SlidersH
}
from
'@/app/components/base/icons/src/vender/line/mediaAndDevices'
import
{
AlertTriangle
}
from
'@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import
TooltipPlus
from
'@/app/components/base/tooltip-plus'
export
type
TriggerProps
=
{
open
?:
boolean
disabled
?:
boolean
currentProvider
?:
ModelProvider
|
Model
currentModel
?:
ModelItem
providerName
?:
string
modelId
?:
string
hasDeprecated
?:
boolean
modelDisabled
?:
boolean
}
const
Trigger
:
FC
<
TriggerProps
>
=
({
disabled
,
currentProvider
,
currentModel
,
providerName
,
modelId
,
hasDeprecated
,
modelDisabled
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
language
=
useLanguage
()
const
{
modelProviders
}
=
useProviderContext
()
return
(
<
div
className=
{
`
flex items-center px-2 h-8 rounded-lg border cursor-pointer hover:border-[1.5px]
${disabled ? 'border-[#F79009] bg-[#FFFAEB]' : 'border-[#444CE7] bg-primary-50'}
`
}
>
{
currentProvider
&&
(
<
ModelIcon
className=
'mr-1.5 !w-5 !h-5'
provider=
{
currentProvider
}
modelName=
{
currentModel
?.
model
}
/>
)
}
{
!
currentProvider
&&
(
<
ModelIcon
className=
'mr-1.5 !w-5 !h-5'
provider=
{
modelProviders
.
find
(
item
=>
item
.
provider
===
providerName
)
}
modelName=
{
modelId
}
/>
)
}
{
currentModel
&&
(
<
ModelName
className=
'mr-1.5 text-gray-900'
modelItem=
{
currentModel
}
showMode
modeClassName=
'!text-[#444CE7] !border-[#A4BCFD]'
showFeatures
featuresClassName=
'!text-[#444CE7] !border-[#A4BCFD]'
/>
)
}
{
!
currentModel
&&
(
<
div
className=
'mr-1 text-[13px] font-medium text-gray-900 truncate'
>
{
modelId
}
</
div
>
)
}
{
disabled
?
(
<
TooltipPlus
popupContent=
{
hasDeprecated
?
t
(
'common.modelProvider.deprecated'
)
:
(
modelDisabled
&&
currentModel
)
?
MODEL_STATUS_TEXT
[
currentModel
.
status
as
string
][
language
]
:
''
}
>
<
AlertTriangle
className=
'w-4 h-4 text-[#F79009]'
/>
</
TooltipPlus
>
)
:
(
<
SlidersH
className=
'w-4 h-4 text-indigo-600'
/>
)
}
</
div
>
)
}
export
default
Trigger
web/context/debug-configuration.ts
View file @
d5361b8d
import
{
createContext
}
from
'use-context-selector'
import
{
createContext
,
useContext
}
from
'use-context-selector'
import
{
PromptMode
}
from
'@/models/debug'
import
type
{
AnnotationReplyConfig
,
...
...
@@ -239,4 +239,6 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({
setVisionConfig
:
()
=>
{
},
})
export
const
useDebugConfigurationContext
=
()
=>
useContext
(
DebugConfigurationContext
)
export
default
DebugConfigurationContext
web/i18n/lang/app-debug.en.ts
View file @
d5361b8d
...
...
@@ -352,6 +352,10 @@ const translation = {
score_thresholdTip
:
'Used to set the similarity threshold for chunks filtering.'
,
retrieveChangeTip
:
'Modifying the index mode and retrieval mode may affect applications associated with this Knowledge.'
,
},
debugAsSingleModel
:
'Debug as Single Model'
,
debugAsMultipleModel
:
'Debug as Multiple Models'
,
duplicateModel
:
'Dusplicate'
,
publishAs
:
'Publish as'
,
assistantType
:
{
name
:
'Assistant Type'
,
chatAssistant
:
{
...
...
web/i18n/lang/app-debug.zh.ts
View file @
d5361b8d
...
...
@@ -346,6 +346,10 @@ const translation = {
score_thresholdTip
:
'用于设置文本片段筛选的相似度阈值。'
,
retrieveChangeTip
:
'修改索引模式和检索模式可能会影响与该知识库关联的应用程序。'
,
},
debugAsSingleModel
:
'单一模型进行调试'
,
debugAsMultipleModel
:
'多个模型进行调试'
,
duplicateModel
:
'复制模型'
,
publishAs
:
'发布为'
,
assistantType
:
{
name
:
'助手类型'
,
chatAssistant
:
{
...
...
web/service/base.ts
View file @
d5361b8d
...
...
@@ -175,9 +175,15 @@ const baseFetch = <T>(
bodyStringify
=
true
,
needAllResponseContent
,
deleteContentType
,
getAbortController
,
}:
IOtherOptions
,
):
Promise
<
T
>
=>
{
const
options
:
typeof
baseOptions
&
FetchOptionType
=
Object
.
assign
({},
baseOptions
,
fetchOptions
)
if
(
getAbortController
)
{
const
abortController
=
new
AbortController
()
getAbortController
(
abortController
)
options
.
signal
=
abortController
.
signal
}
if
(
isPublicAPI
)
{
const
sharedToken
=
globalThis
.
location
.
pathname
.
split
(
'/'
).
slice
(
-
1
)[
0
]
const
accessToken
=
localStorage
.
getItem
(
'token'
)
||
JSON
.
stringify
({
[
sharedToken
]:
''
})
...
...
web/service/debug.ts
View file @
d5361b8d
...
...
@@ -45,15 +45,23 @@ export const sendCompletionMessage = async (appId: string, body: Record<string,
},
{
onData
,
onCompleted
,
onError
,
onMessageReplace
})
}
export
const
fetchSuggestedQuestions
=
(
appId
:
string
,
messageId
:
string
)
=>
{
return
get
(
`apps/
${
appId
}
/chat-messages/
${
messageId
}
/suggested-questions`
)
export
const
fetchSuggestedQuestions
=
(
appId
:
string
,
messageId
:
string
,
getAbortController
?:
any
)
=>
{
return
get
(
`apps/
${
appId
}
/chat-messages/
${
messageId
}
/suggested-questions`
,
{},
{
getAbortController
,
},
)
}
export
const
fetchConvesationMessages
=
(
appId
:
string
,
conversation_id
:
string
)
=>
{
export
const
fetchConvesationMessages
=
(
appId
:
string
,
conversation_id
:
string
,
getAbortController
?:
any
)
=>
{
return
get
(
`apps/
${
appId
}
/chat-messages`
,
{
params
:
{
conversation_id
,
},
},
{
getAbortController
,
})
}
...
...
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