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
efa16dbb
Unverified
Commit
efa16dbb
authored
Dec 01, 2023
by
Yuhao
Committed by
GitHub
Dec 01, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: drag to upload image (#1666)
parent
a6241be4
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
109 additions
and
71 deletions
+109
-71
index.tsx
web/app/components/app/chat/index.tsx
+7
-2
hooks.ts
web/app/components/base/image-uploader/hooks.ts
+98
-18
uploader.tsx
web/app/components/base/image-uploader/uploader.tsx
+4
-51
No files found.
web/app/components/app/chat/index.tsx
View file @
efa16dbb
...
...
@@ -23,7 +23,7 @@ import type { DataSet } from '@/models/datasets'
import
ChatImageUploader
from
'@/app/components/base/image-uploader/chat-image-uploader'
import
ImageList
from
'@/app/components/base/image-uploader/image-list'
import
{
TransferMethod
,
type
VisionFile
,
type
VisionSettings
}
from
'@/types/app'
import
{
useClipboardUploader
,
useImageFiles
}
from
'@/app/components/base/image-uploader/hooks'
import
{
useClipboardUploader
,
use
DraggableUploader
,
use
ImageFiles
}
from
'@/app/components/base/image-uploader/hooks'
export
type
IChatProps
=
{
configElem
?:
React
.
ReactNode
...
...
@@ -102,6 +102,7 @@ const Chat: FC<IChatProps> = ({
onClear
,
}
=
useImageFiles
()
const
{
onPaste
}
=
useClipboardUploader
({
onUpload
,
visionConfig
,
files
})
const
{
onDragEnter
,
onDragLeave
,
onDragOver
,
onDrop
,
isDragActive
}
=
useDraggableUploader
<
HTMLTextAreaElement
>
({
onUpload
,
files
,
visionConfig
})
const
isUseInputMethod
=
useRef
(
false
)
const
[
query
,
setQuery
]
=
React
.
useState
(
''
)
...
...
@@ -273,7 +274,7 @@ const Chat: FC<IChatProps> = ({
</
div
>
</
div
>)
}
<
div
className=
'p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto'
>
<
div
className=
{
cn
(
'p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto'
,
isDragActive
&&
'border-primary-600'
)
}
>
{
visionConfig
?.
enabled
&&
(
<>
...
...
@@ -307,6 +308,10 @@ const Chat: FC<IChatProps> = ({
onKeyUp=
{
handleKeyUp
}
onKeyDown=
{
handleKeyDown
}
onPaste=
{
onPaste
}
onDragEnter=
{
onDragEnter
}
onDragLeave=
{
onDragLeave
}
onDragOver=
{
onDragOver
}
onDrop=
{
onDrop
}
autoSize
/>
<
div
className=
"absolute bottom-2 right-2 flex items-center h-8"
>
...
...
web/app/components/base/image-uploader/hooks.ts
View file @
efa16dbb
...
...
@@ -111,35 +111,27 @@ export const useImageFiles = () => {
}
}
type
use
Clipboard
UploaderProps
=
{
files
:
ImageFile
[]
visionConfig
?:
VisionSettings
type
use
Local
UploaderProps
=
{
disabled
?:
boolean
limit
?:
number
onUpload
:
(
imageFile
:
ImageFile
)
=>
void
}
export
const
use
ClipboardUploader
=
({
visionConfig
,
onUpload
,
files
}:
useClipboard
UploaderProps
)
=>
{
export
const
use
LocalFileUploader
=
({
limit
,
disabled
=
false
,
onUpload
}:
useLocal
UploaderProps
)
=>
{
const
{
notify
}
=
useToastContext
()
const
params
=
useParams
()
const
{
t
}
=
useTranslation
()
const
handleClipboardPaste
=
useCallback
((
e
:
ClipboardEvent
<
HTMLTextAreaElement
>
)
=>
{
if
(
!
visionConfig
||
!
visionConfig
.
enabled
)
return
const
disabled
=
files
.
length
>=
visionConfig
.
number_limits
if
(
disabled
)
const
handleLocalFileUpload
=
useCallback
((
file
:
File
)
=>
{
if
(
disabled
)
{
// TODO: leave some warnings?
return
}
const
file
=
e
.
clipboardData
?.
files
[
0
]
if
(
!
file
||
!
ALLOW_FILE_EXTENSIONS
.
includes
(
file
.
type
.
split
(
'/'
)[
1
]))
if
(
!
ALLOW_FILE_EXTENSIONS
.
includes
(
file
.
type
.
split
(
'/'
)[
1
]))
return
const
limit
=
+
visionConfig
.
image_file_size_limit
!
if
(
file
.
size
>
limit
*
1024
*
1024
)
{
if
(
limit
&&
file
.
size
>
limit
*
1024
*
1024
)
{
notify
({
type
:
'error'
,
message
:
t
(
'common.imageUploader.uploadFromComputerLimit'
,
{
size
:
limit
})
})
return
}
...
...
@@ -182,9 +174,97 @@ export const useClipboardUploader = ({ visionConfig, onUpload, files }: useClipb
false
,
)
reader
.
readAsDataURL
(
file
)
},
[
visionConfig
,
files
.
length
,
notify
,
t
,
onUpload
,
params
.
token
])
},
[
disabled
,
limit
,
notify
,
t
,
onUpload
,
params
.
token
])
return
{
disabled
,
handleLocalFileUpload
}
}
type
useClipboardUploaderProps
=
{
files
:
ImageFile
[]
visionConfig
?:
VisionSettings
onUpload
:
(
imageFile
:
ImageFile
)
=>
void
}
export
const
useClipboardUploader
=
({
visionConfig
,
onUpload
,
files
}:
useClipboardUploaderProps
)
=>
{
const
allowLocalUpload
=
visionConfig
?.
transfer_methods
?.
includes
(
TransferMethod
.
local_file
)
const
disabled
=
useMemo
(()
=>
!
visionConfig
||
!
visionConfig
?.
enabled
||
!
allowLocalUpload
||
files
.
length
>=
visionConfig
.
number_limits
!
,
[
allowLocalUpload
,
files
.
length
,
visionConfig
])
const
limit
=
useMemo
(()
=>
visionConfig
?
+
visionConfig
.
image_file_size_limit
!
:
0
,
[
visionConfig
])
const
{
handleLocalFileUpload
}
=
useLocalFileUploader
({
limit
,
onUpload
,
disabled
})
const
handleClipboardPaste
=
useCallback
((
e
:
ClipboardEvent
<
HTMLTextAreaElement
>
)
=>
{
e
.
preventDefault
()
const
file
=
e
.
clipboardData
?.
files
[
0
]
if
(
!
file
)
return
handleLocalFileUpload
(
file
)
},
[
handleLocalFileUpload
])
return
{
onPaste
:
handleClipboardPaste
,
}
}
type
useDraggableUploaderProps
=
{
files
:
ImageFile
[]
visionConfig
?:
VisionSettings
onUpload
:
(
imageFile
:
ImageFile
)
=>
void
}
export
const
useDraggableUploader
=
<
T
extends
HTMLElement
>
({
visionConfig
,
onUpload
,
files
}:
useDraggableUploaderProps
)
=>
{
const
allowLocalUpload
=
visionConfig
?.
transfer_methods
?.
includes
(
TransferMethod
.
local_file
)
const
disabled
=
useMemo
(()
=>
!
visionConfig
||
!
visionConfig
?.
enabled
||
!
allowLocalUpload
||
files
.
length
>=
visionConfig
.
number_limits
!
,
[
allowLocalUpload
,
files
.
length
,
visionConfig
])
const
limit
=
useMemo
(()
=>
visionConfig
?
+
visionConfig
.
image_file_size_limit
!
:
0
,
[
visionConfig
])
const
{
handleLocalFileUpload
}
=
useLocalFileUploader
({
disabled
,
onUpload
,
limit
})
const
[
isDragActive
,
setIsDragActive
]
=
useState
(
false
)
const
handleDragEnter
=
useCallback
((
e
:
React
.
DragEvent
<
T
>
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
if
(
!
disabled
)
setIsDragActive
(
true
)
},
[
disabled
])
const
handleDragOver
=
useCallback
((
e
:
React
.
DragEvent
<
T
>
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
},
[])
const
handleDragLeave
=
useCallback
((
e
:
React
.
DragEvent
<
T
>
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
setIsDragActive
(
false
)
},
[])
const
handleDrop
=
useCallback
((
e
:
React
.
DragEvent
<
T
>
)
=>
{
e
.
preventDefault
()
e
.
stopPropagation
()
setIsDragActive
(
false
)
const
file
=
e
.
dataTransfer
.
files
[
0
]
if
(
!
file
)
return
handleLocalFileUpload
(
file
)
},
[
handleLocalFileUpload
])
return
{
onDragEnter
:
handleDragEnter
,
onDragOver
:
handleDragOver
,
onDragLeave
:
handleDragLeave
,
onDrop
:
handleDrop
,
isDragActive
,
}
}
web/app/components/base/image-uploader/uploader.tsx
View file @
efa16dbb
import
type
{
ChangeEvent
,
FC
}
from
'react'
import
{
useState
}
from
'react'
import
{
useParams
}
from
'next/navigation'
import
{
useTranslation
}
from
'react-i18next'
import
{
imageUpload
}
from
'./utils'
import
{
useLocalFileUploader
}
from
'./hooks'
import
type
{
ImageFile
}
from
'@/types/app'
import
{
ALLOW_FILE_EXTENSIONS
,
TransferMethod
}
from
'@/types/app'
import
{
useToastContext
}
from
'@/app/components/base/toast'
import
{
ALLOW_FILE_EXTENSIONS
}
from
'@/types/app'
type
UploaderProps
=
{
children
:
(
hovering
:
boolean
)
=>
JSX
.
Element
...
...
@@ -21,9 +18,7 @@ const Uploader: FC<UploaderProps> = ({
disabled
,
})
=>
{
const
[
hovering
,
setHovering
]
=
useState
(
false
)
const
params
=
useParams
()
const
{
notify
}
=
useToastContext
()
const
{
t
}
=
useTranslation
()
const
{
handleLocalFileUpload
}
=
useLocalFileUploader
({
limit
,
onUpload
,
disabled
})
const
handleChange
=
(
e
:
ChangeEvent
<
HTMLInputElement
>
)
=>
{
const
file
=
e
.
target
.
files
?.[
0
]
...
...
@@ -31,49 +26,7 @@ const Uploader: FC<UploaderProps> = ({
if
(
!
file
)
return
if
(
limit
&&
file
.
size
>
limit
*
1024
*
1024
)
{
notify
({
type
:
'error'
,
message
:
t
(
'common.imageUploader.uploadFromComputerLimit'
,
{
size
:
limit
})
})
return
}
const
reader
=
new
FileReader
()
reader
.
addEventListener
(
'load'
,
()
=>
{
const
imageFile
=
{
type
:
TransferMethod
.
local_file
,
_id
:
`
${
Date
.
now
()}
`
,
fileId
:
''
,
file
,
url
:
reader
.
result
as
string
,
base64Url
:
reader
.
result
as
string
,
progress
:
0
,
}
onUpload
(
imageFile
)
imageUpload
({
file
:
imageFile
.
file
,
onProgressCallback
:
(
progress
)
=>
{
onUpload
({
...
imageFile
,
progress
})
},
onSuccessCallback
:
(
res
)
=>
{
onUpload
({
...
imageFile
,
fileId
:
res
.
id
,
progress
:
100
})
},
onErrorCallback
:
()
=>
{
notify
({
type
:
'error'
,
message
:
t
(
'common.imageUploader.uploadFromComputerUploadError'
)
})
onUpload
({
...
imageFile
,
progress
:
-
1
})
},
},
!!
params
.
token
)
},
false
,
)
reader
.
addEventListener
(
'error'
,
()
=>
{
notify
({
type
:
'error'
,
message
:
t
(
'common.imageUploader.uploadFromComputerReadError'
)
})
},
false
,
)
reader
.
readAsDataURL
(
file
)
handleLocalFileUpload
(
file
)
}
return
(
...
...
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