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
b4cf1d34
Commit
b4cf1d34
authored
Jul 18, 2023
by
Joel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: can show chat
parent
9098d099
Changes
7
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
824 additions
and
56 deletions
+824
-56
use-conversation.ts
...mponents/explore/universal-chat/hooks/use-conversation.ts
+70
-0
index.tsx
web/app/components/explore/universal-chat/index.tsx
+601
-5
style.module.css
web/app/components/explore/universal-chat/style.module.css
+3
-0
index.tsx
web/app/components/share/chat/sidebar/index.tsx
+18
-5
index.tsx
web/app/components/share/chat/sidebar/list/index.tsx
+9
-1
index.tsx
web/app/components/share/chat/welcome/index.tsx
+49
-45
universal-chat.ts
web/service/universal-chat.ts
+74
-0
No files found.
web/app/components/explore/universal-chat/hooks/use-conversation.ts
0 → 100644
View file @
b4cf1d34
import
{
useState
}
from
'react'
import
produce
from
'immer'
import
type
{
ConversationItem
}
from
'@/models/share'
const
storageConversationIdKey
=
'conversationIdInfo'
type
ConversationInfoType
=
Omit
<
ConversationItem
,
'inputs'
|
'id'
>
function
useConversation
()
{
const
[
conversationList
,
setConversationList
]
=
useState
<
ConversationItem
[]
>
([])
const
[
pinnedConversationList
,
setPinnedConversationList
]
=
useState
<
ConversationItem
[]
>
([])
const
[
currConversationId
,
doSetCurrConversationId
]
=
useState
<
string
>
(
'-1'
)
// when set conversation id, we do not have set appId
const
setCurrConversationId
=
(
id
:
string
,
appId
:
string
,
isSetToLocalStroge
=
true
,
newConversationName
=
''
)
=>
{
doSetCurrConversationId
(
id
)
if
(
isSetToLocalStroge
&&
id
!==
'-1'
)
{
// conversationIdInfo: {[appId1]: conversationId1, [appId2]: conversationId2}
const
conversationIdInfo
=
globalThis
.
localStorage
?.
getItem
(
storageConversationIdKey
)
?
JSON
.
parse
(
globalThis
.
localStorage
?.
getItem
(
storageConversationIdKey
)
||
''
)
:
{}
conversationIdInfo
[
appId
]
=
id
globalThis
.
localStorage
?.
setItem
(
storageConversationIdKey
,
JSON
.
stringify
(
conversationIdInfo
))
}
}
const
getConversationIdFromStorage
=
(
appId
:
string
)
=>
{
const
conversationIdInfo
=
globalThis
.
localStorage
?.
getItem
(
storageConversationIdKey
)
?
JSON
.
parse
(
globalThis
.
localStorage
?.
getItem
(
storageConversationIdKey
)
||
''
)
:
{}
const
id
=
conversationIdInfo
[
appId
]
return
id
}
const
isNewConversation
=
currConversationId
===
'-1'
// input can be updated by user
const
[
newConversationInputs
,
setNewConversationInputs
]
=
useState
<
Record
<
string
,
any
>
|
null
>
(
null
)
const
resetNewConversationInputs
=
()
=>
{
if
(
!
newConversationInputs
)
return
setNewConversationInputs
(
produce
(
newConversationInputs
,
(
draft
)
=>
{
Object
.
keys
(
draft
).
forEach
((
key
)
=>
{
draft
[
key
]
=
''
})
}))
}
const
[
existConversationInputs
,
setExistConversationInputs
]
=
useState
<
Record
<
string
,
any
>
|
null
>
(
null
)
const
currInputs
=
isNewConversation
?
newConversationInputs
:
existConversationInputs
const
setCurrInputs
=
isNewConversation
?
setNewConversationInputs
:
setExistConversationInputs
// info is muted
const
[
newConversationInfo
,
setNewConversationInfo
]
=
useState
<
ConversationInfoType
|
null
>
(
null
)
const
[
existConversationInfo
,
setExistConversationInfo
]
=
useState
<
ConversationInfoType
|
null
>
(
null
)
const
currConversationInfo
=
isNewConversation
?
newConversationInfo
:
existConversationInfo
return
{
conversationList
,
setConversationList
,
pinnedConversationList
,
setPinnedConversationList
,
currConversationId
,
setCurrConversationId
,
getConversationIdFromStorage
,
isNewConversation
,
currInputs
,
newConversationInputs
,
existConversationInputs
,
resetNewConversationInputs
,
setCurrInputs
,
currConversationInfo
,
setNewConversationInfo
,
setExistConversationInfo
,
}
}
export
default
useConversation
web/app/components/explore/universal-chat/index.tsx
View file @
b4cf1d34
This diff is collapsed.
Click to expand it.
web/app/components/explore/universal-chat/style.module.css
0 → 100644
View file @
b4cf1d34
.installedApp
{
height
:
calc
(
100vh
-
74px
);
}
\ No newline at end of file
web/app/components/share/chat/sidebar/index.tsx
View file @
b4cf1d34
...
...
@@ -11,6 +11,7 @@ import AppInfo from '@/app/components/share/chat/sidebar/app-info'
// import Card from './card'
import
type
{
ConversationItem
,
SiteInfo
}
from
'@/models/share'
import
{
fetchConversations
}
from
'@/service/share'
import
{
fetchConversations
as
fetchUniversalConversations
}
from
'@/service/universal-chat'
export
type
ISidebarProps
=
{
copyRight
:
string
...
...
@@ -22,6 +23,7 @@ export type ISidebarProps = {
isClearPinnedConversationList
:
boolean
isInstalledApp
:
boolean
installedAppId
?:
string
isUniversalChat
?:
boolean
siteInfo
:
SiteInfo
onMoreLoaded
:
(
res
:
{
data
:
ConversationItem
[];
has_more
:
boolean
})
=>
void
onPinnedMoreLoaded
:
(
res
:
{
data
:
ConversationItem
[];
has_more
:
boolean
})
=>
void
...
...
@@ -43,6 +45,7 @@ const Sidebar: FC<ISidebarProps> = ({
isClearPinnedConversationList
,
isInstalledApp
,
installedAppId
,
isUniversalChat
,
siteInfo
,
onMoreLoaded
,
onPinnedMoreLoaded
,
...
...
@@ -57,8 +60,14 @@ const Sidebar: FC<ISidebarProps> = ({
const
[
hasPinned
,
setHasPinned
]
=
useState
(
false
)
const
checkHasPinned
=
async
()
=>
{
const
{
data
}:
any
=
await
fetchConversations
(
isInstalledApp
,
installedAppId
,
undefined
,
true
)
setHasPinned
(
data
.
length
>
0
)
let
res
:
any
if
(
isUniversalChat
)
res
=
await
fetchUniversalConversations
(
undefined
,
true
)
else
res
=
await
fetchConversations
(
isInstalledApp
,
installedAppId
,
undefined
,
true
)
setHasPinned
(
res
.
data
.
length
>
0
)
}
useEffect
(()
=>
{
...
...
@@ -109,6 +118,7 @@ const Sidebar: FC<ISidebarProps> = ({
isClearConversationList=
{
isClearPinnedConversationList
}
isInstalledApp=
{
isInstalledApp
}
installedAppId=
{
installedAppId
}
isUniversalChat=
{
isUniversalChat
}
onMoreLoaded=
{
onPinnedMoreLoaded
}
isNoMore=
{
isPinnedNoMore
}
isPinned=
{
true
}
...
...
@@ -131,6 +141,7 @@ const Sidebar: FC<ISidebarProps> = ({
isClearConversationList=
{
isClearConversationList
}
isInstalledApp=
{
isInstalledApp
}
installedAppId=
{
installedAppId
}
isUniversalChat=
{
isUniversalChat
}
onMoreLoaded=
{
onMoreLoaded
}
isNoMore=
{
isNoMore
}
isPinned=
{
false
}
...
...
@@ -141,9 +152,11 @@ const Sidebar: FC<ISidebarProps> = ({
</
div
>
</
div
>
{
!
isUniversalChat
&&
(
<
div
className=
"flex flex-shrink-0 pr-4 pb-4 pl-4"
>
<
div
className=
"text-gray-400 font-normal text-xs"
>
©
{
copyRight
}
{
(
new
Date
()).
getFullYear
()
}
</
div
>
</
div
>
)
}
</
div
>
)
}
...
...
web/app/components/share/chat/sidebar/list/index.tsx
View file @
b4cf1d34
...
...
@@ -10,6 +10,7 @@ import cn from 'classnames'
import
s
from
'./style.module.css'
import
type
{
ConversationItem
}
from
'@/models/share'
import
{
fetchConversations
}
from
'@/service/share'
import
{
fetchConversations
as
fetchUniversalConversations
}
from
'@/service/universal-chat'
import
ItemOperation
from
'@/app/components/explore/item-operation'
export
type
IListProps
=
{
...
...
@@ -19,6 +20,7 @@ export type IListProps = {
list
:
ConversationItem
[]
isClearConversationList
:
boolean
isInstalledApp
:
boolean
isUniversalChat
?:
boolean
installedAppId
?:
string
onMoreLoaded
:
(
res
:
{
data
:
ConversationItem
[];
has_more
:
boolean
})
=>
void
isNoMore
:
boolean
...
...
@@ -35,6 +37,7 @@ const List: FC<IListProps> = ({
list
,
isClearConversationList
,
isInstalledApp
,
isUniversalChat
,
installedAppId
,
onMoreLoaded
,
isNoMore
,
...
...
@@ -49,7 +52,12 @@ const List: FC<IListProps> = ({
async
()
=>
{
if
(
!
isNoMore
)
{
const
lastId
=
!
isClearConversationList
?
list
[
list
.
length
-
1
]?.
id
:
undefined
const
{
data
:
conversations
,
has_more
}:
any
=
await
fetchConversations
(
isInstalledApp
,
installedAppId
,
lastId
,
isPinned
)
let
res
:
any
if
(
isUniversalChat
)
res
=
await
fetchUniversalConversations
(
lastId
,
isPinned
)
else
res
=
await
fetchConversations
(
isInstalledApp
,
installedAppId
,
lastId
,
isPinned
)
const
{
data
:
conversations
,
has_more
}:
any
=
res
onMoreLoaded
({
data
:
conversations
,
has_more
})
}
return
{
list
:
[]
}
...
...
web/app/components/share/chat/welcome/index.tsx
View file @
b4cf1d34
'use client'
import
type
{
FC
}
from
'react'
import
React
,
{
use
State
,
useEffect
}
from
'react'
import
React
,
{
use
Effect
,
useState
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useContext
}
from
'use-context-selector'
import
TemplateVarPanel
,
{
PanelTitle
,
VarOpBtnGroup
}
from
'../value-panel'
import
s
from
'./style.module.css'
import
{
AppInfo
,
ChatBtn
,
EditBtn
,
FootLogo
,
PromptTemplate
}
from
'./massive-component'
import
type
{
SiteInfo
}
from
'@/models/share'
import
type
{
PromptConfig
}
from
'@/models/debug'
import
{
ToastContext
}
from
'@/app/components/base/toast'
import
Select
from
'@/app/components/base/select'
import
{
DEFAULT_VALUE_MAX_LEN
}
from
'@/config'
import
TemplateVarPanel
,
{
PanelTitle
,
VarOpBtnGroup
}
from
'../value-panel'
import
{
AppInfo
,
PromptTemplate
,
ChatBtn
,
EditBtn
,
FootLogo
}
from
'./massive-component'
// regex to match the {{}} and replace it with a span
const
regex
=
/
\{\{([^
}
]
+
)\}\}
/g
...
...
@@ -25,7 +25,7 @@ export type IWelcomeProps = {
canEidtInpus
:
boolean
savedInputs
:
Record
<
string
,
any
>
onInputsChange
:
(
inputs
:
Record
<
string
,
any
>
)
=>
void
plan
:
string
plan
?
:
string
}
const
Welcome
:
FC
<
IWelcomeProps
>
=
({
...
...
@@ -38,15 +38,15 @@ const Welcome: FC<IWelcomeProps> = ({
onStartChat
,
canEidtInpus
,
savedInputs
,
onInputsChange
onInputsChange
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
hasVar
=
promptConfig
.
prompt_variables
.
length
>
0
const
[
isFold
,
setIsFold
]
=
useState
<
boolean
>
(
true
)
const
[
inputs
,
setInputs
]
=
useState
<
Record
<
string
,
any
>>
((()
=>
{
if
(
hasSetInputs
)
{
if
(
hasSetInputs
)
return
savedInputs
}
const
res
:
Record
<
string
,
any
>
=
{}
if
(
promptConfig
)
{
promptConfig
.
prompt_variables
.
forEach
((
item
)
=>
{
...
...
@@ -65,7 +65,8 @@ const Welcome: FC<IWelcomeProps> = ({
})
}
setInputs
(
res
)
}
else
{
}
else
{
setInputs
(
savedInputs
)
}
},
[
savedInputs
])
...
...
@@ -98,7 +99,8 @@ const Welcome: FC<IWelcomeProps> = ({
{
promptConfig
.
prompt_variables
.
map
(
item
=>
(
<
div
className=
'tablet:flex tablet:!h-9 mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm'
key=
{
item
.
key
}
>
<
label
className=
{
`flex-shrink-0 flex items-center mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`
}
>
{
item
.
name
}
</
label
>
{
item
.
type
===
'select'
?
(
{
item
.
type
===
'select'
?
(
<
Select
className=
'w-full'
defaultValue=
{
inputs
?.[
item
.
key
]
}
...
...
@@ -107,12 +109,13 @@ const Welcome: FC<IWelcomeProps> = ({
allowSearch=
{
false
}
bgClassName=
'bg-gray-50'
/>
)
:
(
)
:
(
<
input
placeholder=
{
`${item.name}${!item.required ? `
(
$
{
t
(
'appDebug.variableTable.optional'
)})
` : ''}`
}
value=
{
inputs
?.[
item
.
key
]
||
''
}
onChange=
{
(
e
)
=>
{
setInputs
({
...
inputs
,
[
item
.
key
]:
e
.
target
.
value
})
}
}
className=
{
`w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50`
}
className=
{
'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'
}
maxLength=
{
item
.
max_length
||
DEFAULT_VALUE_MAX_LEN
}
/>
)
}
...
...
@@ -124,21 +127,20 @@ const Welcome: FC<IWelcomeProps> = ({
const canChat = () =
>
{
const
prompt_variables
=
promptConfig
?.
prompt_variables
if
(
!
inputs
||
!
prompt_variables
||
prompt_variables
?.
length
===
0
)
{
if
(
!
inputs
||
!
prompt_variables
||
prompt_variables
?.
length
===
0
)
return
true
}
let
hasEmptyInput
=
false
const
requiredVars
=
prompt_variables
?.
filter
(({
key
,
name
,
required
})
=>
{
const
res
=
(
!
key
||
!
key
.
trim
())
||
(
!
name
||
!
name
.
trim
())
||
(
required
||
required
===
undefined
||
required
===
null
)
return
res
})
||
[]
// compatible with old version
requiredVars
.
forEach
(({
key
})
=>
{
if
(
hasEmptyInput
)
{
if
(
hasEmptyInput
)
return
}
if
(
!
inputs
?.[
key
])
{
if
(
!
inputs
?.[
key
])
hasEmptyInput
=
true
}
})
if
(
hasEmptyInput
)
{
...
...
@@ -149,9 +151,9 @@ const Welcome: FC<IWelcomeProps> = ({
}
const handleChat = () =
>
{
if
(
!
canChat
())
{
if
(
!
canChat
())
return
}
onStartChat
(
inputs
)
}
...
...
@@ -211,9 +213,9 @@ const Welcome: FC<IWelcomeProps> = ({
return
(
<
VarOpBtnGroup
onConfirm=
{
()
=>
{
if
(
!
canChat
())
{
if
(
!
canChat
())
return
}
onInputsChange
(
inputs
)
setIsFold
(
true
)
}
}
...
...
@@ -269,9 +271,9 @@ const Welcome: FC<IWelcomeProps> = ({
}
const renderHasSetInputsPrivate = () =
>
{
if
(
!
canEidtInpus
||
!
hasVar
)
{
if
(
!
canEidtInpus
||
!
hasVar
)
return
null
}
return
(
<
TemplateVarPanel
isFold=
{
isFold
}
...
...
@@ -293,9 +295,9 @@ const Welcome: FC<IWelcomeProps> = ({
}
const renderHasSetInputs = () =
>
{
if
(
!
isPublicVersion
&&
!
canEidtInpus
||
!
hasVar
)
{
if
(
(
!
isPublicVersion
&&
!
canEidtInpus
)
||
!
hasVar
)
return
null
}
return
(
<
div
className=
'pt-[88px] mb-5'
...
...
@@ -312,9 +314,11 @@ const Welcome: FC<IWelcomeProps> = ({
{
!
hasSetInputs
&&
(
<
div
className=
'mobile:pt-[72px] tablet:pt-[128px] pc:pt-[200px]'
>
{
hasVar
?
(
{
hasVar
?
(
renderVarPanel
()
)
:
(
)
:
(
renderNoVarPanel
()
)
}
</
div
>
...
...
web/service/universal-chat.ts
0 → 100644
View file @
b4cf1d34
import
type
{
IOnCompleted
,
IOnData
,
IOnError
}
from
'./base'
import
{
del
,
get
,
patch
,
post
,
ssePost
,
}
from
'./base'
import
type
{
Feedbacktype
}
from
'@/app/components/app/chat'
const
baseUrl
=
'universal-chat'
function
getUrl
(
url
:
string
)
{
return
`
${
baseUrl
}
/
${
url
.
startsWith
(
'/'
)
?
url
.
slice
(
1
)
:
url
}
`
}
export
const
sendChatMessage
=
async
(
body
:
Record
<
string
,
any
>
,
{
onData
,
onCompleted
,
onError
,
getAbortController
}:
{
onData
:
IOnData
onCompleted
:
IOnCompleted
onError
:
IOnError
getAbortController
?:
(
abortController
:
AbortController
)
=>
void
})
=>
{
return
ssePost
(
getUrl
(
'chat-messages'
),
{
body
:
{
...
body
,
response_mode
:
'streaming'
,
},
},
{
onData
,
onCompleted
,
onError
,
getAbortController
})
}
export
const
stopChatMessageResponding
=
async
(
appId
:
string
,
taskId
:
string
)
=>
{
return
post
(
getUrl
(
`messages/
${
taskId
}
/stop`
))
}
export
const
fetchConversations
=
async
(
last_id
?:
string
,
pinned
?:
boolean
,
limit
?:
number
)
=>
{
return
get
(
getUrl
(
'conversations'
),
{
params
:
{
...{
limit
:
limit
||
20
},
...(
last_id
?
{
last_id
}
:
{}),
...(
pinned
!==
undefined
?
{
pinned
}
:
{})
}
})
}
export
const
pinConversation
=
async
(
id
:
string
)
=>
{
return
patch
(
getUrl
(
`conversations/
${
id
}
/pin`
))
}
export
const
unpinConversation
=
async
(
id
:
string
)
=>
{
return
patch
(
getUrl
(
`conversations/
${
id
}
/unpin`
))
}
export
const
delConversation
=
async
(
id
:
string
)
=>
{
return
del
(
getUrl
(
`conversations/
${
id
}
`
))
}
export
const
fetchChatList
=
async
(
conversationId
:
string
)
=>
{
return
get
(
getUrl
(
'messages'
),
{
params
:
{
conversation_id
:
conversationId
,
limit
:
20
,
last_id
:
''
}
})
}
// init value. wait for server update
export
const
fetchAppParams
=
async
()
=>
{
return
get
(
getUrl
(
'parameters'
))
}
export
const
updateFeedback
=
async
({
url
,
body
}:
{
url
:
string
;
body
:
Feedbacktype
})
=>
{
return
post
(
getUrl
(
url
),
{
body
})
}
export
const
fetchMoreLikeThis
=
async
(
messageId
:
string
)
=>
{
return
get
(
getUrl
(
`/messages/
${
messageId
}
/more-like-this`
),
{
params
:
{
response_mode
:
'blocking'
,
},
})
}
export
const
fetchSuggestedQuestions
=
(
messageId
:
string
)
=>
{
return
get
(
getUrl
(
`/messages/
${
messageId
}
/suggested-questions`
))
}
export
const
audioToText
=
(
url
:
string
,
body
:
FormData
)
=>
{
return
post
(
url
,
{
body
},
{
bodyStringify
:
false
,
deleteContentType
:
true
})
as
Promise
<
{
text
:
string
}
>
}
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