mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 23:10:20 +08:00
feat: support clipboard images in mini assistant
This commit is contained in:
parent
7507443d8b
commit
4a7986f041
@ -7,12 +7,14 @@ import i18n from '@renderer/i18n'
|
|||||||
import { fetchChatCompletion } from '@renderer/services/ApiService'
|
import { fetchChatCompletion } from '@renderer/services/ApiService'
|
||||||
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
import { getDefaultTopic } from '@renderer/services/AssistantService'
|
||||||
import { ConversationService } from '@renderer/services/ConversationService'
|
import { ConversationService } from '@renderer/services/ConversationService'
|
||||||
|
import FileManager from '@renderer/services/FileManager'
|
||||||
import { getAssistantMessage, getUserMessage } from '@renderer/services/MessagesService'
|
import { getAssistantMessage, getUserMessage } from '@renderer/services/MessagesService'
|
||||||
|
import PasteService from '@renderer/services/PasteService'
|
||||||
import store, { useAppSelector } from '@renderer/store'
|
import store, { useAppSelector } from '@renderer/store'
|
||||||
import { updateOneBlock, upsertManyBlocks, upsertOneBlock } from '@renderer/store/messageBlock'
|
import { updateOneBlock, upsertManyBlocks, upsertOneBlock } from '@renderer/store/messageBlock'
|
||||||
import { newMessagesActions, selectMessagesForTopic } from '@renderer/store/newMessage'
|
import { newMessagesActions, selectMessagesForTopic } from '@renderer/store/newMessage'
|
||||||
import { cancelThrottledBlockUpdate, throttledBlockUpdate } from '@renderer/store/thunk/messageThunk'
|
import { cancelThrottledBlockUpdate, throttledBlockUpdate } from '@renderer/store/thunk/messageThunk'
|
||||||
import type { Topic } from '@renderer/types'
|
import type { FileMetadata, Topic } from '@renderer/types'
|
||||||
import { ThemeMode } from '@renderer/types'
|
import { ThemeMode } from '@renderer/types'
|
||||||
import type { Chunk } from '@renderer/types/chunk'
|
import type { Chunk } from '@renderer/types/chunk'
|
||||||
import { ChunkType } from '@renderer/types/chunk'
|
import { ChunkType } from '@renderer/types/chunk'
|
||||||
@ -51,6 +53,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
const [isFirstMessage, setIsFirstMessage] = useState(true)
|
const [isFirstMessage, setIsFirstMessage] = useState(true)
|
||||||
|
|
||||||
const [userInputText, setUserInputText] = useState('')
|
const [userInputText, setUserInputText] = useState('')
|
||||||
|
const [files, setFiles] = useState<FileMetadata[]>([])
|
||||||
|
|
||||||
const [clipboardText, setClipboardText] = useState('')
|
const [clipboardText, setClipboardText] = useState('')
|
||||||
const lastClipboardTextRef = useRef<string | null>(null)
|
const lastClipboardTextRef = useRef<string | null>(null)
|
||||||
@ -73,6 +76,8 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
const inputBarRef = useRef<HTMLDivElement>(null)
|
const inputBarRef = useRef<HTMLDivElement>(null)
|
||||||
const featureMenusRef = useRef<FeatureMenusRef>(null)
|
const featureMenusRef = useRef<FeatureMenusRef>(null)
|
||||||
|
|
||||||
|
const supportedImageExts = useMemo(() => ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'], [])
|
||||||
|
|
||||||
const referenceText = useMemo(() => clipboardText || userInputText, [clipboardText, userInputText])
|
const referenceText = useMemo(() => clipboardText || userInputText, [clipboardText, userInputText])
|
||||||
|
|
||||||
const userContent = useMemo(() => {
|
const userContent = useMemo(() => {
|
||||||
@ -213,6 +218,23 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
setUserInputText(e.target.value)
|
setUserInputText(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handlePaste = useCallback(
|
||||||
|
async (event: React.ClipboardEvent<HTMLInputElement>) => {
|
||||||
|
await PasteService.handlePaste(
|
||||||
|
event.nativeEvent,
|
||||||
|
supportedImageExts,
|
||||||
|
setFiles,
|
||||||
|
setUserInputText,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
userInputText,
|
||||||
|
undefined,
|
||||||
|
t
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[supportedImageExts, t, userInputText]
|
||||||
|
)
|
||||||
|
|
||||||
const handleError = (error: Error) => {
|
const handleError = (error: Error) => {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
setError(error.message)
|
setError(error.message)
|
||||||
@ -227,10 +249,13 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
try {
|
try {
|
||||||
const topicId = currentTopic.current.id
|
const topicId = currentTopic.current.id
|
||||||
|
|
||||||
|
const uploadedFiles = files.length ? await FileManager.uploadFiles(files) : []
|
||||||
|
|
||||||
const { message: userMessage, blocks } = getUserMessage({
|
const { message: userMessage, blocks } = getUserMessage({
|
||||||
content: [prompt, userContent].filter(Boolean).join('\n\n'),
|
content: [prompt, userContent].filter(Boolean).join('\n\n'),
|
||||||
assistant: currentAssistant,
|
assistant: currentAssistant,
|
||||||
topic: currentTopic.current
|
topic: currentTopic.current,
|
||||||
|
files: uploadedFiles
|
||||||
})
|
})
|
||||||
|
|
||||||
store.dispatch(newMessagesActions.addMessage({ topicId, message: userMessage }))
|
store.dispatch(newMessagesActions.addMessage({ topicId, message: userMessage }))
|
||||||
@ -272,6 +297,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
|
|
||||||
setIsFirstMessage(false)
|
setIsFirstMessage(false)
|
||||||
setUserInputText('')
|
setUserInputText('')
|
||||||
|
setFiles([])
|
||||||
|
|
||||||
const newAssistant = cloneDeep(currentAssistant)
|
const newAssistant = cloneDeep(currentAssistant)
|
||||||
if (!newAssistant.settings) {
|
if (!newAssistant.settings) {
|
||||||
@ -452,7 +478,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
currentAskId.current = ''
|
currentAskId.current = ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[userContent, currentAssistant]
|
[files, userContent, currentAssistant]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handlePause = useCallback(() => {
|
const handlePause = useCallback(() => {
|
||||||
@ -546,6 +572,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
handleKeyDown={handleKeyDown}
|
handleKeyDown={handleKeyDown}
|
||||||
handleChange={handleChange}
|
handleChange={handleChange}
|
||||||
|
handlePaste={handlePaste}
|
||||||
ref={inputBarRef}
|
ref={inputBarRef}
|
||||||
/>
|
/>
|
||||||
<Divider style={{ margin: '10px 0' }} />
|
<Divider style={{ margin: '10px 0' }} />
|
||||||
@ -590,6 +617,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
handleKeyDown={handleKeyDown}
|
handleKeyDown={handleKeyDown}
|
||||||
handleChange={handleChange}
|
handleChange={handleChange}
|
||||||
|
handlePaste={handlePaste}
|
||||||
ref={inputBarRef}
|
ref={inputBarRef}
|
||||||
/>
|
/>
|
||||||
<Divider style={{ margin: '10px 0' }} />
|
<Divider style={{ margin: '10px 0' }} />
|
||||||
|
|||||||
@ -14,6 +14,7 @@ interface InputBarProps {
|
|||||||
loading: boolean
|
loading: boolean
|
||||||
handleKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
handleKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
||||||
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
|
handlePaste: (e: React.ClipboardEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputBar = ({
|
const InputBar = ({
|
||||||
@ -23,7 +24,8 @@ const InputBar = ({
|
|||||||
placeholder,
|
placeholder,
|
||||||
loading,
|
loading,
|
||||||
handleKeyDown,
|
handleKeyDown,
|
||||||
handleChange
|
handleChange,
|
||||||
|
handlePaste
|
||||||
}: InputBarProps & { ref?: React.RefObject<HTMLDivElement | null> }) => {
|
}: InputBarProps & { ref?: React.RefObject<HTMLDivElement | null> }) => {
|
||||||
const inputRef = useRef<InputRef>(null)
|
const inputRef = useRef<InputRef>(null)
|
||||||
const { setTimeoutTimer } = useTimer()
|
const { setTimeoutTimer } = useTimer()
|
||||||
@ -40,6 +42,7 @@ const InputBar = ({
|
|||||||
autoFocus
|
autoFocus
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
onPaste={handlePaste}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
</InputWrapper>
|
</InputWrapper>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user