mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 05:39:05 +08:00
feat: added attachment preview and upload/removal capabilities.
- Added functionality to display attachment preview with upload and removal capabilities. - Added support for file attachments to the input bar.
This commit is contained in:
parent
4f250cdcb1
commit
71876e6a70
36
src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx
Normal file
36
src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { FileMetadata } from '@renderer/types'
|
||||||
|
import { Upload } from 'antd'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
files: FileMetadata[]
|
||||||
|
setFiles: (files: FileMetadata[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AttachmentPreview: FC<Props> = ({ files, setFiles }) => {
|
||||||
|
if (isEmpty(files)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Upload
|
||||||
|
listType="picture-card"
|
||||||
|
fileList={files.map((file) => ({ uid: file.id, url: 'file://' + file.path, status: 'done', name: file.name }))}
|
||||||
|
onRemove={(item) => setFiles(files.filter((file) => item.uid !== file.id))}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 10px 20px;
|
||||||
|
margin-right: 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default AttachmentPreview
|
||||||
@ -27,6 +27,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import AttachmentButton from './AttachmentButton'
|
import AttachmentButton from './AttachmentButton'
|
||||||
|
import AttachmentPreview from './AttachmentPreview'
|
||||||
import SendMessageButton from './SendMessageButton'
|
import SendMessageButton from './SendMessageButton'
|
||||||
import TokenCount from './TokenCount'
|
import TokenCount from './TokenCount'
|
||||||
|
|
||||||
@ -202,100 +203,108 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
}, [assistant])
|
}, [assistant])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container id="inputbar" className={inputFocus ? 'focus' : ''} ref={containerRef}>
|
<Container>
|
||||||
<Textarea
|
<AttachmentPreview files={files} setFiles={setFiles} />
|
||||||
value={text}
|
<InputBarContainer id="inputbar" className={inputFocus ? 'focus' : ''} ref={containerRef}>
|
||||||
onChange={(e) => setText(e.target.value)}
|
<Textarea
|
||||||
onKeyDown={handleKeyDown}
|
value={text}
|
||||||
placeholder={t('chat.input.placeholder')}
|
onChange={(e) => setText(e.target.value)}
|
||||||
autoFocus
|
onKeyDown={handleKeyDown}
|
||||||
contextMenu="true"
|
placeholder={t('chat.input.placeholder')}
|
||||||
variant="borderless"
|
autoFocus
|
||||||
rows={1}
|
contextMenu="true"
|
||||||
ref={textareaRef}
|
variant="borderless"
|
||||||
style={{ fontSize }}
|
rows={1}
|
||||||
styles={{ textarea: TextareaStyle }}
|
ref={textareaRef}
|
||||||
onFocus={() => setInputFocus(true)}
|
style={{ fontSize }}
|
||||||
onBlur={() => setInputFocus(false)}
|
styles={{ textarea: TextareaStyle }}
|
||||||
onInput={onInput}
|
onFocus={() => setInputFocus(true)}
|
||||||
disabled={searching}
|
onBlur={() => setInputFocus(false)}
|
||||||
onClick={() => searching && dispatch(setSearching(false))}
|
onInput={onInput}
|
||||||
/>
|
disabled={searching}
|
||||||
<Toolbar>
|
onClick={() => searching && dispatch(setSearching(false))}
|
||||||
<ToolbarMenu>
|
/>
|
||||||
<Tooltip placement="top" title={t('chat.input.new_topic')} arrow>
|
<Toolbar>
|
||||||
<ToolbarButton type="text" onClick={addNewTopic}>
|
<ToolbarMenu>
|
||||||
<PlusCircleOutlined />
|
<Tooltip placement="top" title={t('chat.input.new_topic')} arrow>
|
||||||
</ToolbarButton>
|
<ToolbarButton type="text" onClick={addNewTopic}>
|
||||||
</Tooltip>
|
<PlusCircleOutlined />
|
||||||
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
|
||||||
<Popconfirm
|
|
||||||
title={t('chat.input.clear.content')}
|
|
||||||
placement="top"
|
|
||||||
onConfirm={clearTopic}
|
|
||||||
okButtonProps={{ danger: true }}
|
|
||||||
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
|
||||||
okText={t('chat.input.clear')}>
|
|
||||||
<ToolbarButton type="text">
|
|
||||||
<ClearOutlined />
|
|
||||||
</ToolbarButton>
|
|
||||||
</Popconfirm>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip placement="top" title={t('chat.input.topics')} arrow>
|
|
||||||
<ToolbarButton
|
|
||||||
type="text"
|
|
||||||
onClick={() => {
|
|
||||||
!showTopics && toggleShowTopics()
|
|
||||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
|
|
||||||
}}>
|
|
||||||
<HistoryOutlined />
|
|
||||||
</ToolbarButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip placement="top" title={t('chat.input.settings')} arrow>
|
|
||||||
<ToolbarButton
|
|
||||||
type="text"
|
|
||||||
onClick={() => {
|
|
||||||
!showTopics && toggleShowTopics()
|
|
||||||
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_CHAT_SETTINGS), 0)
|
|
||||||
}}>
|
|
||||||
<ControlOutlined />
|
|
||||||
</ToolbarButton>
|
|
||||||
</Tooltip>
|
|
||||||
<AttachmentButton model={model} files={files} setFiles={setFiles} ToolbarButton={ToolbarButton} />
|
|
||||||
<Tooltip placement="top" title={expended ? t('chat.input.collapse') : t('chat.input.expand')} arrow>
|
|
||||||
<ToolbarButton type="text" onClick={onToggleExpended}>
|
|
||||||
{expended ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
|
||||||
</ToolbarButton>
|
|
||||||
</Tooltip>
|
|
||||||
<TokenCount
|
|
||||||
estimateTokenCount={estimateTokenCount}
|
|
||||||
inputTokenCount={inputTokenCount}
|
|
||||||
contextCount={contextCount}
|
|
||||||
ToolbarButton={ToolbarButton}
|
|
||||||
onClick={onNewContext}
|
|
||||||
/>
|
|
||||||
</ToolbarMenu>
|
|
||||||
<ToolbarMenu>
|
|
||||||
{generating && (
|
|
||||||
<Tooltip placement="top" title={t('chat.input.pause')} arrow>
|
|
||||||
<ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2, marginTop: 1 }}>
|
|
||||||
<PauseCircleOutlined style={{ color: 'var(--color-error)', fontSize: 20 }} />
|
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
||||||
{!generating && <SendMessageButton sendMessage={sendMessage} disabled={generating || !text} />}
|
<Popconfirm
|
||||||
</ToolbarMenu>
|
title={t('chat.input.clear.content')}
|
||||||
</Toolbar>
|
placement="top"
|
||||||
|
onConfirm={clearTopic}
|
||||||
|
okButtonProps={{ danger: true }}
|
||||||
|
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
||||||
|
okText={t('chat.input.clear')}>
|
||||||
|
<ToolbarButton type="text">
|
||||||
|
<ClearOutlined />
|
||||||
|
</ToolbarButton>
|
||||||
|
</Popconfirm>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="top" title={t('chat.input.topics')} arrow>
|
||||||
|
<ToolbarButton
|
||||||
|
type="text"
|
||||||
|
onClick={() => {
|
||||||
|
!showTopics && toggleShowTopics()
|
||||||
|
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
|
||||||
|
}}>
|
||||||
|
<HistoryOutlined />
|
||||||
|
</ToolbarButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="top" title={t('chat.input.settings')} arrow>
|
||||||
|
<ToolbarButton
|
||||||
|
type="text"
|
||||||
|
onClick={() => {
|
||||||
|
!showTopics && toggleShowTopics()
|
||||||
|
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_CHAT_SETTINGS), 0)
|
||||||
|
}}>
|
||||||
|
<ControlOutlined />
|
||||||
|
</ToolbarButton>
|
||||||
|
</Tooltip>
|
||||||
|
<AttachmentButton model={model} files={files} setFiles={setFiles} ToolbarButton={ToolbarButton} />
|
||||||
|
<Tooltip placement="top" title={expended ? t('chat.input.collapse') : t('chat.input.expand')} arrow>
|
||||||
|
<ToolbarButton type="text" onClick={onToggleExpended}>
|
||||||
|
{expended ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
||||||
|
</ToolbarButton>
|
||||||
|
</Tooltip>
|
||||||
|
<TokenCount
|
||||||
|
estimateTokenCount={estimateTokenCount}
|
||||||
|
inputTokenCount={inputTokenCount}
|
||||||
|
contextCount={contextCount}
|
||||||
|
ToolbarButton={ToolbarButton}
|
||||||
|
onClick={onNewContext}
|
||||||
|
/>
|
||||||
|
</ToolbarMenu>
|
||||||
|
<ToolbarMenu>
|
||||||
|
{generating && (
|
||||||
|
<Tooltip placement="top" title={t('chat.input.pause')} arrow>
|
||||||
|
<ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2, marginTop: 1 }}>
|
||||||
|
<PauseCircleOutlined style={{ color: 'var(--color-error)', fontSize: 20 }} />
|
||||||
|
</ToolbarButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{!generating && <SendMessageButton sendMessage={sendMessage} disabled={generating || !text} />}
|
||||||
|
</ToolbarMenu>
|
||||||
|
</Toolbar>
|
||||||
|
</InputBarContainer>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`
|
||||||
|
|
||||||
const TextareaStyle: CSSProperties = {
|
const TextareaStyle: CSSProperties = {
|
||||||
paddingLeft: 0,
|
paddingLeft: 0,
|
||||||
padding: '10px 15px 8px'
|
padding: '10px 15px 8px'
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled.div`
|
const InputBarContainer = styled.div`
|
||||||
border: 1px solid var(--color-border-soft);
|
border: 1px solid var(--color-border-soft);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -351,14 +360,23 @@ const ToolbarButton = styled(Button)`
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
color: var(--color-icon);
|
color: var(--color-icon);
|
||||||
}
|
}
|
||||||
&:hover,
|
&:hover {
|
||||||
&.active {
|
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
.anticon,
|
.anticon,
|
||||||
.iconfont {
|
.iconfont {
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.active {
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
.anticon,
|
||||||
|
.iconfont {
|
||||||
|
color: var(--color-white-soft);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default Inputbar
|
export default Inputbar
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user