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:
kangfenmao 2024-09-13 14:47:05 +08:00
parent 4f250cdcb1
commit 71876e6a70
2 changed files with 139 additions and 85 deletions

View 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

View File

@ -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