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
1b04382a
Unverified
Commit
1b04382a
authored
Feb 07, 2024
by
zxhlyh
Committed by
GitHub
Feb 07, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: chat agent mode content copy (#2418)
parent
71e5828d
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
166 additions
and
133 deletions
+166
-133
agent-content.tsx
web/app/components/base/chat/chat/answer/agent-content.tsx
+8
-7
basic-content.tsx
web/app/components/base/chat/chat/answer/basic-content.tsx
+2
-1
index.tsx
web/app/components/base/chat/chat/answer/index.tsx
+24
-11
more.tsx
web/app/components/base/chat/chat/answer/more.tsx
+2
-1
operation.tsx
web/app/components/base/chat/chat/answer/operation.tsx
+16
-6
suggested-questions.tsx
.../components/base/chat/chat/answer/suggested-questions.tsx
+2
-1
chat-input.tsx
web/app/components/base/chat/chat/chat-input.tsx
+87
-84
hooks.ts
web/app/components/base/chat/chat/hooks.ts
+0
-12
index.tsx
web/app/components/base/chat/chat/index.tsx
+8
-0
question.tsx
web/app/components/base/chat/chat/question.tsx
+15
-9
try-to-ask.tsx
web/app/components/base/chat/chat/try-to-ask.tsx
+2
-1
No files found.
web/app/components/base/chat/chat/answer/agent-content.tsx
View file @
1b04382a
import
type
{
FC
}
from
'react'
import
{
memo
}
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'
import
type
{
Emoji
}
from
'@/app/components/tools/types'
type
AgentContentProps
=
{
item
:
ChatItem
responsing
?:
boolean
allToolIcons
?:
Record
<
string
,
string
|
Emoji
>
}
const
AgentContent
:
FC
<
AgentContentProps
>
=
({
item
,
responsing
,
allToolIcons
,
})
=>
{
const
{
allToolIcons
,
isResponsing
,
}
=
useChatContext
()
const
{
annotation
,
agent_thoughts
,
...
...
@@ -45,7 +46,7 @@ const AgentContent: FC<AgentContentProps> = ({
<
Thought
thought=
{
thought
}
allToolIcons=
{
allToolIcons
||
{}
}
isFinished=
{
!!
thought
.
observation
||
!
isR
esponsing
}
isFinished=
{
!!
thought
.
observation
||
!
r
esponsing
}
/>
)
}
...
...
@@ -58,4 +59,4 @@ const AgentContent: FC<AgentContentProps> = ({
)
}
export
default
AgentContent
export
default
memo
(
AgentContent
)
web/app/components/base/chat/chat/answer/basic-content.tsx
View file @
1b04382a
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
import
type
{
ChatItem
}
from
'../../types'
import
{
Markdown
}
from
'@/app/components/base/markdown'
...
...
@@ -19,4 +20,4 @@ const BasicContent: FC<BasicContentProps> = ({
return
<
Markdown
content=
{
content
}
/>
}
export
default
BasicContent
export
default
memo
(
BasicContent
)
web/app/components/base/chat/chat/answer/index.tsx
View file @
1b04382a
import
type
{
FC
}
from
'react'
import
type
{
FC
,
ReactNode
,
}
from
'react'
import
{
memo
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
ChatItem
}
from
'../../types'
import
{
useChatContext
}
from
'../context'
import
{
useCurrentAnswerIsResponsing
}
from
'../hooks'
import
type
{
ChatConfig
,
ChatItem
,
}
from
'../../types'
import
Operation
from
'./operation'
import
AgentContent
from
'./agent-content'
import
BasicContent
from
'./basic-content'
...
...
@@ -12,23 +17,27 @@ import { AnswerTriangle } from '@/app/components/base/icons/src/vender/solid/gen
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'
import
type
{
Emoji
}
from
'@/app/components/tools/types'
type
AnswerProps
=
{
item
:
ChatItem
question
:
string
index
:
number
config
?:
ChatConfig
answerIcon
?:
ReactNode
responsing
?:
boolean
allToolIcons
?:
Record
<
string
,
string
|
Emoji
>
}
const
Answer
:
FC
<
AnswerProps
>
=
({
item
,
question
,
index
,
config
,
answerIcon
,
responsing
,
allToolIcons
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
config
,
answerIcon
,
}
=
useChatContext
()
const
responsing
=
useCurrentAnswerIsResponsing
(
item
.
id
)
const
{
content
,
citation
,
...
...
@@ -83,7 +92,11 @@ const Answer: FC<AnswerProps> = ({
}
{
hasAgentThoughts
&&
(
<
AgentContent
item=
{
item
}
/>
<
AgentContent
item=
{
item
}
responsing=
{
responsing
}
allToolIcons=
{
allToolIcons
}
/>
)
}
{
...
...
@@ -108,4 +121,4 @@ const Answer: FC<AnswerProps> = ({
)
}
export
default
Answer
export
default
memo
(
Answer
)
web/app/components/base/chat/chat/answer/more.tsx
View file @
1b04382a
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
ChatItem
}
from
'../../types'
import
{
formatNumber
}
from
'@/utils/format'
...
...
@@ -42,4 +43,4 @@ const More: FC<MoreProps> = ({
)
}
export
default
More
export
default
memo
(
More
)
web/app/components/base/chat/chat/answer/operation.tsx
View file @
1b04382a
import
type
{
FC
}
from
'react'
import
{
useState
}
from
'react'
import
{
memo
,
useMemo
,
useState
,
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
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'
...
...
@@ -34,17 +37,24 @@ const Operation: FC<OperationProps> = ({
onFeedback
,
}
=
useChatContext
()
const
[
isShowReplyModal
,
setIsShowReplyModal
]
=
useState
(
false
)
const
responsing
=
useCurrentAnswerIsResponsing
(
item
.
id
)
const
{
id
,
isOpeningStatement
,
content
,
content
:
messageContent
,
annotation
,
feedback
,
agent_thoughts
,
}
=
item
const
hasAnnotation
=
!!
annotation
?.
id
const
[
localFeedback
,
setLocalFeedback
]
=
useState
(
feedback
)
const
content
=
useMemo
(()
=>
{
if
(
agent_thoughts
?.
length
)
return
agent_thoughts
.
reduce
((
acc
,
cur
)
=>
acc
+
cur
.
thought
,
''
)
return
messageContent
},
[
agent_thoughts
,
messageContent
])
const
handleFeedback
=
async
(
rating
:
'like'
|
'dislike'
|
null
)
=>
{
if
(
!
config
?.
supportFeedback
||
!
onFeedback
)
return
...
...
@@ -56,7 +66,7 @@ const Operation: FC<OperationProps> = ({
return
(
<
div
className=
'absolute top-[-14px] right-[-14px] flex justify-end gap-1'
>
{
!
isOpeningStatement
&&
!
responsing
&&
(
!
isOpeningStatement
&&
(
<
CopyBtn
value=
{
content
}
className=
'hidden group-hover:block'
...
...
@@ -159,4 +169,4 @@ const Operation: FC<OperationProps> = ({
)
}
export
default
Operation
export
default
memo
(
Operation
)
web/app/components/base/chat/chat/answer/suggested-questions.tsx
View file @
1b04382a
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
import
type
{
ChatItem
}
from
'../../types'
import
{
useChatContext
}
from
'../context'
...
...
@@ -32,4 +33,4 @@ const SuggestedQuestions: FC<SuggestedQuestionsProps> = ({
)
}
export
default
SuggestedQuestions
export
default
memo
(
SuggestedQuestions
)
web/app/components/base/chat/chat/chat-input.tsx
View file @
1b04382a
import
type
{
FC
}
from
'react'
import
{
memo
,
useRef
,
useState
,
}
from
'react'
...
...
@@ -126,100 +127,102 @@ const ChatInput: FC<ChatInputProps> = ({
)
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
<
div
className=
'relative'
>
<
div
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
'}
p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto
${
isDragActive && 'border-primary-600
'}
`
}
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]'
/>
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
>
)
:
speechToTextConfig
?.
enabled
</>
)
}
<
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=
'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
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
>
)
:
null
:
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
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
export
default
memo
(
ChatInput
)
web/app/components/base/chat/chat/hooks.ts
View file @
1b04382a
...
...
@@ -14,7 +14,6 @@ import type {
PromptVariable
,
VisionFile
,
}
from
'../types'
import
{
useChatContext
}
from
'./context'
import
{
TransferMethod
}
from
'@/types/app'
import
{
useToastContext
}
from
'@/app/components/base/toast'
import
{
ssePost
}
from
'@/service/base'
...
...
@@ -507,14 +506,3 @@ export const useChat = (
handleAnnotationRemoved
,
}
}
export
const
useCurrentAnswerIsResponsing
=
(
answerId
:
string
)
=>
{
const
{
isResponsing
,
chatList
,
}
=
useChatContext
()
const
isLast
=
answerId
===
chatList
[
chatList
.
length
-
1
]?.
id
return
isLast
&&
isResponsing
}
web/app/components/base/chat/chat/index.tsx
View file @
1b04382a
...
...
@@ -140,12 +140,17 @@ const Chat: FC<ChatProps> = ({
{
chatList
.
map
((
item
,
index
)
=>
{
if
(
item
.
isAnswer
)
{
const
isLast
=
item
.
id
===
chatList
[
chatList
.
length
-
1
]?.
id
return
(
<
Answer
key=
{
item
.
id
}
item=
{
item
}
question=
{
chatList
[
index
-
1
]?.
content
}
index=
{
index
}
config=
{
config
}
answerIcon=
{
answerIcon
}
responsing=
{
isLast
&&
isResponsing
}
allToolIcons=
{
allToolIcons
}
/>
)
}
...
...
@@ -153,6 +158,9 @@ const Chat: FC<ChatProps> = ({
<
Question
key=
{
item
.
id
}
item=
{
item
}
showPromptLog=
{
showPromptLog
}
questionIcon=
{
questionIcon
}
isResponsing=
{
isResponsing
}
/>
)
})
...
...
web/app/components/base/chat/chat/question.tsx
View file @
1b04382a
import
type
{
FC
}
from
'react'
import
{
useRef
}
from
'react'
import
type
{
FC
,
ReactNode
,
}
from
'react'
import
{
memo
,
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'
...
...
@@ -10,16 +15,17 @@ import ImageGallery from '@/app/components/base/image-gallery'
type
QuestionProps
=
{
item
:
ChatItem
showPromptLog
?:
boolean
questionIcon
?:
ReactNode
isResponsing
?:
boolean
}
const
Question
:
FC
<
QuestionProps
>
=
({
item
,
showPromptLog
,
isResponsing
,
questionIcon
,
})
=>
{
const
ref
=
useRef
(
null
)
const
{
showPromptLog
,
isResponsing
,
questionIcon
,
}
=
useChatContext
()
const
{
content
,
message_files
,
...
...
@@ -59,4 +65,4 @@ const Question: FC<QuestionProps> = ({
)
}
export
default
Question
export
default
memo
(
Question
)
web/app/components/base/chat/chat/try-to-ask.tsx
View file @
1b04382a
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
OnSend
}
from
'../types'
import
{
Star04
}
from
'@/app/components/base/icons/src/vender/solid/shapes'
...
...
@@ -51,4 +52,4 @@ const TryToAsk: FC<TryToAskProps> = ({
)
}
export
default
TryToAsk
export
default
memo
(
TryToAsk
)
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