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
a0b3891c
Commit
a0b3891c
authored
Jul 01, 2023
by
Gillian97
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: chatbot init
parent
345399a3
Changes
23
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
1731 additions
and
24 deletions
+1731
-24
page.tsx
web/app/(shareLayout)/chatbot/[token]/page.tsx
+13
-0
index.tsx
web/app/components/app/chat/index.tsx
+2
-2
index.tsx
web/app/components/app/overview/embedded/index.tsx
+16
-5
index.tsx
web/app/components/share/chatbot/config-scence/index.tsx
+13
-0
use-conversation.ts
web/app/components/share/chatbot/hooks/use-conversation.ts
+70
-0
index.tsx
web/app/components/share/chatbot/index.tsx
+636
-0
index.tsx
web/app/components/share/chatbot/sidebar/app-info/index.tsx
+28
-0
card.module.css
web/app/components/share/chatbot/sidebar/card.module.css
+3
-0
card.tsx
web/app/components/share/chatbot/sidebar/card.tsx
+19
-0
index.tsx
web/app/components/share/chatbot/sidebar/index.tsx
+151
-0
index.tsx
web/app/components/share/chatbot/sidebar/list/index.tsx
+115
-0
style.module.css
...pp/components/share/chatbot/sidebar/list/style.module.css
+7
-0
style.module.css
web/app/components/share/chatbot/style.module.css
+3
-0
index.tsx
web/app/components/share/chatbot/value-panel/index.tsx
+79
-0
style.module.css
...app/components/share/chatbot/value-panel/style.module.css
+3
-0
logo.png
web/app/components/share/chatbot/welcome/icons/logo.png
+0
-0
index.tsx
web/app/components/share/chatbot/welcome/index.tsx
+356
-0
massive-component.tsx
...pp/components/share/chatbot/welcome/massive-component.tsx
+74
-0
style.module.css
web/app/components/share/chatbot/welcome/style.module.css
+29
-0
header.tsx
web/app/components/share/header.tsx
+23
-17
app-overview.en.ts
web/i18n/lang/app-overview.en.ts
+2
-0
app-overview.zh.ts
web/i18n/lang/app-overview.zh.ts
+2
-0
embed.js
web/public/embed.js
+87
-0
No files found.
web/app/(shareLayout)/chatbot/[token]/page.tsx
0 → 100644
View file @
a0b3891c
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
type
{
IMainProps
}
from
'@/app/components/share/chat'
import
Main
from
'@/app/components/share/chatbot'
const
Chatbot
:
FC
<
IMainProps
>
=
()
=>
{
return
(
<
Main
/>
)
}
export
default
React
.
memo
(
Chatbot
)
web/app/components/app/chat/index.tsx
View file @
a0b3891c
...
...
@@ -466,7 +466,7 @@ const Chat: FC<IChatProps> = ({
}
}
const
han
e
leKeyDown
=
(
e
:
any
)
=>
{
const
han
d
leKeyDown
=
(
e
:
any
)
=>
{
isUseInputMethod
.
current
=
e
.
nativeEvent
.
isComposing
if
(
e
.
code
===
'Enter'
&&
!
e
.
shiftKey
)
{
setQuery
(
query
.
replace
(
/
\n
$/
,
''
))
...
...
@@ -557,7 +557,7 @@ const Chat: FC<IChatProps> = ({
value=
{
query
}
onChange=
{
handleContentChange
}
onKeyUp=
{
handleKeyUp
}
onKeyDown=
{
han
e
leKeyDown
}
onKeyDown=
{
han
d
leKeyDown
}
minHeight=
{
48
}
autoFocus
controlFocus=
{
controlFocus
}
...
...
web/app/components/app/overview/embedded/index.tsx
View file @
a0b3891c
...
...
@@ -5,6 +5,9 @@ import style from './style.module.css'
import
Modal
from
'@/app/components/base/modal'
import
useCopyToClipboard
from
'@/hooks/use-copy-to-clipboard'
import
copyStyle
from
'@/app/components/app/chat/copy-btn/style.module.css'
import
Tooltip
from
'@/app/components/base/tooltip'
const
isDevelopment
=
process
.
env
.
NODE_ENV
===
'development'
type
Props
=
{
isShow
:
boolean
...
...
@@ -17,14 +20,17 @@ const OPTION_MAP = {
iframe
:
{
getContent
:
(
url
:
string
,
token
:
string
)
=>
`<iframe
src="
${
url
}
/c
ompletion
/
${
token
}
"
src="
${
url
}
/c
hatbot
/
${
token
}
"
style="width: 100%; height: 100%; min-height: 700px"
frameborder="0" >
</iframe>`
,
},
scripts
:
{
getContent
:
(
url
:
string
,
token
:
string
)
=>
`<script
`<script>
window.difyChatbotConfig = { token:
${
token
}${
isDevelopment
?
', isDev: true'
:
''
}
}
</script>
<script
src="
${
url
}
/embed.min.js"
id="
${
token
}
"
defer>
...
...
@@ -78,9 +84,14 @@ const Embedded = ({ isShow, onClose, appBaseUrl, accessToken }: Props) => {
{
t
(
`${prefixEmbedded}.${option}`
)
}
</
div
>
<
div
className=
"p-2 rounded-lg justify-center items-center gap-1 flex"
>
<
div
className=
"w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg"
>
<
div
onClick=
{
onClickCopy
}
className=
{
`w-full h-full ${copyStyle.copyIcon} ${isCopied[option] ? copyStyle.copied : ''}`
}
></
div
>
</
div
>
<
Tooltip
selector=
{
'code-copy-feedback'
}
content=
{
(
isCopied
[
option
]
?
t
(
`${prefixEmbedded}.copied`
)
:
t
(
`${prefixEmbedded}.copy`
))
||
''
}
>
<
div
className=
"w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg"
>
<
div
onClick=
{
onClickCopy
}
className=
{
`w-full h-full ${copyStyle.copyIcon} ${isCopied[option] ? copyStyle.copied : ''}`
}
></
div
>
</
div
>
</
Tooltip
>
</
div
>
</
div
>
<
div
className=
"self-stretch p-3 justify-start items-start gap-2 inline-flex"
>
...
...
web/app/components/share/chatbot/config-scence/index.tsx
0 → 100644
View file @
a0b3891c
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
type
{
IWelcomeProps
}
from
'../welcome'
import
Welcome
from
'../welcome'
const
ConfigScene
:
FC
<
IWelcomeProps
>
=
(
props
)
=>
{
return
(
<
div
className=
'mb-5 antialiased font-sans shrink-0'
>
<
Welcome
{
...
props
}
/>
</
div
>
)
}
export
default
React
.
memo
(
ConfigScene
)
web/app/components/share/chatbot/hooks/use-conversation.ts
0 → 100644
View file @
a0b3891c
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/share/chatbot/index.tsx
0 → 100644
View file @
a0b3891c
This diff is collapsed.
Click to expand it.
web/app/components/share/chatbot/sidebar/app-info/index.tsx
0 → 100644
View file @
a0b3891c
'use client'
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
cn
from
'classnames'
import
{
appDefaultIconBackground
}
from
'@/config/index'
import
AppIcon
from
'@/app/components/base/app-icon'
export
type
IAppInfoProps
=
{
className
?:
string
icon
:
string
icon_background
?:
string
name
:
string
}
const
AppInfo
:
FC
<
IAppInfoProps
>
=
({
className
,
icon
,
icon_background
,
name
,
})
=>
{
return
(
<
div
className=
{
cn
(
className
,
'flex items-center space-x-3'
)
}
>
<
AppIcon
size=
"small"
icon=
{
icon
}
background=
{
icon_background
||
appDefaultIconBackground
}
/>
<
div
className=
'w-0 grow text-sm font-semibold text-gray-800 overflow-hidden text-ellipsis whitespace-nowrap'
>
{
name
}
</
div
>
</
div
>
)
}
export
default
React
.
memo
(
AppInfo
)
web/app/components/share/chatbot/sidebar/card.module.css
0 → 100644
View file @
a0b3891c
.card
:hover
{
background
:
linear-gradient
(
0deg
,
rgba
(
235
,
245
,
255
,
0.4
),
rgba
(
235
,
245
,
255
,
0.4
)),
#FFFFFF
;
}
\ No newline at end of file
web/app/components/share/chatbot/sidebar/card.tsx
0 → 100644
View file @
a0b3891c
import
React
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
s
from
'./card.module.css'
type
PropType
=
{
children
:
React
.
ReactNode
text
?:
string
}
function
Card
({
children
,
text
}:
PropType
)
{
const
{
t
}
=
useTranslation
()
return
(
<
div
className=
{
`${s.card} box-border w-full flex flex-col items-start px-4 py-3 rounded-lg border-solid border border-gray-200 cursor-pointer hover:border-primary-300`
}
>
<
div
className=
'text-gray-400 font-medium text-xs mb-2'
>
{
text
??
t
(
'share.chat.powerBy'
)
}
</
div
>
{
children
}
</
div
>
)
}
export
default
Card
web/app/components/share/chatbot/sidebar/index.tsx
0 → 100644
View file @
a0b3891c
import
React
,
{
useEffect
,
useState
}
from
'react'
import
type
{
FC
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
{
PencilSquareIcon
,
}
from
'@heroicons/react/24/outline'
import
cn
from
'classnames'
import
Button
from
'../../../base/button'
import
List
from
'./list'
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'
export
type
ISidebarProps
=
{
copyRight
:
string
currentId
:
string
onCurrentIdChange
:
(
id
:
string
)
=>
void
list
:
ConversationItem
[]
isClearConversationList
:
boolean
pinnedList
:
ConversationItem
[]
isClearPinnedConversationList
:
boolean
isInstalledApp
:
boolean
installedAppId
?:
string
siteInfo
:
SiteInfo
onMoreLoaded
:
(
res
:
{
data
:
ConversationItem
[];
has_more
:
boolean
})
=>
void
onPinnedMoreLoaded
:
(
res
:
{
data
:
ConversationItem
[];
has_more
:
boolean
})
=>
void
isNoMore
:
boolean
isPinnedNoMore
:
boolean
onPin
:
(
id
:
string
)
=>
void
onUnpin
:
(
id
:
string
)
=>
void
controlUpdateList
:
number
onDelete
:
(
id
:
string
)
=>
void
}
const
Sidebar
:
FC
<
ISidebarProps
>
=
({
copyRight
,
currentId
,
onCurrentIdChange
,
list
,
isClearConversationList
,
pinnedList
,
isClearPinnedConversationList
,
isInstalledApp
,
installedAppId
,
siteInfo
,
onMoreLoaded
,
onPinnedMoreLoaded
,
isNoMore
,
isPinnedNoMore
,
onPin
,
onUnpin
,
controlUpdateList
,
onDelete
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
[
hasPinned
,
setHasPinned
]
=
useState
(
false
)
const
checkHasPinned
=
async
()
=>
{
const
{
data
}:
any
=
await
fetchConversations
(
isInstalledApp
,
installedAppId
,
undefined
,
true
)
setHasPinned
(
data
.
length
>
0
)
}
useEffect
(()
=>
{
checkHasPinned
()
},
[])
useEffect
(()
=>
{
if
(
controlUpdateList
!==
0
)
checkHasPinned
()
},
[
controlUpdateList
])
const
maxListHeight
=
isInstalledApp
?
'max-h-[30vh]'
:
'max-h-[40vh]'
return
(
<
div
className=
{
cn
(
isInstalledApp
?
'tablet:h-[calc(100vh_-_74px)]'
:
'tablet:h-[calc(100vh_-_3rem)]'
,
'shrink-0 flex flex-col bg-white pc:w-[244px] tablet:w-[192px] mobile:w-[240px] border-r border-gray-200 mobile:h-screen'
,
)
}
>
{
isInstalledApp
&&
(
<
AppInfo
className=
'my-4 px-4'
name=
{
siteInfo
.
title
||
''
}
icon=
{
siteInfo
.
icon
||
''
}
icon_background=
{
siteInfo
.
icon_background
}
/>
)
}
<
div
className=
"flex flex-shrink-0 p-4 !pb-0"
>
<
Button
onClick=
{
()
=>
{
onCurrentIdChange
(
'-1'
)
}
}
className=
"group block w-full flex-shrink-0 !justify-start !h-9 text-primary-600 items-center text-sm"
>
<
PencilSquareIcon
className=
"mr-2 h-4 w-4"
/>
{
t
(
'share.chat.newChat'
)
}
</
Button
>
</
div
>
<
div
className=
{
'flex-grow flex flex-col h-0 overflow-y-auto overflow-x-hidden'
}
>
{
/* pinned list */
}
{
hasPinned
&&
(
<
div
className=
{
cn
(
'mt-4 px-4'
,
list
.
length
===
0
&&
'flex flex-col flex-grow'
)
}
>
<
div
className=
'mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'
>
{
t
(
'share.chat.pinnedTitle'
)
}
</
div
>
<
List
className=
{
cn
(
list
.
length
>
0
?
maxListHeight
:
'flex-grow'
)
}
currentId=
{
currentId
}
onCurrentIdChange=
{
onCurrentIdChange
}
list=
{
pinnedList
}
isClearConversationList=
{
isClearPinnedConversationList
}
isInstalledApp=
{
isInstalledApp
}
installedAppId=
{
installedAppId
}
onMoreLoaded=
{
onPinnedMoreLoaded
}
isNoMore=
{
isPinnedNoMore
}
isPinned=
{
true
}
onPinChanged=
{
id
=>
onUnpin
(
id
)
}
controlUpdate=
{
controlUpdateList
+
1
}
onDelete=
{
onDelete
}
/>
</
div
>
)
}
{
/* unpinned list */
}
<
div
className=
{
cn
(
'mt-4 px-4'
,
!
hasPinned
&&
'flex flex-col flex-grow'
)
}
>
{
(
hasPinned
&&
list
.
length
>
0
)
&&
(
<
div
className=
'mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'
>
{
t
(
'share.chat.unpinnedTitle'
)
}
</
div
>
)
}
<
List
className=
{
cn
(
hasPinned
?
maxListHeight
:
'flex-grow'
)
}
currentId=
{
currentId
}
onCurrentIdChange=
{
onCurrentIdChange
}
list=
{
list
}
isClearConversationList=
{
isClearConversationList
}
isInstalledApp=
{
isInstalledApp
}
installedAppId=
{
installedAppId
}
onMoreLoaded=
{
onMoreLoaded
}
isNoMore=
{
isNoMore
}
isPinned=
{
false
}
onPinChanged=
{
id
=>
onPin
(
id
)
}
controlUpdate=
{
controlUpdateList
+
1
}
onDelete=
{
onDelete
}
/>
</
div
>
</
div
>
<
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
>
)
}
export
default
React
.
memo
(
Sidebar
)
web/app/components/share/chatbot/sidebar/list/index.tsx
0 → 100644
View file @
a0b3891c
'use client'
import
type
{
FC
}
from
'react'
import
React
,
{
useRef
}
from
'react'
import
{
ChatBubbleOvalLeftEllipsisIcon
,
}
from
'@heroicons/react/24/outline'
import
{
useInfiniteScroll
}
from
'ahooks'
import
{
ChatBubbleOvalLeftEllipsisIcon
as
ChatBubbleOvalLeftEllipsisSolidIcon
}
from
'@heroicons/react/24/solid'
import
cn
from
'classnames'
import
s
from
'./style.module.css'
import
type
{
ConversationItem
}
from
'@/models/share'
import
{
fetchConversations
}
from
'@/service/share'
import
ItemOperation
from
'@/app/components/explore/item-operation'
export
type
IListProps
=
{
className
:
string
currentId
:
string
onCurrentIdChange
:
(
id
:
string
)
=>
void
list
:
ConversationItem
[]
isClearConversationList
:
boolean
isInstalledApp
:
boolean
installedAppId
?:
string
onMoreLoaded
:
(
res
:
{
data
:
ConversationItem
[];
has_more
:
boolean
})
=>
void
isNoMore
:
boolean
isPinned
:
boolean
onPinChanged
:
(
id
:
string
)
=>
void
controlUpdate
:
number
onDelete
:
(
id
:
string
)
=>
void
}
const
List
:
FC
<
IListProps
>
=
({
className
,
currentId
,
onCurrentIdChange
,
list
,
isClearConversationList
,
isInstalledApp
,
installedAppId
,
onMoreLoaded
,
isNoMore
,
isPinned
,
onPinChanged
,
controlUpdate
,
onDelete
,
})
=>
{
const
listRef
=
useRef
<
HTMLDivElement
>
(
null
)
useInfiniteScroll
(
async
()
=>
{
if
(
!
isNoMore
)
{
const
lastId
=
!
isClearConversationList
?
list
[
list
.
length
-
1
]?.
id
:
undefined
const
{
data
:
conversations
,
has_more
}:
any
=
await
fetchConversations
(
isInstalledApp
,
installedAppId
,
lastId
,
isPinned
)
onMoreLoaded
({
data
:
conversations
,
has_more
})
}
return
{
list
:
[]
}
},
{
target
:
listRef
,
isNoMore
:
()
=>
{
return
isNoMore
},
reloadDeps
:
[
isNoMore
,
controlUpdate
],
},
)
return
(
<
nav
ref=
{
listRef
}
className=
{
cn
(
className
,
'shrink-0 space-y-1 bg-white pb-[85px] overflow-y-auto'
)
}
>
{
list
.
map
((
item
)
=>
{
const
isCurrent
=
item
.
id
===
currentId
const
ItemIcon
=
isCurrent
?
ChatBubbleOvalLeftEllipsisSolidIcon
:
ChatBubbleOvalLeftEllipsisIcon
return
(
<
div
onClick=
{
()
=>
onCurrentIdChange
(
item
.
id
)
}
key=
{
item
.
id
}
className=
{
cn
(
s
.
item
,
isCurrent
?
'bg-primary-50 text-primary-600'
:
'text-gray-700 hover:bg-gray-200 hover:text-gray-700'
,
'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer'
,
)
}
>
<
div
className=
'flex items-center w-0 grow'
>
<
ItemIcon
className=
{
cn
(
isCurrent
?
'text-primary-600'
:
'text-gray-400 group-hover:text-gray-500'
,
'mr-3 h-5 w-5 flex-shrink-0'
,
)
}
aria
-
hidden=
"true"
/>
<
span
>
{
item
.
name
}
</
span
>
</
div
>
{
item
.
id
!==
'-1'
&&
(
<
div
className=
{
cn
(
s
.
opBtn
,
'shrink-0'
)
}
onClick=
{
e
=>
e
.
stopPropagation
()
}
>
<
ItemOperation
isPinned=
{
isPinned
}
togglePin=
{
()
=>
onPinChanged
(
item
.
id
)
}
isShowDelete
onDelete=
{
()
=>
onDelete
(
item
.
id
)
}
/>
</
div
>
)
}
</
div
>
)
})
}
</
nav
>
)
}
export
default
React
.
memo
(
List
)
web/app/components/share/chatbot/sidebar/list/style.module.css
0 → 100644
View file @
a0b3891c
.opBtn
{
visibility
:
hidden
;
}
.item
:hover
.opBtn
{
visibility
:
visible
;
}
\ No newline at end of file
web/app/components/share/chatbot/style.module.css
0 → 100644
View file @
a0b3891c
.installedApp
{
height
:
calc
(
100vh
-
74px
);
}
\ No newline at end of file
web/app/components/share/chatbot/value-panel/index.tsx
0 → 100644
View file @
a0b3891c
'use client'
import
type
{
FC
,
ReactNode
}
from
'react'
import
React
from
'react'
import
cn
from
'classnames'
import
{
useTranslation
}
from
'react-i18next'
import
s
from
'./style.module.css'
import
{
StarIcon
}
from
'@/app/components/share/chatbot/welcome/massive-component'
import
Button
from
'@/app/components/base/button'
export
type
ITemplateVarPanelProps
=
{
className
?:
string
header
:
ReactNode
children
?:
ReactNode
|
null
isFold
:
boolean
}
const
TemplateVarPanel
:
FC
<
ITemplateVarPanelProps
>
=
({
className
,
header
,
children
,
isFold
,
})
=>
{
return
(
<
div
className=
{
cn
(
isFold
?
'border border-indigo-100'
:
s
.
boxShodow
,
className
,
'rounded-xl '
)
}
>
{
/* header */
}
<
div
className=
{
cn
(
isFold
&&
'rounded-b-xl'
,
'rounded-t-xl px-6 py-4 bg-indigo-25 text-xs'
)
}
>
{
header
}
</
div
>
{
/* body */
}
{
!
isFold
&&
children
&&
(
<
div
className=
'rounded-b-xl p-6'
>
{
children
}
</
div
>
)
}
</
div
>
)
}
export
const
PanelTitle
:
FC
<
{
title
:
string
;
className
?:
string
}
>
=
({
title
,
className
,
})
=>
{
return
(
<
div
className=
{
cn
(
className
,
'flex items-center space-x-1 text-indigo-600'
)
}
>
<
StarIcon
/>
<
span
className=
'text-xs'
>
{
title
}
</
span
>
</
div
>
)
}
export
const
VarOpBtnGroup
:
FC
<
{
className
?:
string
;
onConfirm
:
()
=>
void
;
onCancel
:
()
=>
void
}
>
=
({
className
,
onConfirm
,
onCancel
,
})
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
div
className=
{
cn
(
className
,
'flex mt-3 space-x-2 mobile:ml-0 tablet:ml-[128px] text-sm'
)
}
>
<
Button
className=
'text-sm'
type=
'primary'
onClick=
{
onConfirm
}
>
{
t
(
'common.operation.save'
)
}
</
Button
>
<
Button
className=
'text-sm'
onClick=
{
onCancel
}
>
{
t
(
'common.operation.cancel'
)
}
</
Button
>
</
div
>
)
}
export
default
React
.
memo
(
TemplateVarPanel
)
web/app/components/share/chatbot/value-panel/style.module.css
0 → 100644
View file @
a0b3891c
.boxShodow
{
box-shadow
:
0px
12px
16px
-4px
rgba
(
16
,
24
,
40
,
0.08
),
0px
4px
6px
-2px
rgba
(
16
,
24
,
40
,
0.03
);
}
\ No newline at end of file
web/app/components/share/chatbot/welcome/icons/logo.png
0 → 100644
View file @
a0b3891c
3.83 KB
web/app/components/share/chatbot/welcome/index.tsx
0 → 100644
View file @
a0b3891c
This diff is collapsed.
Click to expand it.
web/app/components/share/chatbot/welcome/massive-component.tsx
0 → 100644
View file @
a0b3891c
'use client'
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
cn
from
'classnames'
import
{
useTranslation
}
from
'react-i18next'
import
{
PencilIcon
,
}
from
'@heroicons/react/24/solid'
import
s
from
'./style.module.css'
import
type
{
SiteInfo
}
from
'@/models/share'
import
Button
from
'@/app/components/base/button'
export
const
AppInfo
:
FC
<
{
siteInfo
:
SiteInfo
}
>
=
({
siteInfo
})
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
div
>
<
div
className=
'flex items-center py-2 text-xl font-medium text-gray-700 rounded-md'
>
👏
{
t
(
'share.common.welcome'
)
}
{
siteInfo
.
title
}
</
div
>
<
p
className=
'text-sm text-gray-500'
>
{
siteInfo
.
description
}
</
p
>
</
div
>
)
}
export
const
PromptTemplate
:
FC
<
{
html
:
string
}
>
=
({
html
})
=>
{
return
(
<
div
className=
{
' box-border text-sm text-gray-700'
}
dangerouslySetInnerHTML=
{
{
__html
:
html
}
}
></
div
>
)
}
export
const
StarIcon
=
()
=>
(
<
svg
width=
"12"
height=
"12"
viewBox=
"0 0 12 12"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<
path
d=
"M2.75 1C2.75 0.723858 2.52614 0.5 2.25 0.5C1.97386 0.5 1.75 0.723858 1.75 1V1.75H1C0.723858 1.75 0.5 1.97386 0.5 2.25C0.5 2.52614 0.723858 2.75 1 2.75H1.75V3.5C1.75 3.77614 1.97386 4 2.25 4C2.52614 4 2.75 3.77614 2.75 3.5V2.75H3.5C3.77614 2.75 4 2.52614 4 2.25C4 1.97386 3.77614 1.75 3.5 1.75H2.75V1Z"
fill=
"#444CE7"
/>
<
path
d=
"M2.75 8.5C2.75 8.22386 2.52614 8 2.25 8C1.97386 8 1.75 8.22386 1.75 8.5V9.25H1C0.723858 9.25 0.5 9.47386 0.5 9.75C0.5 10.0261 0.723858 10.25 1 10.25H1.75V11C1.75 11.2761 1.97386 11.5 2.25 11.5C2.52614 11.5 2.75 11.2761 2.75 11V10.25H3.5C3.77614 10.25 4 10.0261 4 9.75C4 9.47386 3.77614 9.25 3.5 9.25H2.75V8.5Z"
fill=
"#444CE7"
/>
<
path
d=
"M6.96667 1.32051C6.8924 1.12741 6.70689 1 6.5 1C6.29311 1 6.10759 1.12741 6.03333 1.32051L5.16624 3.57494C5.01604 3.96546 4.96884 4.078 4.90428 4.1688C4.8395 4.2599 4.7599 4.3395 4.6688 4.40428C4.578 4.46884 4.46546 4.51604 4.07494 4.66624L1.82051 5.53333C1.62741 5.60759 1.5 5.79311 1.5 6C1.5 6.20689 1.62741 6.39241 1.82051 6.46667L4.07494 7.33376C4.46546 7.48396 4.578 7.53116 4.6688 7.59572C4.7599 7.6605 4.8395 7.7401 4.90428 7.8312C4.96884 7.922 5.01604 8.03454 5.16624 8.42506L6.03333 10.6795C6.1076 10.8726 6.29311 11 6.5 11C6.70689 11 6.89241 10.8726 6.96667 10.6795L7.83376 8.42506C7.98396 8.03454 8.03116 7.922 8.09572 7.8312C8.1605 7.7401 8.2401 7.6605 8.3312 7.59572C8.422 7.53116 8.53454 7.48396 8.92506 7.33376L11.1795 6.46667C11.3726 6.39241 11.5 6.20689 11.5 6C11.5 5.79311 11.3726 5.60759 11.1795 5.53333L8.92506 4.66624C8.53454 4.51604 8.422 4.46884 8.3312 4.40428C8.2401 4.3395 8.1605 4.2599 8.09572 4.1688C8.03116 4.078 7.98396 3.96546 7.83376 3.57494L6.96667 1.32051Z"
fill=
"#444CE7"
/>
</
svg
>
)
export
const
ChatBtn
:
FC
<
{
onClick
:
()
=>
void
;
className
?:
string
}
>
=
({
className
,
onClick
,
})
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
Button
type=
'primary'
className=
{
cn
(
className
,
`!p-0 space-x-2 flex items-center ${s.customBtn}`
)
}
onClick=
{
onClick
}
>
<
svg
width=
"20"
height=
"21"
viewBox=
"0 0 20 21"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<
path
fillRule=
"evenodd"
clipRule=
"evenodd"
d=
"M18 10.5C18 14.366 14.418 17.5 10 17.5C8.58005 17.506 7.17955 17.1698 5.917 16.52L2 17.5L3.338 14.377C2.493 13.267 2 11.934 2 10.5C2 6.634 5.582 3.5 10 3.5C14.418 3.5 18 6.634 18 10.5ZM7 9.5H5V11.5H7V9.5ZM15 9.5H13V11.5H15V9.5ZM9 9.5H11V11.5H9V9.5Z"
fill=
"white"
/>
</
svg
>
{
t
(
'share.chat.startChat'
)
}
</
Button
>
)
}
export
const
EditBtn
=
({
className
,
onClick
}:
{
className
?:
string
;
onClick
:
()
=>
void
})
=>
{
const
{
t
}
=
useTranslation
()
return
(
<
div
className=
{
cn
(
'px-2 flex space-x-1 items-center rounded-md cursor-pointer'
,
className
)
}
onClick=
{
onClick
}
>
<
PencilIcon
className=
'w-3 h-3'
/>
<
span
>
{
t
(
'common.operation.edit'
)
}
</
span
>
</
div
>
)
}
export
const
FootLogo
=
()
=>
(
<
div
className=
{
s
.
logo
}
/>
)
web/app/components/share/chatbot/welcome/style.module.css
0 → 100644
View file @
a0b3891c
.boxShodow
{
box-shadow
:
0px
12px
16px
-4px
rgba
(
16
,
24
,
40
,
0.08
),
0px
4px
6px
-2px
rgba
(
16
,
24
,
40
,
0.03
);
}
.bgGrayColor
{
background-color
:
#F9FAFB
;
}
.headerBg
{
height
:
3.5rem
;
padding-left
:
1.5rem
;
padding-right
:
1.5rem
;
}
.formLabel
{
width
:
120px
;
margin-right
:
8px
;
}
.customBtn
{
width
:
136px
;
}
.logo
{
width
:
48px
;
height
:
20px
;
background
:
url(./icons/logo.png)
center
center
no-repeat
;
background-size
:
contain
;
}
\ No newline at end of file
web/app/components/share/header.tsx
View file @
a0b3891c
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
AppIcon
from
'@/app/components/base/app-icon'
import
{
Bars3Icon
,
PencilSquareIcon
,
}
from
'@heroicons/react/24/solid'
import
AppIcon
from
'@/app/components/base/app-icon'
export
type
IHeaderProps
=
{
title
:
string
icon
:
string
icon_background
:
string
isMobile
?:
boolean
isEmbedScene
?:
boolean
onShowSideBar
?:
()
=>
void
onCreateNewChat
?:
()
=>
void
}
...
...
@@ -18,29 +19,34 @@ const Header: FC<IHeaderProps> = ({
isMobile
,
icon
,
icon_background
,
isEmbedScene
=
false
,
onShowSideBar
,
onCreateNewChat
,
})
=>
{
return
(
<
div
className=
"shrink-0 flex items-center justify-between h-12 px-3 bg-gray-100"
>
{
isMobile
?
(
<
div
className=
'flex items-center justify-center h-8 w-8 cursor-pointer'
onClick=
{
()
=>
onShowSideBar
?.()
}
>
<
Bars3Icon
className=
"h-4 w-4 text-gray-500"
/>
</
div
>
)
:
<
div
></
div
>
}
<
div
className=
{
`shrink-0 flex items-center justify-between h-12 px-3 bg-gray-100 ${isEmbedScene ? 'bg-gradient-to-r from-blue-600 to-sky-500' : ''}`
}
>
{
isMobile
?
(
<
div
className=
'flex items-center justify-center h-8 w-8 cursor-pointer'
onClick=
{
()
=>
onShowSideBar
?.()
}
>
<
Bars3Icon
className=
"h-4 w-4 text-gray-500"
/>
</
div
>
)
:
<
div
></
div
>
}
<
div
className=
'flex items-center space-x-2'
>
<
AppIcon
size=
"small"
icon=
{
icon
}
background=
{
icon_background
}
/>
<
div
className=
" text-sm text-gray-800 font-bold"
>
{
title
}
</
div
>
<
div
className=
{
`text-sm text-gray-800 font-bold ${isEmbedScene ? 'text-white' : ''}`
}
>
{
title
}
</
div
>
</
div
>
{
isMobile
?
(
<
div
className=
'flex items-center justify-center h-8 w-8 cursor-pointer'
onClick=
{
()
=>
onCreateNewChat
?.()
}
>
<
PencilSquareIcon
className=
"h-4 w-4 text-gray-500"
/>
</
div
>)
:
<
div
></
div
>
}
{
isMobile
?
(
<
div
className=
'flex items-center justify-center h-8 w-8 cursor-pointer'
onClick=
{
()
=>
onCreateNewChat
?.()
}
>
<
PencilSquareIcon
className=
"h-4 w-4 text-gray-500"
/>
</
div
>)
:
<
div
></
div
>
}
</
div
>
)
}
...
...
web/i18n/lang/app-overview.en.ts
View file @
a0b3891c
...
...
@@ -42,6 +42,8 @@ const translation = {
explanation
:
'Choose the way to embed chat app to your website'
,
iframe
:
'To add the chat app any where on your website, add this iframe to your html code.'
,
scripts
:
'To add a chat app to the bottom right of your website add this code to your html.'
,
copied
:
'Copied'
,
copy
:
'Copy'
,
},
customize
:
{
way
:
'way'
,
...
...
web/i18n/lang/app-overview.zh.ts
View file @
a0b3891c
...
...
@@ -42,6 +42,8 @@ const translation = {
explanation
:
'选择一种方式将聊天应用嵌入到你的网站中'
,
iframe
:
'将以下 iframe 嵌入到你的网站中的目标位置'
,
scripts
:
'将以下代码嵌入到你的网站中'
,
copied
:
'已复制'
,
copy
:
'复制'
,
},
customize
:
{
way
:
'方法'
,
...
...
web/public/embed.js
0 → 100644
View file @
a0b3891c
/** this file is used to embed the chatbot in a website
* the difyChatbotConfig should be defined in the html file before this script is included
* the difyChatbotConfig should contain the token of the chatbot
* the token can be found in the chatbot settings page
*/
// attention: This JavaScript script must be placed after the <body> element. Otherwise, the script will not work.
document
.
body
.
onload
=
embedChatbot
;
async
function
embedChatbot
()
{
const
difyChatbotConfig
=
window
.
difyChatbotConfig
;
if
(
!
difyChatbotConfig
||
!
difyChatbotConfig
.
token
)
{
console
.
error
(
'difyChatbotConfig is empty or token is not provided'
)
return
;
}
const
isDev
=
!!
difyChatbotConfig
.
isDev
const
openIcon
=
`<svg
id="openIcon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.7586 2L16.2412 2C17.0462 1.99999 17.7105 1.99998 18.2517 2.04419C18.8138 2.09012 19.3305 2.18868 19.8159 2.43598C20.5685 2.81947 21.1804 3.43139 21.5639 4.18404C21.8112 4.66937 21.9098 5.18608 21.9557 5.74818C21.9999 6.28937 21.9999 6.95373 21.9999 7.7587L22 14.1376C22.0004 14.933 22.0007 15.5236 21.8636 16.0353C21.4937 17.4156 20.4155 18.4938 19.0352 18.8637C18.7277 18.9461 18.3917 18.9789 17.9999 18.9918L17.9999 20.371C18 20.6062 18 20.846 17.9822 21.0425C17.9651 21.2305 17.9199 21.5852 17.6722 21.8955C17.3872 22.2525 16.9551 22.4602 16.4983 22.4597C16.1013 22.4593 15.7961 22.273 15.6386 22.1689C15.474 22.06 15.2868 21.9102 15.1031 21.7632L12.69 19.8327C12.1714 19.4178 12.0174 19.3007 11.8575 19.219C11.697 19.137 11.5262 19.0771 11.3496 19.0408C11.1737 19.0047 10.9803 19 10.3162 19H7.75858C6.95362 19 6.28927 19 5.74808 18.9558C5.18598 18.9099 4.66928 18.8113 4.18394 18.564C3.43129 18.1805 2.81937 17.5686 2.43588 16.816C2.18859 16.3306 2.09002 15.8139 2.0441 15.2518C1.99988 14.7106 1.99989 14.0463 1.9999 13.2413V7.75868C1.99989 6.95372 1.99988 6.28936 2.0441 5.74818C2.09002 5.18608 2.18859 4.66937 2.43588 4.18404C2.81937 3.43139 3.43129 2.81947 4.18394 2.43598C4.66928 2.18868 5.18598 2.09012 5.74808 2.04419C6.28927 1.99998 6.95364 1.99999 7.7586 2ZM10.5073 7.5C10.5073 6.67157 9.83575 6 9.00732 6C8.1789 6 7.50732 6.67157 7.50732 7.5C7.50732 8.32843 8.1789 9 9.00732 9C9.83575 9 10.5073 8.32843 10.5073 7.5ZM16.6073 11.7001C16.1669 11.3697 15.5426 11.4577 15.2105 11.8959C15.1488 11.9746 15.081 12.0486 15.0119 12.1207C14.8646 12.2744 14.6432 12.4829 14.3566 12.6913C13.7796 13.111 12.9818 13.5001 12.0073 13.5001C11.0328 13.5001 10.235 13.111 9.65799 12.6913C9.37138 12.4829 9.15004 12.2744 9.00274 12.1207C8.93366 12.0486 8.86581 11.9745 8.80418 11.8959C8.472 11.4577 7.84775 11.3697 7.40732 11.7001C6.96549 12.0314 6.87595 12.6582 7.20732 13.1001C7.20479 13.0968 7.21072 13.1043 7.22094 13.1171C7.24532 13.1478 7.29407 13.2091 7.31068 13.2289C7.36932 13.2987 7.45232 13.3934 7.55877 13.5045C7.77084 13.7258 8.08075 14.0172 8.48165 14.3088C9.27958 14.8891 10.4818 15.5001 12.0073 15.5001C13.5328 15.5001 14.735 14.8891 15.533 14.3088C15.9339 14.0172 16.2438 13.7258 16.4559 13.5045C16.5623 13.3934 16.6453 13.2987 16.704 13.2289C16.7333 13.1939 16.7567 13.165 16.7739 13.1432C17.1193 12.6969 17.0729 12.0493 16.6073 11.7001ZM15.0073 6C15.8358 6 16.5073 6.67157 16.5073 7.5C16.5073 8.32843 15.8358 9 15.0073 9C14.1789 9 13.5073 8.32843 13.5073 7.5C13.5073 6.67157 14.1789 6 15.0073 6Z"
fill="white"
/>
</svg>`
const
closeIcon
=
`<svg
id="closeIcon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 18L6 6M6 18L18 6"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>`
// create iframe
function
createIframe
()
{
const
iframe
=
document
.
createElement
(
'iframe'
);
iframe
.
allow
=
"fullscreen"
iframe
.
id
=
'dify-chatbot-bubble-window'
iframe
.
src
=
`https://
${
isDev
?
'dev.'
:
''
}
udify.app/chatbot/
${
difyChatbotConfig
.
token
}
`
;
iframe
.
style
.
cssText
=
'border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; height: 40rem; border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset;'
document
.
body
.
appendChild
(
iframe
);
}
const
targetButton
=
document
.
getElementById
(
'dify-chatbot-bubble-button'
)
if
(
!
targetButton
)
{
// create button
const
containerDiv
=
document
.
createElement
(
"div"
);
containerDiv
.
id
=
'dify-chatbot-bubble-button'
containerDiv
.
style
.
cssText
=
`position: fixed; bottom: 1rem; right: 1rem; width: 50px; height: 50px; border-radius: 25px; background-color: #155EEF; box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 8px 0px; cursor: pointer; z-index: 2147483647; transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);}`
;
const
displayDiv
=
document
.
createElement
(
'div'
);
displayDiv
.
style
.
cssText
=
'display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;'
;
displayDiv
.
innerHTML
=
openIcon
containerDiv
.
appendChild
(
displayDiv
);
document
.
body
.
appendChild
(
containerDiv
);
// add click event to control iframe display
containerDiv
.
addEventListener
(
'click'
,
function
()
{
const
targetIframe
=
document
.
getElementById
(
'dify-chatbot-bubble-window'
)
if
(
!
targetIframe
)
{
createIframe
()
return
;
}
if
(
targetIframe
.
style
.
display
===
'none'
)
{
targetIframe
.
style
.
display
=
'block'
;
displayDiv
.
innerHTML
=
closeIcon
}
else
{
targetIframe
.
style
.
display
=
'none'
;
displayDiv
.
innerHTML
=
openIcon
}
});
}
}
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