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
51d35926
Unverified
Commit
51d35926
authored
Feb 04, 2024
by
zxhlyh
Committed by
GitHub
Feb 04, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: replace chat in web app (#2373)
parent
3f0c5153
Changes
49
Show whitespace changes
Inline
Side-by-side
Showing
49 changed files
with
2100 additions
and
92 deletions
+2100
-92
page.tsx
web/app/(shareLayout)/chat/[token]/page.tsx
+4
-2
chat-wrapper.tsx
...p/components/base/chat/chat-with-history/chat-wrapper.tsx
+141
-0
form.tsx
...ponents/base/chat/chat-with-history/config-panel/form.tsx
+82
-0
index.tsx
...onents/base/chat/chat-with-history/config-panel/index.tsx
+158
-0
context.tsx
web/app/components/base/chat/chat-with-history/context.tsx
+74
-0
header-in-mobile.tsx
...mponents/base/chat/chat-with-history/header-in-mobile.tsx
+60
-0
header.tsx
web/app/components/base/chat/chat-with-history/header.tsx
+25
-0
hooks.tsx
web/app/components/base/chat/chat-with-history/hooks.tsx
+385
-0
index.tsx
web/app/components/base/chat/chat-with-history/index.tsx
+195
-0
index.tsx
.../components/base/chat/chat-with-history/sidebar/index.tsx
+141
-0
item.tsx
...p/components/base/chat/chat-with-history/sidebar/item.tsx
+58
-0
list.tsx
...p/components/base/chat/chat-with-history/sidebar/list.tsx
+46
-0
index.tsx
web/app/components/base/chat/chat/answer/index.tsx
+1
-1
operation.tsx
web/app/components/base/chat/chat/answer/operation.tsx
+65
-0
suggested-questions.tsx
.../components/base/chat/chat/answer/suggested-questions.tsx
+1
-1
context.tsx
web/app/components/base/chat/chat/context.tsx
+3
-0
hooks.ts
web/app/components/base/chat/chat/hooks.ts
+41
-29
index.tsx
web/app/components/base/chat/chat/index.tsx
+68
-42
try-to-ask.tsx
web/app/components/base/chat/chat/try-to-ask.tsx
+1
-1
constants.ts
web/app/components/base/chat/constants.ts
+1
-0
types.ts
web/app/components/base/chat/types.ts
+9
-0
common.tsx
web/app/components/base/confirm/common.tsx
+3
-0
thumbs-down.svg
...cons/assets/vender/line/alertsAndFeedback/thumbs-down.svg
+10
-0
thumbs-up.svg
.../icons/assets/vender/line/alertsAndFeedback/thumbs-up.svg
+10
-0
edit-05.svg
...ponents/base/icons/assets/vender/line/general/edit-05.svg
+10
-0
menu-01.svg
...ponents/base/icons/assets/vender/line/general/menu-01.svg
+5
-0
pin-01.svg
...mponents/base/icons/assets/vender/line/general/pin-01.svg
+5
-0
star-06.svg
...ponents/base/icons/assets/vender/solid/shapes/star-06.svg
+9
-0
ThumbsDown.json
...e/icons/src/vender/line/alertsAndFeedback/ThumbsDown.json
+66
-0
ThumbsDown.tsx
...se/icons/src/vender/line/alertsAndFeedback/ThumbsDown.tsx
+16
-0
ThumbsUp.json
...ase/icons/src/vender/line/alertsAndFeedback/ThumbsUp.json
+66
-0
ThumbsUp.tsx
...base/icons/src/vender/line/alertsAndFeedback/ThumbsUp.tsx
+16
-0
index.ts
...nts/base/icons/src/vender/line/alertsAndFeedback/index.ts
+2
-0
Edit05.json
...components/base/icons/src/vender/line/general/Edit05.json
+66
-0
Edit05.tsx
.../components/base/icons/src/vender/line/general/Edit05.tsx
+16
-0
Menu01.json
...components/base/icons/src/vender/line/general/Menu01.json
+39
-0
Menu01.tsx
.../components/base/icons/src/vender/line/general/Menu01.tsx
+16
-0
Pin01.json
.../components/base/icons/src/vender/line/general/Pin01.json
+39
-0
Pin01.tsx
...p/components/base/icons/src/vender/line/general/Pin01.tsx
+16
-0
index.ts
...pp/components/base/icons/src/vender/line/general/index.ts
+3
-0
Star06.json
...components/base/icons/src/vender/solid/shapes/Star06.json
+62
-0
Star06.tsx
.../components/base/icons/src/vender/solid/shapes/Star06.tsx
+16
-0
index.ts
...pp/components/base/icons/src/vender/solid/shapes/index.ts
+1
-0
index.tsx
web/app/components/explore/index.tsx
+1
-1
index.tsx
web/app/components/explore/installed-app/index.tsx
+2
-2
index.tsx
web/app/components/share/chat/index.tsx
+1
-1
share.ts
web/models/share.ts
+20
-5
share.ts
web/service/share.ts
+14
-7
app.ts
web/types/app.ts
+11
-0
No files found.
web/app/(shareLayout)/chat/[token]/page.tsx
View file @
51d35926
'use client'
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
type
{
IMainProps
}
from
'@/app/components/share/chat'
import
Main
from
'@/app/components/share/chat
'
import
ChatWithHistoryWrap
from
'@/app/components/base/chat/chat-with-history
'
const
Chat
:
FC
<
IMainProps
>
=
()
=>
{
return
(
<
Main
/>
<
ChatWithHistoryWrap
/>
)
}
...
...
web/app/components/base/chat/chat-with-history/chat-wrapper.tsx
0 → 100644
View file @
51d35926
import
{
useCallback
,
useEffect
,
useMemo
}
from
'react'
import
Chat
from
'../chat'
import
type
{
ChatConfig
,
OnSend
,
}
from
'../types'
import
{
useChat
}
from
'../chat/hooks'
import
{
useChatWithHistoryContext
}
from
'./context'
import
Header
from
'./header'
import
ConfigPanel
from
'./config-panel'
import
{
fetchSuggestedQuestions
,
getUrl
,
}
from
'@/service/share'
const
ChatWrapper
=
()
=>
{
const
{
appParams
,
appPrevChatList
,
currentConversationId
,
currentConversationItem
,
inputsForms
,
newConversationInputs
,
handleNewConversationCompleted
,
isMobile
,
isInstalledApp
,
appId
,
appMeta
,
handleFeedback
,
currentChatInstanceRef
,
}
=
useChatWithHistoryContext
()
const
appConfig
=
useMemo
(()
=>
{
const
config
=
appParams
||
{}
return
{
...
config
,
supportFeedback
:
true
,
}
as
ChatConfig
},
[
appParams
])
const
{
chatList
,
handleSend
,
handleStop
,
isResponsing
,
suggestedQuestions
,
}
=
useChat
(
appConfig
,
undefined
,
appPrevChatList
,
)
useEffect
(()
=>
{
if
(
currentChatInstanceRef
.
current
)
currentChatInstanceRef
.
current
.
handleStop
=
handleStop
},
[])
const
doSend
:
OnSend
=
useCallback
((
message
,
files
)
=>
{
const
data
:
any
=
{
query
:
message
,
inputs
:
currentConversationId
?
currentConversationItem
?.
inputs
:
newConversationInputs
,
conversation_id
:
currentConversationId
,
}
if
(
appConfig
?.
file_upload
?.
image
.
enabled
&&
files
?.
length
)
data
.
files
=
files
handleSend
(
getUrl
(
'chat-messages'
,
isInstalledApp
,
appId
||
''
),
data
,
{
onGetSuggestedQuestions
:
responseItemId
=>
fetchSuggestedQuestions
(
responseItemId
,
isInstalledApp
,
appId
),
onConversationComplete
:
currentConversationId
?
undefined
:
handleNewConversationCompleted
,
isPublicAPI
:
!
isInstalledApp
,
},
)
},
[
appConfig
,
currentConversationId
,
currentConversationItem
,
handleSend
,
newConversationInputs
,
handleNewConversationCompleted
,
isInstalledApp
,
appId
,
])
const
chatNode
=
useMemo
(()
=>
{
if
(
inputsForms
.
length
)
{
return
(
<>
<
Header
isMobile=
{
isMobile
}
title=
{
currentConversationItem
?.
name
||
''
}
/>
{
!
currentConversationId
&&
(
<
div
className=
{
`mx-auto w-full max-w-[720px] ${isMobile && 'px-4'}`
}
>
<
div
className=
'mb-6'
/>
<
ConfigPanel
/>
<
div
className=
'my-6 h-[1px]'
style=
{
{
background
:
'linear-gradient(90deg, rgba(242, 244, 247, 0.00) 0%, #F2F4F7 49.17%, rgba(242, 244, 247, 0.00) 100%)'
}
}
/>
</
div
>
)
}
</>
)
}
return
(
<
Header
isMobile=
{
isMobile
}
title=
{
currentConversationItem
?.
name
||
''
}
/>
)
},
[
currentConversationId
,
inputsForms
,
currentConversationItem
,
isMobile
,
])
return
(
<
Chat
config=
{
appConfig
}
chatList=
{
chatList
}
isResponsing=
{
isResponsing
}
chatContainerInnerClassName=
{
`mx-auto pt-6 w-full max-w-[720px] ${isMobile && 'px-4'}`
}
chatFooterClassName=
'pb-4'
chatFooterInnerClassName=
{
`mx-auto w-full max-w-[720px] ${isMobile && 'px-4'}`
}
onSend=
{
doSend
}
onStopResponding=
{
handleStop
}
chatNode=
{
chatNode
}
allToolIcons=
{
appMeta
?.
tool_icons
||
{}
}
onFeedback=
{
handleFeedback
}
suggestedQuestions=
{
suggestedQuestions
}
/>
)
}
export
default
ChatWrapper
web/app/components/base/chat/chat-with-history/config-panel/form.tsx
0 → 100644
View file @
51d35926
import
{
useTranslation
}
from
'react-i18next'
import
{
useChatWithHistoryContext
}
from
'../context'
import
{
PortalSelect
}
from
'@/app/components/base/select'
const
Form
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
inputsForms
,
newConversationInputs
,
handleNewConversationInputsChange
,
isMobile
,
}
=
useChatWithHistoryContext
()
const
handleFormChange
=
(
variable
:
string
,
value
:
string
)
=>
{
handleNewConversationInputsChange
({
...
newConversationInputs
,
[
variable
]:
value
,
})
}
const
renderField
=
(
form
:
any
)
=>
{
const
{
label
,
required
,
max_length
,
variable
,
options
,
}
=
form
if
(
form
.
type
===
'text-input'
)
{
return
(
<
input
className=
'grow h-9 rounded-lg bg-gray-100 px-2.5 outline-none appearance-none'
value=
{
newConversationInputs
[
variable
]
||
''
}
maxLength=
{
max_length
}
onChange=
{
e
=>
handleFormChange
(
variable
,
e
.
target
.
value
)
}
placeholder=
{
`${label}${!required ? `
(
$
{
t
(
'appDebug.variableTable.optional'
)})
` : ''}`
}
/>
)
}
if
(
form
.
type
===
'paragraph'
)
{
return
(
<
textarea
value=
{
newConversationInputs
[
variable
]
}
className=
'grow h-[104px] rounded-lg bg-gray-100 px-2.5 py-2 outline-none appearance-none resize-none'
onChange=
{
e
=>
handleFormChange
(
variable
,
e
.
target
.
value
)
}
placeholder=
{
`${label}${!required ? `
(
$
{
t
(
'appDebug.variableTable.optional'
)})
` : ''}`
}
/>
)
}
return
(
<
PortalSelect
popupClassName=
'w-[200px]'
value=
{
newConversationInputs
[
variable
]
}
items=
{
options
.
map
((
option
:
string
)
=>
({
value
:
option
,
name
:
option
}))
}
onSelect=
{
item
=>
handleFormChange
(
variable
,
item
.
value
as
string
)
}
placeholder=
{
`${label}${!required ? `
(
$
{
t
(
'appDebug.variableTable.optional'
)})
` : ''}`
}
/>
)
}
if
(
!
inputsForms
.
length
)
return
null
return
(
<
div
className=
'mb-4 py-2'
>
{
inputsForms
.
map
(
form
=>
(
<
div
key=
{
form
.
variable
}
className=
{
`flex mb-3 last-of-type:mb-0 text-sm text-gray-900 ${isMobile && '!flex-wrap'}`
}
>
<
div
className=
{
`shrink-0 mr-2 py-2 w-[128px] ${isMobile && '!w-full'}`
}
>
{
form
.
label
}
</
div
>
{
renderField
(
form
)
}
</
div
>
))
}
</
div
>
)
}
export
default
Form
web/app/components/base/chat/chat-with-history/config-panel/index.tsx
0 → 100644
View file @
51d35926
import
{
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useChatWithHistoryContext
}
from
'../context'
import
Form
from
'./form'
import
Button
from
'@/app/components/base/button'
import
{
MessageDotsCircle
}
from
'@/app/components/base/icons/src/vender/solid/communication'
import
{
Edit02
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
Star06
}
from
'@/app/components/base/icons/src/vender/solid/shapes'
import
{
FootLogo
}
from
'@/app/components/share/chat/welcome/massive-component'
const
ConfigPanel
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
appData
,
inputsForms
,
handleStartChat
,
showConfigPanelBeforeChat
,
isMobile
,
}
=
useChatWithHistoryContext
()
const
[
collapsed
,
setCollapsed
]
=
useState
(
true
)
const
customConfig
=
appData
?.
custom_config
const
site
=
appData
?.
site
return
(
<
div
className=
'flex flex-col max-h-[80%] w-full max-w-[720px]'
>
<
div
className=
{
`
grow rounded-xl overflow-y-auto
${showConfigPanelBeforeChat && 'border-[0.5px] border-gray-100 shadow-lg'}
${!showConfigPanelBeforeChat && collapsed && 'border border-indigo-100'}
${!showConfigPanelBeforeChat && !collapsed && 'border-[0.5px] border-gray-100 shadow-lg'}
`
}
>
<
div
className=
{
`
flex flex-wrap px-6 py-4 rounded-t-xl bg-indigo-25
${isMobile && '!px-4 !py-3'}
`
}
>
{
showConfigPanelBeforeChat
&&
(
<>
<
div
className=
'flex items-center text-2xl font-semibold text-gray-800'
>
{
appData
?.
site
.
icon
}
{
appData
?.
site
.
title
}
</
div
>
{
appData
?.
site
.
description
&&
(
<
div
className=
'mt-2 w-full text-sm text-gray-500'
>
{
appData
?.
site
.
description
}
</
div
>
)
}
</>
)
}
{
!
showConfigPanelBeforeChat
&&
collapsed
&&
(
<>
<
Star06
className=
'mr-1 mt-1 w-4 h-4 text-indigo-600'
/>
<
div
className=
'grow py-[3px] text-[13px] text-indigo-600 leading-[18px] font-medium'
>
{
t
(
'share.chat.configStatusDes'
)
}
</
div
>
<
Button
className=
'shrink-0 px-2 py-0 h-6 bg-white text-xs font-medium text-primary-600 rounded-md'
onClick=
{
()
=>
setCollapsed
(
false
)
}
>
<
Edit02
className=
'mr-1 w-3 h-3'
/>
{
t
(
'common.operation.edit'
)
}
</
Button
>
</>
)
}
{
!
showConfigPanelBeforeChat
&&
!
collapsed
&&
(
<>
<
Star06
className=
'mr-1 mt-1 w-4 h-4 text-indigo-600'
/>
<
div
className=
'grow py-[3px] text-[13px] text-indigo-600 leading-[18px] font-medium'
>
{
t
(
'share.chat.privatePromptConfigTitle'
)
}
</
div
>
</>
)
}
</
div
>
{
!
collapsed
&&
!
showConfigPanelBeforeChat
&&
(
<
div
className=
'p-6 rounded-b-xl'
>
<
Form
/>
<
div
className=
{
`pl-[136px] flex items-center ${isMobile && '!pl-0'}`
}
>
<
Button
type=
'primary'
className=
'mr-2 text-sm font-medium'
onClick=
{
handleStartChat
}
>
{
t
(
'common.operation.save'
)
}
</
Button
>
<
Button
className=
'text-sm font-medium'
onClick=
{
()
=>
setCollapsed
(
true
)
}
>
{
t
(
'common.operation.cancel'
)
}
</
Button
>
</
div
>
</
div
>
)
}
{
showConfigPanelBeforeChat
&&
(
<
div
className=
'p-6 rounded-b-xl'
>
<
Form
/>
<
Button
className=
{
`px-4 py-0 h-9 ${inputsForms.length && !isMobile && 'ml-[136px]'}`
}
type=
'primary'
onClick=
{
handleStartChat
}
>
<
MessageDotsCircle
className=
'mr-2 w-4 h-4 text-white'
/>
{
t
(
'share.chat.startChat'
)
}
</
Button
>
</
div
>
)
}
</
div
>
{
showConfigPanelBeforeChat
&&
(
site
||
customConfig
)
&&
(
<
div
className=
'mt-4 flex flex-wrap justify-between items-center py-2 text-xs text-gray-400'
>
{
site
?.
privacy_policy
?
<
div
className=
{
`flex items-center ${isMobile && 'w-full justify-end'}`
}
>
{
t
(
'share.chat.privacyPolicyLeft'
)
}
<
a
className=
'text-gray-500'
href=
{
site
?.
privacy_policy
}
target=
'_blank'
rel=
'noopener noreferrer'
>
{
t
(
'share.chat.privacyPolicyMiddle'
)
}
</
a
>
{
t
(
'share.chat.privacyPolicyRight'
)
}
</
div
>
:
<
div
>
</
div
>
}
{
customConfig
?.
remove_webapp_brand
?
null
:
(
<
div
className=
{
`flex items-center justify-end ${isMobile && 'w-full'}`
}
>
<
a
className=
'flex items-center pr-3 space-x-3'
href=
"https://dify.ai/"
target=
"_blank"
>
<
span
className=
'uppercase'
>
{
t
(
'share.chat.powerBy'
)
}
</
span
>
{
customConfig
?.
replace_webapp_logo
?
<
img
src=
{
customConfig
?.
replace_webapp_logo
}
alt=
'logo'
className=
'block w-auto h-5'
/>
:
<
FootLogo
/>
}
</
a
>
</
div
>
)
}
</
div
>
)
}
</
div
>
)
}
export
default
ConfigPanel
web/app/components/base/chat/chat-with-history/context.tsx
0 → 100644
View file @
51d35926
'use client'
import
type
{
RefObject
}
from
'react'
import
{
createContext
,
useContext
}
from
'use-context-selector'
import
type
{
Callback
,
ChatConfig
,
ChatItem
,
Feedback
,
}
from
'../types'
import
type
{
AppConversationData
,
AppData
,
AppMeta
,
ConversationItem
,
}
from
'@/models/share'
export
type
ChatWithHistoryContextValue
=
{
appInfoLoading
?:
boolean
appMeta
?:
AppMeta
appData
?:
AppData
appParams
?:
ChatConfig
appChatListDataLoading
?:
boolean
currentConversationId
:
string
currentConversationItem
?:
ConversationItem
appPrevChatList
:
ChatItem
[]
pinnedConversationList
:
AppConversationData
[
'data'
]
conversationList
:
AppConversationData
[
'data'
]
showConfigPanelBeforeChat
:
boolean
newConversationInputs
:
Record
<
string
,
any
>
handleNewConversationInputsChange
:
(
v
:
Record
<
string
,
any
>
)
=>
void
inputsForms
:
any
[]
handleNewConversation
:
()
=>
void
handleStartChat
:
()
=>
void
handleChangeConversation
:
(
conversationId
:
string
)
=>
void
handlePinConversation
:
(
conversationId
:
string
)
=>
void
handleUnpinConversation
:
(
conversationId
:
string
)
=>
void
handleDeleteConversation
:
(
conversationId
:
string
,
callback
:
Callback
)
=>
void
conversationRenaming
:
boolean
handleRenameConversation
:
(
conversationId
:
string
,
newName
:
string
,
callback
:
Callback
)
=>
void
handleNewConversationCompleted
:
(
newConversationId
:
string
)
=>
void
chatShouldReloadKey
:
string
isMobile
:
boolean
isInstalledApp
:
boolean
appId
?:
string
handleFeedback
:
(
messageId
:
string
,
feedback
:
Feedback
)
=>
void
currentChatInstanceRef
:
RefObject
<
{
handleStop
:
()
=>
void
}
>
}
export
const
ChatWithHistoryContext
=
createContext
<
ChatWithHistoryContextValue
>
({
currentConversationId
:
''
,
appPrevChatList
:
[],
pinnedConversationList
:
[],
conversationList
:
[],
showConfigPanelBeforeChat
:
false
,
newConversationInputs
:
{},
handleNewConversationInputsChange
:
()
=>
{},
inputsForms
:
[],
handleNewConversation
:
()
=>
{},
handleStartChat
:
()
=>
{},
handleChangeConversation
:
()
=>
{},
handlePinConversation
:
()
=>
{},
handleUnpinConversation
:
()
=>
{},
handleDeleteConversation
:
()
=>
{},
conversationRenaming
:
false
,
handleRenameConversation
:
()
=>
{},
handleNewConversationCompleted
:
()
=>
{},
chatShouldReloadKey
:
''
,
isMobile
:
false
,
isInstalledApp
:
false
,
handleFeedback
:
()
=>
{},
currentChatInstanceRef
:
{
current
:
{
handleStop
:
()
=>
{}
}
},
})
export
const
useChatWithHistoryContext
=
()
=>
useContext
(
ChatWithHistoryContext
)
web/app/components/base/chat/chat-with-history/header-in-mobile.tsx
0 → 100644
View file @
51d35926
import
{
useState
}
from
'react'
import
{
useChatWithHistoryContext
}
from
'./context'
import
Sidebar
from
'./sidebar'
import
AppIcon
from
'@/app/components/base/app-icon'
import
{
Edit05
,
Menu01
,
}
from
'@/app/components/base/icons/src/vender/line/general'
const
HeaderInMobile
=
()
=>
{
const
{
appData
,
handleNewConversation
,
}
=
useChatWithHistoryContext
()
const
[
showSidebar
,
setShowSidebar
]
=
useState
(
false
)
return
(
<>
<
div
className=
'shrink-0 flex items-center px-3 h-[44px] border-b-[0.5px] border-b-gray-200'
>
<
div
className=
'shrink-0 flex items-center justify-center w-8 h-8 rounded-lg'
onClick=
{
()
=>
setShowSidebar
(
true
)
}
>
<
Menu01
className=
'w-4 h-4 text-gray-700'
/>
</
div
>
<
div
className=
'grow flex justify-center items-center px-3'
>
<
AppIcon
className=
'mr-2'
size=
'tiny'
icon=
{
appData
?.
site
.
icon
}
background=
{
appData
?.
site
.
icon_background
}
/>
<
div
className=
'py-1 text-base font-semibold text-gray-800 truncate'
>
{
appData
?.
site
.
title
}
</
div
>
</
div
>
<
div
className=
'shrink-0 flex items-center justify-center w-8 h-8 rounded-lg'
onClick=
{
handleNewConversation
}
>
<
Edit05
className=
'w-4 h-4 text-gray-700'
/>
</
div
>
</
div
>
{
showSidebar
&&
(
<
div
className=
'fixed inset-0 z-50'
style=
{
{
backgroundColor
:
'rgba(35, 56, 118, 0.2)'
}
}
onClick=
{
()
=>
setShowSidebar
(
false
)
}
>
<
div
className=
'inline-block h-full bg-white'
onClick=
{
e
=>
e
.
stopPropagation
()
}
>
<
Sidebar
/>
</
div
>
</
div
>
)
}
</>
)
}
export
default
HeaderInMobile
web/app/components/base/chat/chat-with-history/header.tsx
0 → 100644
View file @
51d35926
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
type
HeaderProps
=
{
title
:
string
isMobile
:
boolean
}
const
Header
:
FC
<
HeaderProps
>
=
({
title
,
isMobile
,
})
=>
{
return
(
<
div
className=
{
`
sticky top-0 flex items-center px-8 h-16 bg-white/80 text-base font-medium
text-gray-900 border-b-[0.5px] border-b-gray-100 backdrop-blur-md z-10
${isMobile && '!h-12'}
`
}
>
{
title
}
</
div
>
)
}
export
default
memo
(
Header
)
web/app/components/base/chat/chat-with-history/hooks.tsx
0 → 100644
View file @
51d35926
import
{
useCallback
,
useEffect
,
useMemo
,
useRef
,
useState
,
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
useSWR
from
'swr'
import
{
useLocalStorageState
}
from
'ahooks'
import
produce
from
'immer'
import
type
{
Callback
,
ChatConfig
,
ChatItem
,
Feedback
,
}
from
'../types'
import
{
CONVERSATION_ID_INFO
}
from
'../constants'
import
{
delConversation
,
fetchAppInfo
,
fetchAppMeta
,
fetchAppParams
,
fetchChatList
,
fetchConversations
,
generationConversationName
,
pinConversation
,
renameConversation
,
unpinConversation
,
updateFeedback
,
}
from
'@/service/share'
import
type
{
InstalledApp
}
from
'@/models/explore'
import
type
{
AppData
,
ConversationItem
,
}
from
'@/models/share'
import
{
addFileInfos
,
sortAgentSorts
}
from
'@/app/components/tools/utils'
import
{
useToastContext
}
from
'@/app/components/base/toast'
export
const
useChatWithHistory
=
(
installedAppInfo
?:
InstalledApp
)
=>
{
const
isInstalledApp
=
useMemo
(()
=>
!!
installedAppInfo
,
[
installedAppInfo
])
const
{
data
:
appInfo
,
isLoading
:
appInfoLoading
}
=
useSWR
(
installedAppInfo
?
null
:
'appInfo'
,
fetchAppInfo
)
const
appData
=
useMemo
(()
=>
{
if
(
isInstalledApp
)
{
const
{
id
,
app
}
=
installedAppInfo
!
return
{
app_id
:
id
,
site
:
{
title
:
app
.
name
,
icon
:
app
.
icon
,
icon_background
:
app
.
icon_background
,
prompt_public
:
false
,
copyright
:
''
},
plan
:
'basic'
,
}
as
AppData
}
return
appInfo
},
[
isInstalledApp
,
installedAppInfo
,
appInfo
])
const
appId
=
useMemo
(()
=>
appData
?.
app_id
,
[
appData
])
const
[
conversationIdInfo
,
setConversationIdInfo
]
=
useLocalStorageState
<
Record
<
string
,
string
>>
(
CONVERSATION_ID_INFO
,
{
defaultValue
:
{},
})
const
currentConversationId
=
useMemo
(()
=>
conversationIdInfo
?.[
appId
||
''
]
||
''
,
[
appId
,
conversationIdInfo
])
const
handleConversationIdInfoChange
=
useCallback
((
changeConversationId
:
string
)
=>
{
if
(
appId
)
{
setConversationIdInfo
({
...
conversationIdInfo
,
[
appId
||
''
]:
changeConversationId
,
})
}
},
[
appId
,
conversationIdInfo
,
setConversationIdInfo
])
const
[
showConfigPanelBeforeChat
,
setShowConfigPanelBeforeChat
]
=
useState
(
true
)
const
[
newConversationId
,
setNewConversationId
]
=
useState
(
''
)
const
chatShouldReloadKey
=
useMemo
(()
=>
{
if
(
currentConversationId
===
newConversationId
)
return
''
return
currentConversationId
},
[
currentConversationId
,
newConversationId
])
const
{
data
:
appParams
}
=
useSWR
([
'appParams'
,
isInstalledApp
,
appId
],
()
=>
fetchAppParams
(
isInstalledApp
,
appId
))
const
{
data
:
appMeta
}
=
useSWR
([
'appMeta'
,
isInstalledApp
,
appId
],
()
=>
fetchAppMeta
(
isInstalledApp
,
appId
))
const
{
data
:
appPinnedConversationData
,
mutate
:
mutateAppPinnedConversationData
}
=
useSWR
([
'appConversationData'
,
isInstalledApp
,
appId
,
true
],
()
=>
fetchConversations
(
isInstalledApp
,
appId
,
undefined
,
true
,
100
))
const
{
data
:
appConversationData
,
isLoading
:
appConversationDataLoading
,
mutate
:
mutateAppConversationData
}
=
useSWR
([
'appConversationData'
,
isInstalledApp
,
appId
,
false
],
()
=>
fetchConversations
(
isInstalledApp
,
appId
,
undefined
,
false
,
100
))
const
{
data
:
appChatListData
,
isLoading
:
appChatListDataLoading
}
=
useSWR
(
chatShouldReloadKey
?
[
'appChatList'
,
chatShouldReloadKey
,
isInstalledApp
,
appId
]
:
null
,
()
=>
fetchChatList
(
chatShouldReloadKey
,
isInstalledApp
,
appId
))
const
appPrevChatList
=
useMemo
(()
=>
{
const
data
=
appChatListData
?.
data
||
[]
const
chatList
:
ChatItem
[]
=
[]
if
(
currentConversationId
&&
data
.
length
)
{
data
.
forEach
((
item
:
any
)
=>
{
chatList
.
push
({
id
:
`question-
${
item
.
id
}
`
,
content
:
item
.
query
,
isAnswer
:
false
,
message_files
:
item
.
message_files
?.
filter
((
file
:
any
)
=>
file
.
belongs_to
===
'user'
)
||
[],
})
chatList
.
push
({
id
:
item
.
id
,
content
:
item
.
answer
,
agent_thoughts
:
addFileInfos
(
item
.
agent_thoughts
?
sortAgentSorts
(
item
.
agent_thoughts
)
:
item
.
agent_thoughts
,
item
.
message_files
),
feedback
:
item
.
feedback
,
isAnswer
:
true
,
citation
:
item
.
retriever_resources
,
message_files
:
item
.
message_files
?.
filter
((
file
:
any
)
=>
file
.
belongs_to
===
'assistant'
)
||
[],
})
})
}
return
chatList
},
[
appChatListData
,
currentConversationId
])
const
[
showNewConversationItemInList
,
setShowNewConversationItemInList
]
=
useState
(
false
)
const
pinnedConversationList
=
useMemo
(()
=>
{
return
appPinnedConversationData
?.
data
||
[]
},
[
appPinnedConversationData
])
const
{
t
}
=
useTranslation
()
const
newConversationInputsRef
=
useRef
<
Record
<
string
,
any
>>
({})
const
[
newConversationInputs
,
setNewConversationInputs
]
=
useState
<
Record
<
string
,
any
>>
({})
const
handleNewConversationInputsChange
=
useCallback
((
newInputs
:
Record
<
string
,
any
>
)
=>
{
newConversationInputsRef
.
current
=
newInputs
setNewConversationInputs
(
newInputs
)
},
[])
const
inputsForms
=
useMemo
(()
=>
{
return
(
appParams
?.
user_input_form
||
[]).
filter
((
item
:
any
)
=>
item
.
paragraph
||
item
.
select
||
item
[
'text-input'
]).
map
((
item
:
any
)
=>
{
if
(
item
.
paragraph
)
{
return
{
...
item
.
paragraph
,
type
:
'paragraph'
,
}
}
if
(
item
.
select
)
{
return
{
...
item
.
select
,
type
:
'select'
,
}
}
return
{
...
item
[
'text-input'
],
type
:
'text-input'
,
}
})
},
[
appParams
])
useEffect
(()
=>
{
const
conversationInputs
:
Record
<
string
,
any
>
=
{}
inputsForms
.
forEach
((
item
:
any
)
=>
{
conversationInputs
[
item
.
variable
]
=
item
.
default
||
''
})
handleNewConversationInputsChange
(
conversationInputs
)
},
[
handleNewConversationInputsChange
,
inputsForms
])
const
{
data
:
newConversation
}
=
useSWR
(
newConversationId
?
[
isInstalledApp
,
appId
,
newConversationId
]
:
null
,
()
=>
generationConversationName
(
isInstalledApp
,
appId
,
newConversationId
))
const
[
originConversationList
,
setOriginConversationList
]
=
useState
<
ConversationItem
[]
>
([])
useEffect
(()
=>
{
if
(
appConversationData
?.
data
&&
!
appConversationDataLoading
)
setOriginConversationList
(
appConversationData
?.
data
)
},
[
appConversationData
,
appConversationDataLoading
])
const
conversationList
=
useMemo
(()
=>
{
const
data
=
originConversationList
.
slice
()
if
(
showNewConversationItemInList
&&
data
[
0
]?.
id
!==
''
)
{
data
.
unshift
({
id
:
''
,
name
:
t
(
'share.chat.newChatDefaultName'
),
inputs
:
{},
introduction
:
''
,
})
}
return
data
},
[
originConversationList
,
showNewConversationItemInList
,
t
])
useEffect
(()
=>
{
if
(
newConversation
)
{
setOriginConversationList
(
produce
((
draft
)
=>
{
const
index
=
draft
.
findIndex
(
item
=>
item
.
id
===
newConversation
.
id
)
if
(
index
>
-
1
)
draft
[
index
]
=
newConversation
else
draft
.
unshift
(
newConversation
)
}))
}
},
[
newConversation
])
const
currentConversationItem
=
useMemo
(()
=>
{
let
coversationItem
=
conversationList
.
find
(
item
=>
item
.
id
===
currentConversationId
)
if
(
!
coversationItem
&&
pinnedConversationList
.
length
)
coversationItem
=
pinnedConversationList
.
find
(
item
=>
item
.
id
===
currentConversationId
)
return
coversationItem
},
[
conversationList
,
currentConversationId
,
pinnedConversationList
])
const
{
notify
}
=
useToastContext
()
const
checkInputsRequired
=
useCallback
((
silent
?:
boolean
)
=>
{
if
(
inputsForms
.
length
)
{
for
(
let
i
=
0
;
i
<
inputsForms
.
length
;
i
+=
1
)
{
const
item
=
inputsForms
[
i
]
if
(
item
.
required
&&
!
newConversationInputsRef
.
current
[
item
.
variable
])
{
if
(
!
silent
)
{
notify
({
type
:
'error'
,
message
:
t
(
'appDebug.errorMessage.valueOfVarRequired'
,
{
key
:
item
.
variable
}),
})
}
return
}
}
return
true
}
return
true
},
[
inputsForms
,
notify
,
t
])
const
handleStartChat
=
useCallback
(()
=>
{
if
(
checkInputsRequired
())
{
setShowConfigPanelBeforeChat
(
false
)
setShowNewConversationItemInList
(
true
)
}
},
[
setShowConfigPanelBeforeChat
,
setShowNewConversationItemInList
,
checkInputsRequired
])
const
currentChatInstanceRef
=
useRef
<
{
handleStop
:
()
=>
void
}
>
({
handleStop
:
()
=>
{}
})
const
handleChangeConversation
=
useCallback
((
conversationId
:
string
)
=>
{
currentChatInstanceRef
.
current
.
handleStop
()
setNewConversationId
(
''
)
handleConversationIdInfoChange
(
conversationId
)
if
(
conversationId
===
''
&&
!
checkInputsRequired
(
true
))
setShowConfigPanelBeforeChat
(
true
)
else
setShowConfigPanelBeforeChat
(
false
)
},
[
handleConversationIdInfoChange
,
setShowConfigPanelBeforeChat
,
checkInputsRequired
])
const
handleNewConversation
=
useCallback
(()
=>
{
currentChatInstanceRef
.
current
.
handleStop
()
setNewConversationId
(
''
)
if
(
showNewConversationItemInList
)
{
handleChangeConversation
(
''
)
}
else
if
(
currentConversationId
)
{
handleConversationIdInfoChange
(
''
)
setShowConfigPanelBeforeChat
(
true
)
setShowNewConversationItemInList
(
true
)
handleNewConversationInputsChange
({})
}
},
[
handleChangeConversation
,
currentConversationId
,
handleConversationIdInfoChange
,
setShowConfigPanelBeforeChat
,
setShowNewConversationItemInList
,
showNewConversationItemInList
,
handleNewConversationInputsChange
])
const
handleUpdateConversationList
=
useCallback
(()
=>
{
mutateAppConversationData
()
mutateAppPinnedConversationData
()
},
[
mutateAppConversationData
,
mutateAppPinnedConversationData
])
const
handlePinConversation
=
useCallback
(
async
(
conversationId
:
string
)
=>
{
await
pinConversation
(
isInstalledApp
,
appId
,
conversationId
)
notify
({
type
:
'success'
,
message
:
t
(
'common.api.success'
)
})
handleUpdateConversationList
()
},
[
isInstalledApp
,
appId
,
notify
,
t
,
handleUpdateConversationList
])
const
handleUnpinConversation
=
useCallback
(
async
(
conversationId
:
string
)
=>
{
await
unpinConversation
(
isInstalledApp
,
appId
,
conversationId
)
notify
({
type
:
'success'
,
message
:
t
(
'common.api.success'
)
})
handleUpdateConversationList
()
},
[
isInstalledApp
,
appId
,
notify
,
t
,
handleUpdateConversationList
])
const
[
conversationDeleting
,
setConversationDeleting
]
=
useState
(
false
)
const
handleDeleteConversation
=
useCallback
(
async
(
conversationId
:
string
,
{
onSuccess
,
}:
Callback
,
)
=>
{
if
(
conversationDeleting
)
return
try
{
setConversationDeleting
(
true
)
await
delConversation
(
isInstalledApp
,
appId
,
conversationId
)
notify
({
type
:
'success'
,
message
:
t
(
'common.api.success'
)
})
onSuccess
()
}
finally
{
setConversationDeleting
(
false
)
}
if
(
conversationId
===
currentConversationId
)
handleNewConversation
()
handleUpdateConversationList
()
},
[
isInstalledApp
,
appId
,
notify
,
t
,
handleUpdateConversationList
,
handleNewConversation
,
currentConversationId
,
conversationDeleting
])
const
[
conversationRenaming
,
setConversationRenaming
]
=
useState
(
false
)
const
handleRenameConversation
=
useCallback
(
async
(
conversationId
:
string
,
newName
:
string
,
{
onSuccess
,
}:
Callback
,
)
=>
{
if
(
conversationRenaming
)
return
if
(
!
newName
.
trim
())
{
notify
({
type
:
'error'
,
message
:
t
(
'common.chat.conversationNameCanNotEmpty'
),
})
return
}
setConversationRenaming
(
true
)
try
{
await
renameConversation
(
isInstalledApp
,
appId
,
conversationId
,
newName
)
notify
({
type
:
'success'
,
message
:
t
(
'common.actionMsg.modifiedSuccessfully'
),
})
setOriginConversationList
(
produce
((
draft
)
=>
{
const
index
=
originConversationList
.
findIndex
(
item
=>
item
.
id
===
conversationId
)
const
item
=
draft
[
index
]
draft
[
index
]
=
{
...
item
,
name
:
newName
,
}
}))
onSuccess
()
}
finally
{
setConversationRenaming
(
false
)
}
},
[
isInstalledApp
,
appId
,
notify
,
t
,
conversationRenaming
,
originConversationList
])
const
handleNewConversationCompleted
=
useCallback
((
newConversationId
:
string
)
=>
{
setNewConversationId
(
newConversationId
)
handleConversationIdInfoChange
(
newConversationId
)
setShowNewConversationItemInList
(
false
)
mutateAppConversationData
()
},
[
mutateAppConversationData
,
handleConversationIdInfoChange
])
const
handleFeedback
=
useCallback
(
async
(
messageId
:
string
,
feedback
:
Feedback
)
=>
{
await
updateFeedback
({
url
:
`/messages/
${
messageId
}
/feedbacks`
,
body
:
{
rating
:
feedback
.
rating
}
},
isInstalledApp
,
appId
)
notify
({
type
:
'success'
,
message
:
t
(
'common.api.success'
)
})
},
[
isInstalledApp
,
appId
,
t
,
notify
])
return
{
appInfoLoading
,
isInstalledApp
,
appId
,
currentConversationId
,
currentConversationItem
,
handleConversationIdInfoChange
,
appData
,
appParams
:
appParams
||
{}
as
ChatConfig
,
appMeta
,
appPinnedConversationData
,
appConversationData
,
appConversationDataLoading
,
appChatListData
,
appChatListDataLoading
,
appPrevChatList
,
pinnedConversationList
,
conversationList
,
showConfigPanelBeforeChat
,
setShowConfigPanelBeforeChat
,
setShowNewConversationItemInList
,
newConversationInputs
,
handleNewConversationInputsChange
,
inputsForms
,
handleNewConversation
,
handleStartChat
,
handleChangeConversation
,
handlePinConversation
,
handleUnpinConversation
,
conversationDeleting
,
handleDeleteConversation
,
conversationRenaming
,
handleRenameConversation
,
handleNewConversationCompleted
,
newConversationId
,
chatShouldReloadKey
,
handleFeedback
,
currentChatInstanceRef
,
}
}
web/app/components/base/chat/chat-with-history/index.tsx
0 → 100644
View file @
51d35926
import
type
{
FC
}
from
'react'
import
{
useEffect
,
useState
,
}
from
'react'
import
{
useAsyncEffect
}
from
'ahooks'
import
{
ChatWithHistoryContext
,
useChatWithHistoryContext
,
}
from
'./context'
import
{
useChatWithHistory
}
from
'./hooks'
import
Sidebar
from
'./sidebar'
import
HeaderInMobile
from
'./header-in-mobile'
import
ConfigPanel
from
'./config-panel'
import
ChatWrapper
from
'./chat-wrapper'
import
type
{
InstalledApp
}
from
'@/models/explore'
import
Loading
from
'@/app/components/base/loading'
import
useBreakpoints
,
{
MediaType
}
from
'@/hooks/use-breakpoints'
import
{
checkOrSetAccessToken
}
from
'@/app/components/share/utils'
type
ChatWithHistoryProps
=
{
className
?:
string
}
const
ChatWithHistory
:
FC
<
ChatWithHistoryProps
>
=
({
className
,
})
=>
{
const
{
appData
,
appInfoLoading
,
appPrevChatList
,
showConfigPanelBeforeChat
,
appChatListDataLoading
,
chatShouldReloadKey
,
isMobile
,
}
=
useChatWithHistoryContext
()
const
chatReady
=
(
!
showConfigPanelBeforeChat
||
!!
appPrevChatList
.
length
)
const
customConfig
=
appData
?.
custom_config
const
site
=
appData
?.
site
useEffect
(()
=>
{
if
(
site
)
{
if
(
customConfig
)
document
.
title
=
`
${
site
.
title
}
`
else
document
.
title
=
`
${
site
.
title
}
- Powered by Dify`
}
},
[
site
,
customConfig
])
if
(
appInfoLoading
)
{
return
(
<
Loading
type=
'app'
/>
)
}
return
(
<
div
className=
{
`h-full flex bg-white ${className} ${isMobile && 'flex-col'}`
}
>
{
!
isMobile
&&
(
<
Sidebar
/>
)
}
{
isMobile
&&
(
<
HeaderInMobile
/>
)
}
<
div
className=
{
`grow overflow-hidden ${showConfigPanelBeforeChat && !appPrevChatList.length && 'flex items-center justify-center'}`
}
>
{
showConfigPanelBeforeChat
&&
!
appChatListDataLoading
&&
!
appPrevChatList
.
length
&&
(
<
div
className=
{
`flex w-full items-center justify-center h-full ${isMobile && 'px-4'}`
}
>
<
ConfigPanel
/>
</
div
>
)
}
{
appChatListDataLoading
&&
chatReady
&&
(
<
Loading
type=
'app'
/>
)
}
{
chatReady
&&
!
appChatListDataLoading
&&
(
<
ChatWrapper
key=
{
chatShouldReloadKey
}
/>
)
}
</
div
>
</
div
>
)
}
export
type
ChatWithHistoryWrapProps
=
{
installedAppInfo
?:
InstalledApp
className
?:
string
}
const
ChatWithHistoryWrap
:
FC
<
ChatWithHistoryWrapProps
>
=
({
installedAppInfo
,
className
,
})
=>
{
const
media
=
useBreakpoints
()
const
isMobile
=
media
===
MediaType
.
mobile
const
{
appInfoLoading
,
appData
,
appParams
,
appMeta
,
appChatListDataLoading
,
currentConversationId
,
currentConversationItem
,
appPrevChatList
,
pinnedConversationList
,
conversationList
,
showConfigPanelBeforeChat
,
newConversationInputs
,
handleNewConversationInputsChange
,
inputsForms
,
handleNewConversation
,
handleStartChat
,
handleChangeConversation
,
handlePinConversation
,
handleUnpinConversation
,
handleDeleteConversation
,
conversationRenaming
,
handleRenameConversation
,
handleNewConversationCompleted
,
chatShouldReloadKey
,
isInstalledApp
,
appId
,
handleFeedback
,
currentChatInstanceRef
,
}
=
useChatWithHistory
(
installedAppInfo
)
return
(
<
ChatWithHistoryContext
.
Provider
value=
{
{
appInfoLoading
,
appData
,
appParams
,
appMeta
,
appChatListDataLoading
,
currentConversationId
,
currentConversationItem
,
appPrevChatList
,
pinnedConversationList
,
conversationList
,
showConfigPanelBeforeChat
,
newConversationInputs
,
handleNewConversationInputsChange
,
inputsForms
,
handleNewConversation
,
handleStartChat
,
handleChangeConversation
,
handlePinConversation
,
handleUnpinConversation
,
handleDeleteConversation
,
conversationRenaming
,
handleRenameConversation
,
handleNewConversationCompleted
,
chatShouldReloadKey
,
isMobile
,
isInstalledApp
,
appId
,
handleFeedback
,
currentChatInstanceRef
,
}
}
>
<
ChatWithHistory
className=
{
className
}
/>
</
ChatWithHistoryContext
.
Provider
>
)
}
const
ChatWithHistoryWrapWithCheckToken
:
FC
<
ChatWithHistoryWrapProps
>
=
({
installedAppInfo
,
className
,
})
=>
{
const
[
inited
,
setInited
]
=
useState
(
false
)
useAsyncEffect
(
async
()
=>
{
if
(
!
inited
)
{
if
(
!
installedAppInfo
)
await
checkOrSetAccessToken
()
setInited
(
true
)
}
},
[])
if
(
!
inited
)
return
null
return
(
<
ChatWithHistoryWrap
installedAppInfo=
{
installedAppInfo
}
className=
{
className
}
/>
)
}
export
default
ChatWithHistoryWrapWithCheckToken
web/app/components/base/chat/chat-with-history/sidebar/index.tsx
0 → 100644
View file @
51d35926
import
{
useCallback
,
useState
,
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useChatWithHistoryContext
}
from
'../context'
import
List
from
'./list'
import
AppIcon
from
'@/app/components/base/app-icon'
import
Button
from
'@/app/components/base/button'
import
{
Edit05
}
from
'@/app/components/base/icons/src/vender/line/general'
import
type
{
ConversationItem
}
from
'@/models/share'
import
Confirm
from
'@/app/components/base/confirm'
import
RenameModal
from
'@/app/components/share/chat/sidebar/rename-modal'
const
Sidebar
=
()
=>
{
const
{
t
}
=
useTranslation
()
const
{
appData
,
pinnedConversationList
,
conversationList
,
handleNewConversation
,
currentConversationId
,
handleChangeConversation
,
handlePinConversation
,
handleUnpinConversation
,
conversationRenaming
,
handleRenameConversation
,
handleDeleteConversation
,
isMobile
,
}
=
useChatWithHistoryContext
()
const
[
showConfirm
,
setShowConfirm
]
=
useState
<
ConversationItem
|
null
>
(
null
)
const
[
showRename
,
setShowRename
]
=
useState
<
ConversationItem
|
null
>
(
null
)
const
handleOperate
=
useCallback
((
type
:
string
,
item
:
ConversationItem
)
=>
{
if
(
type
===
'pin'
)
handlePinConversation
(
item
.
id
)
if
(
type
===
'unpin'
)
handleUnpinConversation
(
item
.
id
)
if
(
type
===
'delete'
)
setShowConfirm
(
item
)
if
(
type
===
'rename'
)
setShowRename
(
item
)
},
[
handlePinConversation
,
handleUnpinConversation
])
const
handleCancelConfirm
=
useCallback
(()
=>
{
setShowConfirm
(
null
)
},
[])
const
handleDelete
=
useCallback
(()
=>
{
if
(
showConfirm
)
handleDeleteConversation
(
showConfirm
.
id
,
{
onSuccess
:
handleCancelConfirm
})
},
[
showConfirm
,
handleDeleteConversation
,
handleCancelConfirm
])
const
handleCancelRename
=
useCallback
(()
=>
{
setShowRename
(
null
)
},
[])
const
handleRename
=
useCallback
((
newName
:
string
)
=>
{
if
(
showRename
)
handleRenameConversation
(
showRename
.
id
,
newName
,
{
onSuccess
:
handleCancelRename
})
},
[
showRename
,
handleRenameConversation
,
handleCancelRename
])
return
(
<
div
className=
'shrink-0 h-full flex flex-col w-[240px] border-r border-r-gray-100'
>
{
!
isMobile
&&
(
<
div
className=
'shrink-0 flex p-4'
>
<
AppIcon
className=
'mr-3'
size=
'small'
icon=
{
appData
?.
site
.
icon
}
background=
{
appData
?.
site
.
icon_background
}
/>
<
div
className=
'py-1 text-base font-semibold text-gray-800'
>
{
appData
?.
site
.
title
}
</
div
>
</
div
>
)
}
<
div
className=
'shrink-0 p-4'
>
<
Button
className=
'justify-start px-3 py-0 w-full h-9 text-sm font-medium text-primary-600'
onClick=
{
handleNewConversation
}
>
<
Edit05
className=
'mr-2 w-4 h-4'
/>
{
t
(
'share.chat.newChat'
)
}
</
Button
>
</
div
>
<
div
className=
'grow px-4 py-2 overflow-y-auto'
>
{
!!
pinnedConversationList
.
length
&&
(
<
div
className=
'mb-4'
>
<
List
isPin
title=
{
t
(
'share.chat.pinnedTitle'
)
||
''
}
list=
{
pinnedConversationList
}
onChangeConversation=
{
handleChangeConversation
}
onOperate=
{
handleOperate
}
currentConversationId=
{
currentConversationId
}
/>
</
div
>
)
}
{
!!
conversationList
.
length
&&
(
<
List
title=
{
(
pinnedConversationList
.
length
&&
t
(
'share.chat.unpinnedTitle'
))
||
''
}
list=
{
conversationList
}
onChangeConversation=
{
handleChangeConversation
}
onOperate=
{
handleOperate
}
currentConversationId=
{
currentConversationId
}
/>
)
}
</
div
>
<
div
className=
'px-4 pb-4 text-xs text-gray-400'
>
©
{
appData
?.
site
.
copyright
||
appData
?.
site
.
title
}
{
(
new
Date
()).
getFullYear
()
}
</
div
>
{
!!
showConfirm
&&
(
<
Confirm
title=
{
t
(
'share.chat.deleteConversation.title'
)
}
content=
{
t
(
'share.chat.deleteConversation.content'
)
||
''
}
isShow
onClose=
{
handleCancelConfirm
}
onCancel=
{
handleCancelConfirm
}
onConfirm=
{
handleDelete
}
/>
)
}
{
showRename
&&
(
<
RenameModal
isShow
onClose=
{
handleCancelRename
}
saveLoading=
{
conversationRenaming
}
name=
{
showRename
?.
name
||
''
}
onSave=
{
handleRename
}
/>
)
}
</
div
>
)
}
export
default
Sidebar
web/app/components/base/chat/chat-with-history/sidebar/item.tsx
0 → 100644
View file @
51d35926
import
type
{
FC
}
from
'react'
import
{
memo
,
useRef
,
}
from
'react'
import
{
useHover
}
from
'ahooks'
import
type
{
ConversationItem
}
from
'@/models/share'
import
{
MessageDotsCircle
}
from
'@/app/components/base/icons/src/vender/solid/communication'
import
ItemOperation
from
'@/app/components/explore/item-operation'
type
ItemProps
=
{
isPin
?:
boolean
item
:
ConversationItem
onOperate
:
(
type
:
string
,
item
:
ConversationItem
)
=>
void
onChangeConversation
:
(
conversationId
:
string
)
=>
void
currentConversationId
:
string
}
const
Item
:
FC
<
ItemProps
>
=
({
isPin
,
item
,
onOperate
,
onChangeConversation
,
currentConversationId
,
})
=>
{
const
ref
=
useRef
(
null
)
const
isHovering
=
useHover
(
ref
)
return
(
<
div
ref=
{
ref
}
key=
{
item
.
id
}
className=
{
`
flex mb-0.5 last-of-type:mb-0 py-1.5 pl-3 pr-1.5 text-sm font-medium text-gray-700
rounded-lg cursor-pointer hover:bg-gray-50 group
${currentConversationId === item.id && 'text-primary-600 bg-primary-50'}
`
}
onClick=
{
()
=>
onChangeConversation
(
item
.
id
)
}
>
<
MessageDotsCircle
className=
{
`shrink-0 mt-1 mr-2 w-4 h-4 text-gray-400 ${currentConversationId === item.id && 'text-primary-600'}`
}
/>
<
div
className=
'grow py-0.5 break-all'
title=
{
item
.
name
}
>
{
item
.
name
}
</
div
>
{
item
.
id
!==
''
&&
(
<
div
className=
'shrink-0 h-6'
onClick=
{
e
=>
e
.
stopPropagation
()
}
>
<
ItemOperation
isPinned=
{
!!
isPin
}
isItemHovering=
{
isHovering
}
togglePin=
{
()
=>
onOperate
(
isPin
?
'unpin'
:
'pin'
,
item
)
}
isShowDelete
isShowRenameConversation
onRenameConversation=
{
()
=>
onOperate
(
'rename'
,
item
)
}
onDelete=
{
()
=>
onOperate
(
'delete'
,
item
)
}
/>
</
div
>
)
}
</
div
>
)
}
export
default
memo
(
Item
)
web/app/components/base/chat/chat-with-history/sidebar/list.tsx
0 → 100644
View file @
51d35926
import
type
{
FC
}
from
'react'
import
Item
from
'./item'
import
type
{
ConversationItem
}
from
'@/models/share'
type
ListProps
=
{
isPin
?:
boolean
title
?:
string
list
:
ConversationItem
[]
onOperate
:
(
type
:
string
,
item
:
ConversationItem
)
=>
void
onChangeConversation
:
(
conversationId
:
string
)
=>
void
currentConversationId
:
string
}
const
List
:
FC
<
ListProps
>
=
({
isPin
,
title
,
list
,
onOperate
,
onChangeConversation
,
currentConversationId
,
})
=>
{
return
(
<
div
>
{
title
&&
(
<
div
className=
'mb-0.5 px-3 h-[26px] text-xs font-medium text-gray-500'
>
{
title
}
</
div
>
)
}
{
list
.
map
(
item
=>
(
<
Item
key=
{
item
.
id
}
isPin=
{
isPin
}
item=
{
item
}
onOperate=
{
onOperate
}
onChangeConversation=
{
onChangeConversation
}
currentConversationId=
{
currentConversationId
}
/>
))
}
</
div
>
)
}
export
default
List
web/app/components/base/chat/chat/answer/index.tsx
View file @
51d35926
...
...
@@ -82,7 +82,7 @@ const Answer: FC<AnswerProps> = ({
)
}
{
hasAgentThoughts
&&
!
content
&&
(
hasAgentThoughts
&&
(
<
AgentContent
item=
{
item
}
/>
)
}
...
...
web/app/components/base/chat/chat/answer/operation.tsx
View file @
51d35926
import
type
{
FC
}
from
'react'
import
{
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
ChatItem
}
from
'../../types'
import
{
useCurrentAnswerIsResponsing
}
from
'../hooks'
import
{
useChatContext
}
from
'../context'
...
...
@@ -8,6 +9,11 @@ import { MessageFast } from '@/app/components/base/icons/src/vender/solid/commun
import
AudioBtn
from
'@/app/components/base/audio-btn'
import
AnnotationCtrlBtn
from
'@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn'
import
EditReplyModal
from
'@/app/components/app/annotation/edit-annotation-modal'
import
{
ThumbsDown
,
ThumbsUp
,
}
from
'@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import
TooltipPlus
from
'@/app/components/base/tooltip-plus'
type
OperationProps
=
{
item
:
ChatItem
...
...
@@ -19,11 +25,13 @@ const Operation: FC<OperationProps> = ({
question
,
index
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
{
config
,
onAnnotationAdded
,
onAnnotationEdited
,
onAnnotationRemoved
,
onFeedback
,
}
=
useChatContext
()
const
[
isShowReplyModal
,
setIsShowReplyModal
]
=
useState
(
false
)
const
responsing
=
useCurrentAnswerIsResponsing
(
item
.
id
)
...
...
@@ -32,8 +40,18 @@ const Operation: FC<OperationProps> = ({
isOpeningStatement
,
content
,
annotation
,
feedback
,
}
=
item
const
hasAnnotation
=
!!
annotation
?.
id
const
[
localFeedback
,
setLocalFeedback
]
=
useState
(
feedback
)
const
handleFeedback
=
async
(
rating
:
'like'
|
'dislike'
|
null
)
=>
{
if
(
!
config
?.
supportFeedback
||
!
onFeedback
)
return
await
onFeedback
?.(
id
,
{
rating
})
setLocalFeedback
({
rating
})
}
return
(
<
div
className=
'absolute top-[-14px] right-[-14px] flex justify-end gap-1'
>
...
...
@@ -90,6 +108,53 @@ const Operation: FC<OperationProps> = ({
</
div
>
)
}
{
config
?.
supportFeedback
&&
!
localFeedback
?.
rating
&&
onFeedback
&&
!
isOpeningStatement
&&
(
<
div
className=
'hidden group-hover:flex ml-1 shrink-0 items-center px-0.5 bg-white border-[0.5px] border-gray-100 shadow-md text-gray-500 rounded-lg'
>
<
TooltipPlus
popupContent=
{
t
(
'appDebug.operation.agree'
)
}
>
<
div
className=
'flex items-center justify-center mr-0.5 w-6 h-6 rounded-md hover:bg-black/5 hover:text-gray-800 cursor-pointer'
onClick=
{
()
=>
handleFeedback
(
'like'
)
}
>
<
ThumbsUp
className=
'w-4 h-4'
/>
</
div
>
</
TooltipPlus
>
<
TooltipPlus
popupContent=
{
t
(
'appDebug.operation.disagree'
)
}
>
<
div
className=
'flex items-center justify-center w-6 h-6 rounded-md hover:bg-black/5 hover:text-gray-800 cursor-pointer'
onClick=
{
()
=>
handleFeedback
(
'dislike'
)
}
>
<
ThumbsDown
className=
'w-4 h-4'
/>
</
div
>
</
TooltipPlus
>
</
div
>
)
}
{
config
?.
supportFeedback
&&
localFeedback
?.
rating
&&
onFeedback
&&
!
isOpeningStatement
&&
(
<
TooltipPlus
popupContent=
{
localFeedback
.
rating
===
'like'
?
t
(
'appDebug.operation.cancelAgree'
)
:
t
(
'appDebug.operation.cancelDisagree'
)
}
>
<
div
className=
{
`
flex items-center justify-center w-7 h-7 rounded-[10px] border-[2px] border-white cursor-pointer
${localFeedback.rating === 'like' && 'bg-blue-50 text-blue-600'}
${localFeedback.rating === 'dislike' && 'bg-red-100 text-red-600'}
`
}
onClick=
{
()
=>
handleFeedback
(
null
)
}
>
{
localFeedback
.
rating
===
'like'
&&
(
<
ThumbsUp
className=
'w-4 h-4'
/>
)
}
{
localFeedback
.
rating
===
'dislike'
&&
(
<
ThumbsDown
className=
'w-4 h-4'
/>
)
}
</
div
>
</
TooltipPlus
>
)
}
</
div
>
)
}
...
...
web/app/components/base/chat/chat/answer/suggested-questions.tsx
View file @
51d35926
...
...
@@ -19,7 +19,7 @@ const SuggestedQuestions: FC<SuggestedQuestionsProps> = ({
return
(
<
div
className=
'flex flex-wrap'
>
{
suggestedQuestions
.
map
((
question
,
index
)
=>
(
{
suggestedQuestions
.
filter
(
q
=>
!!
q
&&
q
.
trim
()).
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'
...
...
web/app/components/base/chat/chat/context.tsx
View file @
51d35926
...
...
@@ -15,6 +15,7 @@ export type ChatContextValue = Pick<ChatProps, 'config'
|
'onAnnotationEdited'
|
'onAnnotationAdded'
|
'onAnnotationRemoved'
|
'onFeedback'
>
const
ChatContext
=
createContext
<
ChatContextValue
>
({
...
...
@@ -38,6 +39,7 @@ export const ChatContextProvider = ({
onAnnotationEdited
,
onAnnotationAdded
,
onAnnotationRemoved
,
onFeedback
,
}:
ChatContextProviderProps
)
=>
{
return
(
<
ChatContext
.
Provider
value=
{
{
...
...
@@ -52,6 +54,7 @@ export const ChatContextProvider = ({
onAnnotationEdited
,
onAnnotationAdded
,
onAnnotationRemoved
,
onFeedback
,
}
}
>
{
children
}
</
ChatContext
.
Provider
>
...
...
web/app/components/base/chat/chat/hooks.ts
View file @
51d35926
...
...
@@ -5,7 +5,7 @@ import {
useState
,
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
produce
}
from
'immer'
import
{
produce
,
setAutoFreeze
}
from
'immer'
import
dayjs
from
'dayjs'
import
type
{
ChatConfig
,
...
...
@@ -23,8 +23,10 @@ import type { Annotation } from '@/models/log'
type
GetAbortController
=
(
abortController
:
AbortController
)
=>
void
type
SendCallback
=
{
onGetConvesationMessages
:
(
conversationId
:
string
,
getAbortController
:
GetAbortController
)
=>
Promise
<
any
>
onGetConvesationMessages
?
:
(
conversationId
:
string
,
getAbortController
:
GetAbortController
)
=>
Promise
<
any
>
onGetSuggestedQuestions
?:
(
responseItemId
:
string
,
getAbortController
:
GetAbortController
)
=>
Promise
<
any
>
onConversationComplete
?:
(
conversationId
:
string
)
=>
void
isPublicAPI
?:
boolean
}
export
const
useCheckPromptVariables
=
()
=>
{
...
...
@@ -67,7 +69,7 @@ export const useCheckPromptVariables = () => {
}
export
const
useChat
=
(
config
:
ChatConfig
,
config
?
:
ChatConfig
,
promptVariablesConfig
?:
{
inputs
:
Inputs
promptVariables
:
PromptVariable
[]
...
...
@@ -90,10 +92,17 @@ export const useChat = (
const
suggestedQuestionsAbortControllerRef
=
useRef
<
AbortController
|
null
>
(
null
)
const
checkPromptVariables
=
useCheckPromptVariables
()
useEffect
(()
=>
{
setAutoFreeze
(
false
)
return
()
=>
{
setAutoFreeze
(
true
)
}
},
[])
const
handleUpdateChatList
=
useCallback
((
newChatList
:
ChatItem
[])
=>
{
setChatList
(
newChatList
)
chatListRef
.
current
=
newChatList
},
[])
},
[
setChatList
])
const
handleResponsing
=
useCallback
((
isResponsing
:
boolean
)
=>
{
setIsResponsing
(
isResponsing
)
isResponsingRef
.
current
=
isResponsing
...
...
@@ -103,22 +112,19 @@ export const useChat = (
return
replaceStringWithValues
(
str
,
promptVariablesConfig
?.
promptVariables
||
[],
promptVariablesConfig
?.
inputs
||
{})
},
[
promptVariablesConfig
?.
inputs
,
promptVariablesConfig
?.
promptVariables
])
useEffect
(()
=>
{
if
(
config
.
opening_statement
&&
!
chatList
.
length
)
{
handleUpdateChatList
([{
if
(
config
?.
opening_statement
&&
chatListRef
.
current
.
filter
(
item
=>
item
.
isOpeningStatement
).
length
===
0
)
{
handleUpdateChatList
([
{
id
:
`
${
Date
.
now
()}
`
,
content
:
getIntroduction
(
config
.
opening_statement
),
isAnswer
:
true
,
isOpeningStatement
:
true
,
suggestedQuestions
:
config
.
suggested_questions
,
}])
}
},
[
config
.
opening_statement
,
config
.
suggested_questions
,
getIntroduction
,
chatList
,
handleUpdateChatList
,
},
...
chatListRef
.
current
,
])
}
},
[])
const
handleStop
=
useCallback
(()
=>
{
hasStopResponded
.
current
=
true
...
...
@@ -136,7 +142,7 @@ export const useChat = (
const
handleRestart
=
useCallback
(()
=>
{
handleStop
()
connversationId
.
current
=
''
const
newChatList
=
config
.
opening_statement
const
newChatList
=
config
?
.
opening_statement
?
[{
id
:
`
${
Date
.
now
()}
`
,
content
:
config
.
opening_statement
,
...
...
@@ -181,6 +187,8 @@ export const useChat = (
{
onGetConvesationMessages
,
onGetSuggestedQuestions
,
onConversationComplete
,
isPublicAPI
,
}:
SendCallback
,
)
=>
{
setSuggestQuestions
([])
...
...
@@ -248,6 +256,7 @@ export const useChat = (
body
:
bodyParams
,
},
{
isPublicAPI
,
getAbortController
:
(
abortController
)
=>
{
abortControllerRef
.
current
=
abortController
},
...
...
@@ -286,7 +295,10 @@ export const useChat = (
if
(
hasError
)
return
if
(
connversationId
.
current
&&
!
hasStopResponded
.
current
)
{
if
(
onConversationComplete
)
onConversationComplete
(
connversationId
.
current
)
if
(
connversationId
.
current
&&
!
hasStopResponded
.
current
&&
onGetConvesationMessages
)
{
const
{
data
}:
any
=
await
onGetConvesationMessages
(
connversationId
.
current
,
newAbortController
=>
conversationMessagesAbortControllerRef
.
current
=
newAbortController
,
...
...
@@ -315,7 +327,7 @@ export const useChat = (
})
handleUpdateChatList
(
newChatList
)
}
if
(
config
.
suggested_questions_after_answer
?.
enabled
&&
!
hasStopResponded
.
current
&&
onGetSuggestedQuestions
)
{
if
(
config
?
.
suggested_questions_after_answer
?.
enabled
&&
!
hasStopResponded
.
current
&&
onGetSuggestedQuestions
)
{
const
{
data
}:
any
=
await
onGetSuggestedQuestions
(
responseItem
.
id
,
newAbortController
=>
suggestedQuestionsAbortControllerRef
.
current
=
newAbortController
,
...
...
@@ -409,7 +421,7 @@ export const useChat = (
return
true
},
[
checkPromptVariables
,
config
.
suggested_questions_after_answer
,
config
?
.
suggested_questions_after_answer
,
updateCurrentQA
,
t
,
notify
,
...
...
@@ -419,7 +431,7 @@ export const useChat = (
])
const
handleAnnotationEdited
=
useCallback
((
query
:
string
,
answer
:
string
,
index
:
number
)
=>
{
set
ChatList
(
chatListRef
.
current
.
map
((
item
,
i
)
=>
{
handleUpdate
ChatList
(
chatListRef
.
current
.
map
((
item
,
i
)
=>
{
if
(
i
===
index
-
1
)
{
return
{
...
item
,
...
...
@@ -438,9 +450,9 @@ export const useChat = (
}
return
item
}))
},
[])
},
[
handleUpdateChatList
])
const
handleAnnotationAdded
=
useCallback
((
annotationId
:
string
,
authorName
:
string
,
query
:
string
,
answer
:
string
,
index
:
number
)
=>
{
set
ChatList
(
chatListRef
.
current
.
map
((
item
,
i
)
=>
{
handleUpdate
ChatList
(
chatListRef
.
current
.
map
((
item
,
i
)
=>
{
if
(
i
===
index
-
1
)
{
return
{
...
item
,
...
...
@@ -468,9 +480,9 @@ export const useChat = (
}
return
item
}))
},
[])
},
[
handleUpdateChatList
])
const
handleAnnotationRemoved
=
useCallback
((
index
:
number
)
=>
{
set
ChatList
(
chatListRef
.
current
.
map
((
item
,
i
)
=>
{
handleUpdate
ChatList
(
chatListRef
.
current
.
map
((
item
,
i
)
=>
{
if
(
i
===
index
)
{
return
{
...
item
,
...
...
@@ -483,7 +495,7 @@ export const useChat = (
}
return
item
}))
},
[])
},
[
handleUpdateChatList
])
return
{
chatList
,
...
...
web/app/components/base/chat/chat/index.tsx
View file @
51d35926
...
...
@@ -12,6 +12,7 @@ import { useThrottleEffect } from 'ahooks'
import
type
{
ChatConfig
,
ChatItem
,
Feedback
,
OnSend
,
}
from
'../types'
import
Question
from
'./question'
...
...
@@ -32,7 +33,9 @@ export type ChatProps = {
noChatInput
?:
boolean
onSend
?:
OnSend
chatContainerclassName
?:
string
chatContainerInnerClassName
?:
string
chatFooterClassName
?:
string
chatFooterInnerClassName
?:
string
suggestedQuestions
?:
string
[]
showPromptLog
?:
boolean
questionIcon
?:
ReactNode
...
...
@@ -41,6 +44,8 @@ export type ChatProps = {
onAnnotationEdited
?:
(
question
:
string
,
answer
:
string
,
index
:
number
)
=>
void
onAnnotationAdded
?:
(
annotationId
:
string
,
authorName
:
string
,
question
:
string
,
answer
:
string
,
index
:
number
)
=>
void
onAnnotationRemoved
?:
(
index
:
number
)
=>
void
chatNode
?:
ReactNode
onFeedback
?:
(
messageId
:
string
,
feedback
:
Feedback
)
=>
void
}
const
Chat
:
FC
<
ChatProps
>
=
({
config
,
...
...
@@ -51,7 +56,9 @@ const Chat: FC<ChatProps> = ({
onStopResponding
,
noChatInput
,
chatContainerclassName
,
chatContainerInnerClassName
,
chatFooterClassName
,
chatFooterInnerClassName
,
suggestedQuestions
,
showPromptLog
,
questionIcon
,
...
...
@@ -60,10 +67,14 @@ const Chat: FC<ChatProps> = ({
onAnnotationAdded
,
onAnnotationEdited
,
onAnnotationRemoved
,
chatNode
,
onFeedback
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
chatContainerRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
chatContainerInnerRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
chatFooterRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
chatFooterInnerRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
handleScrolltoBottom
=
()
=>
{
if
(
chatContainerRef
.
current
)
...
...
@@ -75,6 +86,9 @@ const Chat: FC<ChatProps> = ({
if
(
chatContainerRef
.
current
&&
chatFooterRef
.
current
)
chatFooterRef
.
current
.
style
.
width
=
`
${
chatContainerRef
.
current
.
clientWidth
}
px`
if
(
chatContainerInnerRef
.
current
&&
chatFooterInnerRef
.
current
)
chatFooterInnerRef
.
current
.
style
.
width
=
`
${
chatContainerInnerRef
.
current
.
clientWidth
}
px`
},
[
chatList
],
{
wait
:
500
})
useEffect
(()
=>
{
...
...
@@ -111,11 +125,17 @@ const Chat: FC<ChatProps> = ({
onAnnotationAdded=
{
onAnnotationAdded
}
onAnnotationEdited=
{
onAnnotationEdited
}
onAnnotationRemoved=
{
onAnnotationRemoved
}
onFeedback=
{
onFeedback
}
>
<
div
className=
'relative h-full'
>
<
div
ref=
{
chatContainerRef
}
className=
{
`relative h-full overflow-y-auto ${chatContainerclassName}`
}
>
{
chatNode
}
<
div
ref=
{
chatContainerInnerRef
}
className=
{
`${chatContainerInnerClassName}`
}
>
{
chatList
.
map
((
item
,
index
)
=>
{
...
...
@@ -138,12 +158,17 @@ const Chat: FC<ChatProps> = ({
})
}
</
div
>
</
div
>
<
div
className=
{
`absolute bottom-0 ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`
}
ref=
{
chatFooterRef
}
style=
{
{
background
:
'linear-gradient(0deg, #F9FAFB 40%, rgba(255, 255, 255, 0.00) 100%)'
,
}
}
>
<
div
ref=
{
chatFooterInnerRef
}
className=
{
`${chatFooterInnerClassName}`
}
>
{
!
noStopResponding
&&
isResponsing
&&
(
...
...
@@ -174,6 +199,7 @@ const Chat: FC<ChatProps> = ({
}
</
div
>
</
div
>
</
div
>
</
ChatContextProvider
>
)
}
...
...
web/app/components/base/chat/chat/try-to-ask.tsx
View file @
51d35926
...
...
@@ -34,7 +34,7 @@ const TryToAsk: FC<TryToAskProps> = ({
}
}
/>
</
div
>
<
div
className=
'flex flex-wrap'
>
<
div
className=
'flex flex-wrap
justify-center
'
>
{
suggestedQuestions
.
map
((
suggestQuestion
,
index
)
=>
(
<
Button
...
...
web/app/components/base/chat/constants.ts
0 → 100644
View file @
51d35926
export
const
CONVERSATION_ID_INFO
=
'conversationIdInfo'
web/app/components/base/chat/types.ts
View file @
51d35926
...
...
@@ -44,8 +44,17 @@ export type EnableType = {
export
type
ChatConfig
=
Omit
<
ModelConfig
,
'model'
>
&
{
supportAnnotation
?:
boolean
appId
?:
string
supportFeedback
?:
boolean
}
export
type
ChatItem
=
IChatItem
export
type
OnSend
=
(
message
:
string
,
files
?:
VisionFile
[])
=>
void
export
type
Callback
=
{
onSuccess
:
()
=>
void
}
export
type
Feedback
=
{
rating
:
'like'
|
'dislike'
|
null
}
web/app/components/base/confirm/common.tsx
View file @
51d35926
...
...
@@ -20,6 +20,7 @@ export type ConfirmCommonProps = {
confirmBtnClassName
?:
string
confirmText
?:
string
confirmWrapperClassName
?:
string
confirmDisabled
?:
boolean
}
const
ConfirmCommon
:
FC
<
ConfirmCommonProps
>
=
({
...
...
@@ -34,6 +35,7 @@ const ConfirmCommon: FC<ConfirmCommonProps> = ({
confirmBtnClassName
,
confirmText
,
confirmWrapperClassName
,
confirmDisabled
,
})
=>
{
const
{
t
}
=
useTranslation
()
...
...
@@ -78,6 +80,7 @@ const ConfirmCommon: FC<ConfirmCommonProps> = ({
type=
'primary'
className=
{
confirmBtnClassName
||
''
}
onClick=
{
onConfirm
}
disabled=
{
confirmDisabled
}
>
{
confirmText
||
CONFIRM_MAP
[
type
].
confirmText
}
</
Button
>
...
...
web/app/components/base/icons/assets/vender/line/alertsAndFeedback/thumbs-down.svg
0 → 100644
View file @
51d35926
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"Icon"
clip-path=
"url(#clip0_17340_934)"
>
<path
id=
"Icon_2"
d=
"M11.3333 1.33398V8.66732M14.6666 6.53398V3.46732C14.6666 2.72058 14.6666 2.34721 14.5213 2.062C14.3935 1.81111 14.1895 1.60714 13.9386 1.47931C13.6534 1.33398 13.28 1.33398 12.5333 1.33398H5.41196C4.43764 1.33398 3.95048 1.33398 3.55701 1.51227C3.21022 1.66941 2.91549 1.92227 2.70745 2.24113C2.4714 2.60291 2.39732 3.08441 2.24917 4.0474L1.90045 6.31407C1.70505 7.58419 1.60735 8.21926 1.79582 8.7134C1.96125 9.14711 2.27239 9.50978 2.6759 9.73923C3.13564 10.0007 3.77818 10.0007 5.06324 10.0007H5.59995C5.97332 10.0007 6.16001 10.0007 6.30261 10.0733C6.42806 10.1372 6.53004 10.2392 6.59396 10.3647C6.66662 10.5073 6.66662 10.6939 6.66662 11.0673V13.0234C6.66662 13.9313 7.40262 14.6673 8.31051 14.6673C8.52706 14.6673 8.7233 14.5398 8.81125 14.3419L11.0518 9.30077C11.1537 9.07148 11.2046 8.95684 11.2852 8.87278C11.3563 8.79847 11.4438 8.74165 11.5406 8.70678C11.6501 8.66732 11.7756 8.66732 12.0265 8.66732H12.5333C13.28 8.66732 13.6534 8.66732 13.9386 8.52199C14.1895 8.39416 14.3935 8.19019 14.5213 7.93931C14.6666 7.65409 14.6666 7.28072 14.6666 6.53398Z"
stroke=
"#667085"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</g>
<defs>
<clipPath
id=
"clip0_17340_934"
>
<rect
width=
"16"
height=
"16"
fill=
"white"
/>
</clipPath>
</defs>
</svg>
web/app/components/base/icons/assets/vender/line/alertsAndFeedback/thumbs-up.svg
0 → 100644
View file @
51d35926
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"Icon"
clip-path=
"url(#clip0_17340_931)"
>
<path
id=
"Icon_2"
d=
"M4.66671 14.6673V7.33398M1.33337 8.66732V13.334C1.33337 14.0704 1.93033 14.6673 2.66671 14.6673H11.6175C12.6047 14.6673 13.4442 13.9471 13.5943 12.9714L14.3122 8.30477C14.4986 7.09325 13.5613 6.00065 12.3355 6.00065H10C9.63185 6.00065 9.33337 5.70217 9.33337 5.33398V2.97788C9.33337 2.06998 8.59738 1.33398 7.68948 1.33398C7.47293 1.33398 7.27669 1.46151 7.18875 1.6594L4.84267 6.93808C4.73567 7.17883 4.49692 7.33398 4.23346 7.33398H2.66671C1.93033 7.33398 1.33337 7.93094 1.33337 8.66732Z"
stroke=
"#667085"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</g>
<defs>
<clipPath
id=
"clip0_17340_931"
>
<rect
width=
"16"
height=
"16"
fill=
"white"
/>
</clipPath>
</defs>
</svg>
web/app/components/base/icons/assets/vender/line/general/edit-05.svg
0 → 100644
View file @
51d35926
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"edit-05"
clip-path=
"url(#clip0_17249_52683)"
>
<path
id=
"Icon"
d=
"M7.33325 2.66617H4.53325C3.41315 2.66617 2.85309 2.66617 2.42527 2.88415C2.04895 3.0759 1.74299 3.38186 1.55124 3.75819C1.33325 4.18601 1.33325 4.74606 1.33325 5.86617V11.4662C1.33325 12.5863 1.33325 13.1463 1.55124 13.5741C1.74299 13.9505 2.04895 14.2564 2.42527 14.4482C2.85309 14.6662 3.41315 14.6662 4.53325 14.6662H10.1333C11.2534 14.6662 11.8134 14.6662 12.2412 14.4482C12.6176 14.2564 12.9235 13.9505 13.1153 13.5741C13.3333 13.1463 13.3333 12.5863 13.3333 11.4662V8.66617M5.33323 10.6662H6.4496C6.77572 10.6662 6.93878 10.6662 7.09223 10.6293C7.22828 10.5967 7.35834 10.5428 7.47763 10.4697C7.61219 10.3872 7.72749 10.2719 7.95809 10.0413L14.3333 3.66617C14.8855 3.11388 14.8855 2.21845 14.3333 1.66617C13.781 1.11388 12.8855 1.11388 12.3333 1.66617L5.95808 8.04133C5.72747 8.27193 5.61217 8.38723 5.52971 8.52179C5.45661 8.64108 5.40274 8.77114 5.37007 8.90719C5.33323 9.06064 5.33323 9.2237 5.33323 9.54982V10.6662Z"
stroke=
"#155EEF"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</g>
<defs>
<clipPath
id=
"clip0_17249_52683"
>
<rect
width=
"16"
height=
"16"
fill=
"white"
/>
</clipPath>
</defs>
</svg>
web/app/components/base/icons/assets/vender/line/general/menu-01.svg
0 → 100644
View file @
51d35926
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"menu-01"
>
<path
id=
"Icon"
d=
"M2 8H14M2 4H14M2 12H14"
stroke=
"#344054"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</g>
</svg>
web/app/components/base/icons/assets/vender/line/general/pin-01.svg
0 → 100644
View file @
51d35926
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"pin-01"
>
<path
id=
"Icon"
d=
"M8.00037 10.0007L8.00037 14.6673M5.3337 4.87274V6.29315C5.3337 6.43183 5.3337 6.50117 5.32009 6.56749C5.30801 6.62633 5.28804 6.68327 5.26071 6.73677C5.22991 6.79706 5.18659 6.8512 5.09996 6.95949L4.05344 8.26764C3.60962 8.82242 3.3877 9.09982 3.38745 9.33326C3.38723 9.53629 3.47954 9.72835 3.63822 9.85501C3.82067 10.0007 4.1759 10.0007 4.88637 10.0007H11.1144C11.8248 10.0007 12.1801 10.0007 12.3625 9.85501C12.5212 9.72835 12.6135 9.53629 12.6133 9.33326C12.613 9.09982 12.3911 8.82242 11.9473 8.26764L10.9008 6.95949C10.8141 6.8512 10.7708 6.79706 10.74 6.73677C10.7127 6.68327 10.6927 6.62633 10.6806 6.56749C10.667 6.50117 10.667 6.43183 10.667 6.29315V4.87274C10.667 4.79599 10.667 4.75761 10.6714 4.71977C10.6752 4.68615 10.6816 4.65287 10.6905 4.62023C10.7006 4.58348 10.7148 4.54785 10.7433 4.47659L11.4152 2.7968C11.6113 2.30674 11.7093 2.06171 11.6684 1.86502C11.6327 1.693 11.5305 1.54206 11.384 1.44499C11.2166 1.33398 10.9527 1.33398 10.4249 1.33398H5.57587C5.04806 1.33398 4.78416 1.33398 4.61671 1.44499C4.47027 1.54206 4.36808 1.693 4.33233 1.86502C4.29146 2.06171 4.38947 2.30674 4.58549 2.7968L5.25741 4.47659C5.28591 4.54785 5.30017 4.58348 5.31019 4.62023C5.3191 4.65287 5.32551 4.68615 5.32936 4.71977C5.3337 4.75761 5.3337 4.79599 5.3337 4.87274Z"
stroke=
"#667085"
stroke-width=
"1.5"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/>
</g>
</svg>
web/app/components/base/icons/assets/vender/solid/shapes/star-06.svg
0 → 100644
View file @
51d35926
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
id=
"star-06"
>
<g
id=
"Solid"
>
<path
d=
"M3.66675 1.33268C3.66675 0.964492 3.36827 0.666016 3.00008 0.666016C2.63189 0.666016 2.33341 0.964492 2.33341 1.33268V2.33268H1.33341C0.965225 2.33268 0.666748 2.63116 0.666748 2.99935C0.666748 3.36754 0.965225 3.66602 1.33341 3.66602H2.33341V4.66602C2.33341 5.0342 2.63189 5.33268 3.00008 5.33268C3.36827 5.33268 3.66675 5.0342 3.66675 4.66602V3.66602H4.66675C5.03494 3.66602 5.33341 3.36754 5.33341 2.99935C5.33341 2.63116 5.03494 2.33268 4.66675 2.33268H3.66675V1.33268Z"
fill=
"#444CE7"
/>
<path
d=
"M3.66675 11.3327C3.66675 10.9645 3.36827 10.666 3.00008 10.666C2.63189 10.666 2.33341 10.9645 2.33341 11.3327V12.3327H1.33341C0.965225 12.3327 0.666748 12.6312 0.666748 12.9993C0.666748 13.3675 0.965225 13.666 1.33341 13.666H2.33341V14.666C2.33341 15.0342 2.63189 15.3327 3.00008 15.3327C3.36827 15.3327 3.66675 15.0342 3.66675 14.666V13.666H4.66675C5.03494 13.666 5.33341 13.3675 5.33341 12.9993C5.33341 12.6312 5.03494 12.3327 4.66675 12.3327H3.66675V11.3327Z"
fill=
"#444CE7"
/>
<path
d=
"M9.28898 1.76003C9.18995 1.50257 8.94259 1.33268 8.66675 1.33268C8.3909 1.33268 8.14354 1.50257 8.04452 1.76003L6.8884 4.76594C6.68813 5.28663 6.6252 5.43668 6.53912 5.55774C6.45274 5.67921 6.34661 5.78534 6.22514 5.87172C6.10408 5.9578 5.95403 6.02073 5.43334 6.221L2.42743 7.37712C2.16997 7.47614 2.00008 7.7235 2.00008 7.99935C2.00008 8.2752 2.16997 8.52256 2.42743 8.62158L5.43334 9.7777C5.95403 9.97797 6.10408 10.0409 6.22514 10.127C6.34661 10.2134 6.45274 10.3195 6.53912 10.441C6.6252 10.562 6.68813 10.7121 6.8884 11.2328L8.04452 14.2387C8.14354 14.4961 8.3909 14.666 8.66675 14.666C8.9426 14.666 9.18995 14.4961 9.28898 14.2387L10.4451 11.2328C10.6454 10.7121 10.7083 10.562 10.7944 10.441C10.8808 10.3195 10.9869 10.2134 11.1084 10.127C11.2294 10.0409 11.3795 9.97797 11.9002 9.7777L14.9061 8.62158C15.1635 8.52256 15.3334 8.2752 15.3334 7.99935C15.3334 7.7235 15.1635 7.47614 14.9061 7.37712L11.9002 6.221C11.3795 6.02073 11.2294 5.9578 11.1084 5.87172C10.9869 5.78534 10.8808 5.67921 10.7944 5.55774C10.7083 5.43668 10.6454 5.28663 10.4451 4.76594L9.28898 1.76003Z"
fill=
"#444CE7"
/>
</g>
</g>
</svg>
web/app/components/base/icons/src/vender/line/alertsAndFeedback/ThumbsDown.json
0 → 100644
View file @
51d35926
{
"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"
:
"Icon"
,
"clip-path"
:
"url(#clip0_17340_934)"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Icon_2"
,
"d"
:
"M11.3333 1.33398V8.66732M14.6666 6.53398V3.46732C14.6666 2.72058 14.6666 2.34721 14.5213 2.062C14.3935 1.81111 14.1895 1.60714 13.9386 1.47931C13.6534 1.33398 13.28 1.33398 12.5333 1.33398H5.41196C4.43764 1.33398 3.95048 1.33398 3.55701 1.51227C3.21022 1.66941 2.91549 1.92227 2.70745 2.24113C2.4714 2.60291 2.39732 3.08441 2.24917 4.0474L1.90045 6.31407C1.70505 7.58419 1.60735 8.21926 1.79582 8.7134C1.96125 9.14711 2.27239 9.50978 2.6759 9.73923C3.13564 10.0007 3.77818 10.0007 5.06324 10.0007H5.59995C5.97332 10.0007 6.16001 10.0007 6.30261 10.0733C6.42806 10.1372 6.53004 10.2392 6.59396 10.3647C6.66662 10.5073 6.66662 10.6939 6.66662 11.0673V13.0234C6.66662 13.9313 7.40262 14.6673 8.31051 14.6673C8.52706 14.6673 8.7233 14.5398 8.81125 14.3419L11.0518 9.30077C11.1537 9.07148 11.2046 8.95684 11.2852 8.87278C11.3563 8.79847 11.4438 8.74165 11.5406 8.70678C11.6501 8.66732 11.7756 8.66732 12.0265 8.66732H12.5333C13.28 8.66732 13.6534 8.66732 13.9386 8.52199C14.1895 8.39416 14.3935 8.19019 14.5213 7.93931C14.6666 7.65409 14.6666 7.28072 14.6666 6.53398Z"
,
"stroke"
:
"currentColor"
,
"stroke-width"
:
"1.5"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
}
]
},
{
"type"
:
"element"
,
"name"
:
"defs"
,
"attributes"
:
{},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"clipPath"
,
"attributes"
:
{
"id"
:
"clip0_17340_934"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"rect"
,
"attributes"
:
{
"width"
:
"16"
,
"height"
:
"16"
,
"fill"
:
"white"
},
"children"
:
[]
}
]
}
]
}
]
},
"name"
:
"ThumbsDown"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/line/alertsAndFeedback/ThumbsDown.tsx
0 → 100644
View file @
51d35926
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./ThumbsDown.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
=
'ThumbsDown'
export
default
Icon
web/app/components/base/icons/src/vender/line/alertsAndFeedback/ThumbsUp.json
0 → 100644
View file @
51d35926
{
"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"
:
"Icon"
,
"clip-path"
:
"url(#clip0_17340_931)"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Icon_2"
,
"d"
:
"M4.66671 14.6673V7.33398M1.33337 8.66732V13.334C1.33337 14.0704 1.93033 14.6673 2.66671 14.6673H11.6175C12.6047 14.6673 13.4442 13.9471 13.5943 12.9714L14.3122 8.30477C14.4986 7.09325 13.5613 6.00065 12.3355 6.00065H10C9.63185 6.00065 9.33337 5.70217 9.33337 5.33398V2.97788C9.33337 2.06998 8.59738 1.33398 7.68948 1.33398C7.47293 1.33398 7.27669 1.46151 7.18875 1.6594L4.84267 6.93808C4.73567 7.17883 4.49692 7.33398 4.23346 7.33398H2.66671C1.93033 7.33398 1.33337 7.93094 1.33337 8.66732Z"
,
"stroke"
:
"currentColor"
,
"stroke-width"
:
"1.5"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
}
]
},
{
"type"
:
"element"
,
"name"
:
"defs"
,
"attributes"
:
{},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"clipPath"
,
"attributes"
:
{
"id"
:
"clip0_17340_931"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"rect"
,
"attributes"
:
{
"width"
:
"16"
,
"height"
:
"16"
,
"fill"
:
"white"
},
"children"
:
[]
}
]
}
]
}
]
},
"name"
:
"ThumbsUp"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/line/alertsAndFeedback/ThumbsUp.tsx
0 → 100644
View file @
51d35926
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./ThumbsUp.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
=
'ThumbsUp'
export
default
Icon
web/app/components/base/icons/src/vender/line/alertsAndFeedback/index.ts
View file @
51d35926
export
{
default
as
AlertCircle
}
from
'./AlertCircle'
export
{
default
as
AlertTriangle
}
from
'./AlertTriangle'
export
{
default
as
ThumbsDown
}
from
'./ThumbsDown'
export
{
default
as
ThumbsUp
}
from
'./ThumbsUp'
web/app/components/base/icons/src/vender/line/general/Edit05.json
0 → 100644
View file @
51d35926
{
"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"
:
"edit-05"
,
"clip-path"
:
"url(#clip0_17249_52683)"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Icon"
,
"d"
:
"M7.33325 2.66617H4.53325C3.41315 2.66617 2.85309 2.66617 2.42527 2.88415C2.04895 3.0759 1.74299 3.38186 1.55124 3.75819C1.33325 4.18601 1.33325 4.74606 1.33325 5.86617V11.4662C1.33325 12.5863 1.33325 13.1463 1.55124 13.5741C1.74299 13.9505 2.04895 14.2564 2.42527 14.4482C2.85309 14.6662 3.41315 14.6662 4.53325 14.6662H10.1333C11.2534 14.6662 11.8134 14.6662 12.2412 14.4482C12.6176 14.2564 12.9235 13.9505 13.1153 13.5741C13.3333 13.1463 13.3333 12.5863 13.3333 11.4662V8.66617M5.33323 10.6662H6.4496C6.77572 10.6662 6.93878 10.6662 7.09223 10.6293C7.22828 10.5967 7.35834 10.5428 7.47763 10.4697C7.61219 10.3872 7.72749 10.2719 7.95809 10.0413L14.3333 3.66617C14.8855 3.11388 14.8855 2.21845 14.3333 1.66617C13.781 1.11388 12.8855 1.11388 12.3333 1.66617L5.95808 8.04133C5.72747 8.27193 5.61217 8.38723 5.52971 8.52179C5.45661 8.64108 5.40274 8.77114 5.37007 8.90719C5.33323 9.06064 5.33323 9.2237 5.33323 9.54982V10.6662Z"
,
"stroke"
:
"currentColor"
,
"stroke-width"
:
"1.5"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
}
]
},
{
"type"
:
"element"
,
"name"
:
"defs"
,
"attributes"
:
{},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"clipPath"
,
"attributes"
:
{
"id"
:
"clip0_17249_52683"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"rect"
,
"attributes"
:
{
"width"
:
"16"
,
"height"
:
"16"
,
"fill"
:
"white"
},
"children"
:
[]
}
]
}
]
}
]
},
"name"
:
"Edit05"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/line/general/Edit05.tsx
0 → 100644
View file @
51d35926
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./Edit05.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
=
'Edit05'
export
default
Icon
web/app/components/base/icons/src/vender/line/general/Menu01.json
0 → 100644
View file @
51d35926
{
"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"
:
"menu-01"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Icon"
,
"d"
:
"M2 8H14M2 4H14M2 12H14"
,
"stroke"
:
"currentColor"
,
"stroke-width"
:
"1.5"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"Menu01"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/line/general/Menu01.tsx
0 → 100644
View file @
51d35926
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./Menu01.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
=
'Menu01'
export
default
Icon
web/app/components/base/icons/src/vender/line/general/Pin01.json
0 → 100644
View file @
51d35926
{
"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"
:
"pin-01"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"id"
:
"Icon"
,
"d"
:
"M8.00037 10.0007L8.00037 14.6673M5.3337 4.87274V6.29315C5.3337 6.43183 5.3337 6.50117 5.32009 6.56749C5.30801 6.62633 5.28804 6.68327 5.26071 6.73677C5.22991 6.79706 5.18659 6.8512 5.09996 6.95949L4.05344 8.26764C3.60962 8.82242 3.3877 9.09982 3.38745 9.33326C3.38723 9.53629 3.47954 9.72835 3.63822 9.85501C3.82067 10.0007 4.1759 10.0007 4.88637 10.0007H11.1144C11.8248 10.0007 12.1801 10.0007 12.3625 9.85501C12.5212 9.72835 12.6135 9.53629 12.6133 9.33326C12.613 9.09982 12.3911 8.82242 11.9473 8.26764L10.9008 6.95949C10.8141 6.8512 10.7708 6.79706 10.74 6.73677C10.7127 6.68327 10.6927 6.62633 10.6806 6.56749C10.667 6.50117 10.667 6.43183 10.667 6.29315V4.87274C10.667 4.79599 10.667 4.75761 10.6714 4.71977C10.6752 4.68615 10.6816 4.65287 10.6905 4.62023C10.7006 4.58348 10.7148 4.54785 10.7433 4.47659L11.4152 2.7968C11.6113 2.30674 11.7093 2.06171 11.6684 1.86502C11.6327 1.693 11.5305 1.54206 11.384 1.44499C11.2166 1.33398 10.9527 1.33398 10.4249 1.33398H5.57587C5.04806 1.33398 4.78416 1.33398 4.61671 1.44499C4.47027 1.54206 4.36808 1.693 4.33233 1.86502C4.29146 2.06171 4.38947 2.30674 4.58549 2.7968L5.25741 4.47659C5.28591 4.54785 5.30017 4.58348 5.31019 4.62023C5.3191 4.65287 5.32551 4.68615 5.32936 4.71977C5.3337 4.75761 5.3337 4.79599 5.3337 4.87274Z"
,
"stroke"
:
"currentColor"
,
"stroke-width"
:
"1.5"
,
"stroke-linecap"
:
"round"
,
"stroke-linejoin"
:
"round"
},
"children"
:
[]
}
]
}
]
},
"name"
:
"Pin01"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/line/general/Pin01.tsx
0 → 100644
View file @
51d35926
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./Pin01.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
=
'Pin01'
export
default
Icon
web/app/components/base/icons/src/vender/line/general/index.ts
View file @
51d35926
...
...
@@ -5,6 +5,7 @@ export { default as DotsHorizontal } from './DotsHorizontal'
export
{
default
as
Edit02
}
from
'./Edit02'
export
{
default
as
Edit03
}
from
'./Edit03'
export
{
default
as
Edit04
}
from
'./Edit04'
export
{
default
as
Edit05
}
from
'./Edit05'
export
{
default
as
Hash02
}
from
'./Hash02'
export
{
default
as
HelpCircle
}
from
'./HelpCircle'
export
{
default
as
InfoCircle
}
from
'./InfoCircle'
...
...
@@ -13,6 +14,8 @@ export { default as LinkExternal01 } from './LinkExternal01'
export
{
default
as
LinkExternal02
}
from
'./LinkExternal02'
export
{
default
as
Loading02
}
from
'./Loading02'
export
{
default
as
LogOut01
}
from
'./LogOut01'
export
{
default
as
Menu01
}
from
'./Menu01'
export
{
default
as
Pin01
}
from
'./Pin01'
export
{
default
as
Pin02
}
from
'./Pin02'
export
{
default
as
Plus
}
from
'./Plus'
export
{
default
as
SearchLg
}
from
'./SearchLg'
...
...
web/app/components/base/icons/src/vender/solid/shapes/Star06.json
0 → 100644
View file @
51d35926
{
"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"
:
"star-06"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"g"
,
"attributes"
:
{
"id"
:
"Solid"
},
"children"
:
[
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M3.66675 1.33268C3.66675 0.964492 3.36827 0.666016 3.00008 0.666016C2.63189 0.666016 2.33341 0.964492 2.33341 1.33268V2.33268H1.33341C0.965225 2.33268 0.666748 2.63116 0.666748 2.99935C0.666748 3.36754 0.965225 3.66602 1.33341 3.66602H2.33341V4.66602C2.33341 5.0342 2.63189 5.33268 3.00008 5.33268C3.36827 5.33268 3.66675 5.0342 3.66675 4.66602V3.66602H4.66675C5.03494 3.66602 5.33341 3.36754 5.33341 2.99935C5.33341 2.63116 5.03494 2.33268 4.66675 2.33268H3.66675V1.33268Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
},
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M3.66675 11.3327C3.66675 10.9645 3.36827 10.666 3.00008 10.666C2.63189 10.666 2.33341 10.9645 2.33341 11.3327V12.3327H1.33341C0.965225 12.3327 0.666748 12.6312 0.666748 12.9993C0.666748 13.3675 0.965225 13.666 1.33341 13.666H2.33341V14.666C2.33341 15.0342 2.63189 15.3327 3.00008 15.3327C3.36827 15.3327 3.66675 15.0342 3.66675 14.666V13.666H4.66675C5.03494 13.666 5.33341 13.3675 5.33341 12.9993C5.33341 12.6312 5.03494 12.3327 4.66675 12.3327H3.66675V11.3327Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
},
{
"type"
:
"element"
,
"name"
:
"path"
,
"attributes"
:
{
"d"
:
"M9.28898 1.76003C9.18995 1.50257 8.94259 1.33268 8.66675 1.33268C8.3909 1.33268 8.14354 1.50257 8.04452 1.76003L6.8884 4.76594C6.68813 5.28663 6.6252 5.43668 6.53912 5.55774C6.45274 5.67921 6.34661 5.78534 6.22514 5.87172C6.10408 5.9578 5.95403 6.02073 5.43334 6.221L2.42743 7.37712C2.16997 7.47614 2.00008 7.7235 2.00008 7.99935C2.00008 8.2752 2.16997 8.52256 2.42743 8.62158L5.43334 9.7777C5.95403 9.97797 6.10408 10.0409 6.22514 10.127C6.34661 10.2134 6.45274 10.3195 6.53912 10.441C6.6252 10.562 6.68813 10.7121 6.8884 11.2328L8.04452 14.2387C8.14354 14.4961 8.3909 14.666 8.66675 14.666C8.9426 14.666 9.18995 14.4961 9.28898 14.2387L10.4451 11.2328C10.6454 10.7121 10.7083 10.562 10.7944 10.441C10.8808 10.3195 10.9869 10.2134 11.1084 10.127C11.2294 10.0409 11.3795 9.97797 11.9002 9.7777L14.9061 8.62158C15.1635 8.52256 15.3334 8.2752 15.3334 7.99935C15.3334 7.7235 15.1635 7.47614 14.9061 7.37712L11.9002 6.221C11.3795 6.02073 11.2294 5.9578 11.1084 5.87172C10.9869 5.78534 10.8808 5.67921 10.7944 5.55774C10.7083 5.43668 10.6454 5.28663 10.4451 4.76594L9.28898 1.76003Z"
,
"fill"
:
"currentColor"
},
"children"
:
[]
}
]
}
]
}
]
},
"name"
:
"Star06"
}
\ No newline at end of file
web/app/components/base/icons/src/vender/solid/shapes/Star06.tsx
0 → 100644
View file @
51d35926
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import
*
as
React
from
'react'
import
data
from
'./Star06.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
=
'Star06'
export
default
Icon
web/app/components/base/icons/src/vender/solid/shapes/index.ts
View file @
51d35926
export
{
default
as
Star04
}
from
'./Star04'
export
{
default
as
Star06
}
from
'./Star06'
web/app/components/explore/index.tsx
View file @
51d35926
...
...
@@ -46,7 +46,7 @@ const Explore: FC<IExploreProps> = ({
}
>
<
Sidebar
controlUpdateInstalledApps=
{
controlUpdateInstalledApps
}
/>
<
div
className=
'grow'
>
<
div
className=
'grow
w-0
'
>
{
children
}
</
div
>
</
ExploreContext
.
Provider
>
...
...
web/app/components/explore/installed-app/index.tsx
View file @
51d35926
...
...
@@ -3,9 +3,9 @@ import type { FC } from 'react'
import
React
from
'react'
import
{
useContext
}
from
'use-context-selector'
import
ExploreContext
from
'@/context/explore-context'
import
ChatApp
from
'@/app/components/share/chat'
import
TextGenerationApp
from
'@/app/components/share/text-generation'
import
Loading
from
'@/app/components/base/loading'
import
ChatWithHistory
from
'@/app/components/base/chat/chat-with-history'
export
type
IInstalledAppProps
=
{
id
:
string
...
...
@@ -29,7 +29,7 @@ const InstalledApp: FC<IInstalledAppProps> = ({
<
div
className=
'h-full py-2 pl-0 pr-2 sm:p-2'
>
{
installedApp
?.
app
.
mode
===
'chat'
?
(
<
Chat
App
isInstalledApp
installedAppInfo=
{
installedApp
}
/>
<
Chat
WithHistory
installedAppInfo=
{
installedApp
}
className=
'rounded-2xl shadow-md overflow-hidden'
/>
)
:
(
<
TextGenerationApp
isInstalledApp
installedAppInfo=
{
installedApp
}
/>
...
...
web/app/components/share/chat/index.tsx
View file @
51d35926
...
...
@@ -853,7 +853,7 @@ const Main: FC<IMainProps> = ({
<
Header
title=
{
siteInfo
.
title
}
icon=
{
siteInfo
.
icon
||
''
}
icon_background=
{
siteInfo
.
icon_background
}
icon_background=
{
siteInfo
.
icon_background
||
''
}
isMobile=
{
isMobile
}
onShowSideBar=
{
showSidebar
}
onCreateNewChat=
{
handleStartChatOnSidebar
}
...
...
web/models/share.ts
View file @
51d35926
...
...
@@ -11,11 +11,11 @@ export type ConversationItem = {
export
type
SiteInfo
=
{
title
:
string
icon
:
string
icon_background
:
string
description
:
string
default_language
:
Locale
prompt_public
:
boolean
icon
?
:
string
icon_background
?
:
string
description
?
:
string
default_language
?
:
Locale
prompt_public
?
:
boolean
copyright
?:
string
privacy_policy
?:
string
}
...
...
@@ -23,3 +23,18 @@ export type SiteInfo = {
export
type
AppMeta
=
{
tool_icons
:
Record
<
string
,
string
>
}
export
type
AppData
=
{
app_id
:
string
can_replace_logo
?:
boolean
custom_config
?:
Record
<
string
,
any
>
enable_site
?:
boolean
end_user_id
?:
string
site
:
SiteInfo
}
export
type
AppConversationData
=
{
data
:
ConversationItem
[]
has_more
:
boolean
limit
:
number
}
web/service/share.ts
View file @
51d35926
...
...
@@ -4,6 +4,13 @@ import {
delPublic
as
del
,
getPublic
as
get
,
patchPublic
as
patch
,
postPublic
as
post
,
ssePost
,
}
from
'./base'
import
type
{
Feedbacktype
}
from
'@/app/components/app/chat/type'
import
type
{
AppConversationData
,
AppData
,
AppMeta
,
ConversationItem
,
}
from
'@/models/share'
import
type
{
ChatConfig
}
from
'@/app/components/base/chat/types'
function
getAction
(
action
:
'get'
|
'post'
|
'del'
|
'patch'
,
isInstalledApp
:
boolean
)
{
switch
(
action
)
{
...
...
@@ -18,7 +25,7 @@ function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boo
}
}
function
getUrl
(
url
:
string
,
isInstalledApp
:
boolean
,
installedAppId
:
string
)
{
export
function
getUrl
(
url
:
string
,
isInstalledApp
:
boolean
,
installedAppId
:
string
)
{
return
isInstalledApp
?
`installed-apps/
${
installedAppId
}
/
${
url
.
startsWith
(
'/'
)
?
url
.
slice
(
1
)
:
url
}
`
:
url
}
...
...
@@ -59,11 +66,11 @@ export const sendCompletionMessage = async (body: Record<string, any>, { onData,
}
export
const
fetchAppInfo
=
async
()
=>
{
return
get
(
'/site'
)
return
get
(
'/site'
)
as
Promise
<
AppData
>
}
export
const
fetchConversations
=
async
(
isInstalledApp
:
boolean
,
installedAppId
=
''
,
last_id
?:
string
,
pinned
?:
boolean
,
limit
?:
number
)
=>
{
return
getAction
(
'get'
,
isInstalledApp
)(
getUrl
(
'conversations'
,
isInstalledApp
,
installedAppId
),
{
params
:
{
...{
limit
:
limit
||
20
},
...(
last_id
?
{
last_id
}
:
{}),
...(
pinned
!==
undefined
?
{
pinned
}
:
{})
}
})
return
getAction
(
'get'
,
isInstalledApp
)(
getUrl
(
'conversations'
,
isInstalledApp
,
installedAppId
),
{
params
:
{
...{
limit
:
limit
||
20
},
...(
last_id
?
{
last_id
}
:
{}),
...(
pinned
!==
undefined
?
{
pinned
}
:
{})
}
})
as
Promise
<
AppConversationData
>
}
export
const
pinConversation
=
async
(
isInstalledApp
:
boolean
,
installedAppId
=
''
,
id
:
string
)
=>
{
...
...
@@ -83,11 +90,11 @@ export const renameConversation = async (isInstalledApp: boolean, installedAppId
}
export
const
generationConversationName
=
async
(
isInstalledApp
:
boolean
,
installedAppId
=
''
,
id
:
string
)
=>
{
return
getAction
(
'post'
,
isInstalledApp
)(
getUrl
(
`conversations/
${
id
}
/name`
,
isInstalledApp
,
installedAppId
),
{
body
:
{
auto_generate
:
true
}
})
return
getAction
(
'post'
,
isInstalledApp
)(
getUrl
(
`conversations/
${
id
}
/name`
,
isInstalledApp
,
installedAppId
),
{
body
:
{
auto_generate
:
true
}
})
as
Promise
<
ConversationItem
>
}
export
const
fetchChatList
=
async
(
conversationId
:
string
,
isInstalledApp
:
boolean
,
installedAppId
=
''
)
=>
{
return
getAction
(
'get'
,
isInstalledApp
)(
getUrl
(
'messages'
,
isInstalledApp
,
installedAppId
),
{
params
:
{
conversation_id
:
conversationId
,
limit
:
20
,
last_id
:
''
}
})
return
getAction
(
'get'
,
isInstalledApp
)(
getUrl
(
'messages'
,
isInstalledApp
,
installedAppId
),
{
params
:
{
conversation_id
:
conversationId
,
limit
:
20
,
last_id
:
''
}
})
as
any
}
// Abandoned API interface
...
...
@@ -97,11 +104,11 @@ export const fetchChatList = async (conversationId: string, isInstalledApp: bool
// init value. wait for server update
export
const
fetchAppParams
=
async
(
isInstalledApp
:
boolean
,
installedAppId
=
''
)
=>
{
return
(
getAction
(
'get'
,
isInstalledApp
))(
getUrl
(
'parameters'
,
isInstalledApp
,
installedAppId
))
return
(
getAction
(
'get'
,
isInstalledApp
))(
getUrl
(
'parameters'
,
isInstalledApp
,
installedAppId
))
as
Promise
<
ChatConfig
>
}
export
const
fetchAppMeta
=
async
(
isInstalledApp
:
boolean
,
installedAppId
=
''
)
=>
{
return
(
getAction
(
'get'
,
isInstalledApp
))(
getUrl
(
'meta'
,
isInstalledApp
,
installedAppId
))
return
(
getAction
(
'get'
,
isInstalledApp
))(
getUrl
(
'meta'
,
isInstalledApp
,
installedAppId
))
as
Promise
<
AppMeta
>
}
export
const
updateFeedback
=
async
({
url
,
body
}:
{
url
:
string
;
body
:
Feedbacktype
},
isInstalledApp
:
boolean
,
installedAppId
=
''
)
=>
{
...
...
web/types/app.ts
View file @
51d35926
...
...
@@ -70,6 +70,7 @@ export type PromptVariable = {
}
export
type
TextTypeFormItem
=
{
default
:
string
label
:
string
variable
:
string
required
:
boolean
...
...
@@ -77,11 +78,19 @@ export type TextTypeFormItem = {
}
export
type
SelectTypeFormItem
=
{
default
:
string
label
:
string
variable
:
string
required
:
boolean
options
:
string
[]
}
export
type
ParagraphTypeFormItem
=
{
default
:
string
label
:
string
variable
:
string
required
:
boolean
}
/**
* User Input Form Item
*/
...
...
@@ -89,6 +98,8 @@ export type UserInputFormItem = {
'text-input'
:
TextTypeFormItem
}
|
{
'select'
:
SelectTypeFormItem
}
|
{
'paragraph'
:
TextTypeFormItem
}
export
type
AgentTool
=
{
...
...
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