mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 05:39:05 +08:00
Merge pull request #3 from xihajun/codex/add-image-support-to-ctrl+e-input-m06lwh
feat: enable image input in mini assistant
This commit is contained in:
commit
047a581220
@ -41,6 +41,7 @@ import type { FeatureMenusRef } from './components/FeatureMenus'
|
|||||||
import FeatureMenus from './components/FeatureMenus'
|
import FeatureMenus from './components/FeatureMenus'
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import InputBar from './components/InputBar'
|
import InputBar from './components/InputBar'
|
||||||
|
import PastedFilesPreview from './components/PastedFilesPreview'
|
||||||
|
|
||||||
const logger = loggerService.withContext('HomeWindow')
|
const logger = loggerService.withContext('HomeWindow')
|
||||||
|
|
||||||
@ -87,6 +88,8 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
return userInputText.trim()
|
return userInputText.trim()
|
||||||
}, [isFirstMessage, referenceText, userInputText])
|
}, [isFirstMessage, referenceText, userInputText])
|
||||||
|
|
||||||
|
const hasChatInput = useMemo(() => Boolean(userContent) || files.length > 0, [files.length, userContent])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
i18n.changeLanguage(language || navigator.language || defaultLanguage)
|
i18n.changeLanguage(language || navigator.language || defaultLanguage)
|
||||||
}, [language])
|
}, [language])
|
||||||
@ -171,7 +174,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
if (isLoading) return
|
if (isLoading) return
|
||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (userContent) {
|
if (userContent || files.length > 0) {
|
||||||
if (route === 'home') {
|
if (route === 'home') {
|
||||||
featureMenusRef.current?.useFeature()
|
featureMenusRef.current?.useFeature()
|
||||||
} else {
|
} else {
|
||||||
@ -242,7 +245,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
|
|
||||||
const handleSendMessage = useCallback(
|
const handleSendMessage = useCallback(
|
||||||
async (prompt?: string) => {
|
async (prompt?: string) => {
|
||||||
if (isEmpty(userContent) || !currentTopic.current) {
|
if ((isEmpty(userContent) && files.length === 0) || !currentTopic.current) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,8 +254,10 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
|
|
||||||
const uploadedFiles = files.length ? await FileManager.uploadFiles(files) : []
|
const uploadedFiles = files.length ? await FileManager.uploadFiles(files) : []
|
||||||
|
|
||||||
|
const content = [prompt, userContent].filter(Boolean).join('\n\n') || undefined
|
||||||
|
|
||||||
const { message: userMessage, blocks } = getUserMessage({
|
const { message: userMessage, blocks } = getUserMessage({
|
||||||
content: [prompt, userContent].filter(Boolean).join('\n\n'),
|
content,
|
||||||
assistant: currentAssistant,
|
assistant: currentAssistant,
|
||||||
topic: currentTopic.current,
|
topic: currentTopic.current,
|
||||||
files: uploadedFiles
|
files: uploadedFiles
|
||||||
@ -481,6 +486,10 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
[files, userContent, currentAssistant]
|
[files, userContent, currentAssistant]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleRemoveFile = useCallback((filePath: string) => {
|
||||||
|
setFiles((prevFiles) => prevFiles.filter((file) => file.path !== filePath))
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handlePause = useCallback(() => {
|
const handlePause = useCallback(() => {
|
||||||
if (currentAskId.current) {
|
if (currentAskId.current) {
|
||||||
abortCompletion(currentAskId.current)
|
abortCompletion(currentAskId.current)
|
||||||
@ -575,6 +584,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
handlePaste={handlePaste}
|
handlePaste={handlePaste}
|
||||||
ref={inputBarRef}
|
ref={inputBarRef}
|
||||||
/>
|
/>
|
||||||
|
<PastedFilesPreview files={files} onRemove={handleRemoveFile} />
|
||||||
<Divider style={{ margin: '10px 0' }} />
|
<Divider style={{ margin: '10px 0' }} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -620,6 +630,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
handlePaste={handlePaste}
|
handlePaste={handlePaste}
|
||||||
ref={inputBarRef}
|
ref={inputBarRef}
|
||||||
/>
|
/>
|
||||||
|
<PastedFilesPreview files={files} onRemove={handleRemoveFile} />
|
||||||
<Divider style={{ margin: '10px 0' }} />
|
<Divider style={{ margin: '10px 0' }} />
|
||||||
<ClipboardPreview referenceText={referenceText} clearClipboard={clearClipboard} t={t} />
|
<ClipboardPreview referenceText={referenceText} clearClipboard={clearClipboard} t={t} />
|
||||||
<Main>
|
<Main>
|
||||||
@ -627,6 +638,7 @@ const HomeWindow: FC<{ draggable?: boolean }> = ({ draggable = true }) => {
|
|||||||
setRoute={setRoute}
|
setRoute={setRoute}
|
||||||
onSendMessage={handleSendMessage}
|
onSendMessage={handleSendMessage}
|
||||||
text={userContent}
|
text={userContent}
|
||||||
|
hasChatInput={hasChatInput}
|
||||||
ref={featureMenusRef}
|
ref={featureMenusRef}
|
||||||
/>
|
/>
|
||||||
</Main>
|
</Main>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ interface FeatureMenusProps {
|
|||||||
text: string
|
text: string
|
||||||
setRoute: Dispatch<SetStateAction<'translate' | 'summary' | 'chat' | 'explanation' | 'home'>>
|
setRoute: Dispatch<SetStateAction<'translate' | 'summary' | 'chat' | 'explanation' | 'home'>>
|
||||||
onSendMessage: (prompt?: string) => void
|
onSendMessage: (prompt?: string) => void
|
||||||
|
hasChatInput: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FeatureMenusRef {
|
export interface FeatureMenusRef {
|
||||||
@ -23,6 +24,7 @@ export interface FeatureMenusRef {
|
|||||||
const FeatureMenus = ({
|
const FeatureMenus = ({
|
||||||
ref,
|
ref,
|
||||||
text,
|
text,
|
||||||
|
hasChatInput,
|
||||||
setRoute,
|
setRoute,
|
||||||
onSendMessage
|
onSendMessage
|
||||||
}: FeatureMenusProps & { ref?: React.RefObject<FeatureMenusRef | null> }) => {
|
}: FeatureMenusProps & { ref?: React.RefObject<FeatureMenusRef | null> }) => {
|
||||||
@ -36,7 +38,7 @@ const FeatureMenus = ({
|
|||||||
title: t('miniwindow.feature.chat'),
|
title: t('miniwindow.feature.chat'),
|
||||||
active: true,
|
active: true,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (text) {
|
if (hasChatInput) {
|
||||||
setRoute('chat')
|
setRoute('chat')
|
||||||
onSendMessage()
|
onSendMessage()
|
||||||
}
|
}
|
||||||
@ -68,7 +70,7 @@ const FeatureMenus = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[onSendMessage, setRoute, t, text]
|
[hasChatInput, onSendMessage, setRoute, t, text]
|
||||||
)
|
)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
|
|||||||
@ -0,0 +1,82 @@
|
|||||||
|
import { CloseOutlined, FileImageOutlined, FileOutlined } from '@ant-design/icons'
|
||||||
|
import type { FileMetadata } from '@renderer/types'
|
||||||
|
import { FileTypes } from '@renderer/types'
|
||||||
|
import { Tooltip } from 'antd'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface PastedFilesPreviewProps {
|
||||||
|
files: FileMetadata[]
|
||||||
|
onRemove: (filePath: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PastedFilesPreview: FC<PastedFilesPreviewProps> = ({ files, onRemove }) => {
|
||||||
|
if (!files.length) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
{files.map((file) => (
|
||||||
|
<FileChip key={file.path} className="nodrag">
|
||||||
|
<IconWrapper>{file.type === FileTypes.IMAGE ? <FileImageOutlined /> : <FileOutlined />}</IconWrapper>
|
||||||
|
<Tooltip title={file.name} placement="topLeft">
|
||||||
|
<FileName>{file.name}</FileName>
|
||||||
|
</Tooltip>
|
||||||
|
<RemoveButton onClick={() => onRemove(file.path)}>
|
||||||
|
<CloseOutlined />
|
||||||
|
</RemoveButton>
|
||||||
|
</FileChip>
|
||||||
|
))}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 8px 0 2px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const FileChip = styled.div`
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--color-background-opacity);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
color: var(--color-text);
|
||||||
|
max-width: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
|
const IconWrapper = styled.span`
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
`
|
||||||
|
|
||||||
|
const FileName = styled.span`
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 180px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const RemoveButton = styled.button`
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default PastedFilesPreview
|
||||||
Loading…
Reference in New Issue
Block a user