-
Notifications
You must be signed in to change notification settings - Fork 97
feat: add upload image for button, and can preview and remove image by one click. #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
13eca24
0e49cb4
6a80af4
ee1aa69
ec3d8b5
8856754
584eae1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,23 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* eslint-disable react/require-default-props */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Box, Textarea, IconButton, HStack, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Box, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Textarea, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IconButton, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HStack, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Image, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@chakra-ui/react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { BsMicFill, BsMicMuteFill, BsPaperclip } from 'react-icons/bs'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { BsMicFill, BsMicMuteFill, BsPaperclip, BsX } from 'react-icons/bs'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { IoHandRightSharp } from 'react-icons/io5'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { FiChevronDown } from 'react-icons/fi'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { memo } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { memo, useRef, useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useTranslation } from 'react-i18next'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { InputGroup } from '@/components/ui/input-group'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DialogRoot, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DialogContent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DialogCloseTrigger, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DialogBody, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@/components/ui/dialog'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { footerStyles } from './footer-styles'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import AIStateIndicator from './ai-state-indicator'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useFooter } from '@/hooks/footer/use-footer'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -35,6 +45,8 @@ interface MessageInputProps { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionStart: () => void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionEnd: () => void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onAttachFiles: (files: FileList | null) => void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attachedCount: number | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Reusable components | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -81,55 +93,151 @@ const MessageInput = memo(({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onKeyDown, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionStart, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionEnd, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onAttachFiles, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attachedCount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: MessageInputProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { t } = useTranslation(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fileInputRef = useRef<HTMLInputElement | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <InputGroup flex={1}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box position="relative" width="100%"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <IconButton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label="Attach file" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variant="ghost" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...footerStyles.footer.attachButton} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <BsPaperclip size="24" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </IconButton> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Textarea | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={value} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={onChange} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onKeyDown={onKeyDown} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionStart={onCompositionStart} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionEnd={onCompositionEnd} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder={t('footer.typeYourMessage')} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...footerStyles.footer.input} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </InputGroup> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box flex={1} minW="0" display="flex" flexDirection="column" gap="2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <InputGroup> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box position="relative" width="100%"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <IconButton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label="Attach file" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variant="ghost" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...footerStyles.footer.attachButton} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => fileInputRef.current?.click()} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <BsPaperclip size="24" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </IconButton> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ref={fileInputRef} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="file" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accept="image/*" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| multiple | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={{ display: 'none' }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(event) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onAttachFiles(event.target.files); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event.target.value = ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label={t('footer.attachFile')} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Textarea | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={value} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={onChange} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onKeyDown={onKeyDown} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionStart={onCompositionStart} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionEnd={onCompositionEnd} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder={t('footer.typeYourMessage')} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...footerStyles.footer.input} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {attachedCount > 0 && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position="absolute" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| top="2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| right="2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fontSize="xs" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color="whiteAlpha.700" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {t('footer.attachmentsCount', { count: attachedCount })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </InputGroup> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MessageInput.displayName = 'MessageInput'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Main component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function Footer({ isCollapsed = false, onToggle }: FooterProps): JSX.Element { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { t } = useTranslation(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inputValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleInputChange, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleKeyPress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleCompositionStart, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleCompositionEnd, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleAttachFiles, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attachedImages, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleRemoveAttachment, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleInterrupt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleMicToggle, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| micOn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = useFooter(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [previewImage, setPreviewImage] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box {...footerStyles.footer.container(isCollapsed)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box {...footerStyles.footer.container(isCollapsed, attachedImages.length > 0)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ToggleButton isCollapsed={isCollapsed} onToggle={onToggle} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box pt="0" px="4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <HStack width="100%" gap={4}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {attachedImages.length > 0 && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width="100%" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minW="0" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bg="gray.700" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| borderRadius="12px" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| px="3" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| py="2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mb="3" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <HStack spacing="2" flexWrap="wrap"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {attachedImages.map((image, index) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={`${image.data}-${index}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position="relative" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| borderRadius="md" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| overflow="hidden" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border="1px solid" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| borderColor="whiteAlpha.300" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cursor="zoom-in" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| role="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tabIndex={0} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => setPreviewImage(image.data)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onKeyDown={(event) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (event.key === 'Enter' || event.key === ' ') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event.preventDefault(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setPreviewImage(image.data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Image | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| src={image.data} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alt={t('footer.attachFile')} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| boxSize="128px" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectFit="cover" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <IconButton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label={t('footer.removeAttachment')} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| icon={<BsX />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size="xs" // 先用 xs 当基准 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w="18px" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| h="18px" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minW="18px" // IconButton 默认有 minW,不设会缩不下去 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p="0" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fontSize="12px" // 控制图标大小(icon 会吃到 fontSize) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position="absolute" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| top="1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| right="1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| borderRadius="full" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bg="blackAlpha.700" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color="whiteAlpha.900" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _hover={{ bg: 'blackAlpha.800' }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={(event) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event.stopPropagation(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleRemoveAttachment(index); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里的 你可以在 // In src/renderer/src/components/footer/footer-styles.tsx
// ...
interface FooterStyles {
// ...
removeAttachmentButton: SystemStyleObject
}
export const footerStyles: {
footer: FooterStyles
// ...
} = {
footer: {
// ...
removeAttachmentButton: {
size: "xs",
w: "18px",
h: "18px",
minW: "18px",
p: "0",
fontSize: "12px",
position: "absolute",
top: "1",
right: "1",
borderRadius: "full",
bg: "blackAlpha.700",
color: "whiteAlpha.900",
_hover: { bg: "blackAlpha.800" },
},
},
// ...
}然后在这里使用它,这样代码会更整洁。
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </HStack> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <HStack width="100%" gap={4} align="flex-start"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box flexShrink={0}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box mb="1.5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <AIStateIndicator /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -146,9 +254,34 @@ function Footer({ isCollapsed = false, onToggle }: FooterProps): JSX.Element { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onKeyDown={handleKeyPress} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionStart={handleCompositionStart} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCompositionEnd={handleCompositionEnd} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onAttachFiles={handleAttachFiles} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attachedCount={attachedImages.length} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </HStack> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DialogRoot | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| open={Boolean(previewImage)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onOpenChange={(details) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!details.open) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setPreviewImage(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DialogContent bg="gray.900" maxW="80vw" w="fit-content"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DialogCloseTrigger /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DialogBody p="4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {previewImage && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Image | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| src={previewImage} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alt={t('footer.previewAttachment')} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxH="80vh" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxW="80vw" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectFit="contain" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </DialogBody> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </DialogContent> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </DialogRoot> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+287
to
+309
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在 React 中,使用列表的索引
index或者不稳定的数据(如这里的 base64 字符串image.data)作为key是一种反模式,尤其是在列表项可以被增删的情况下。这可能会导致渲染问题和性能下降。建议在上传图片时为每个图片生成一个唯一的客户端 ID,并用它作为
key。你可以在
src/renderer/src/hooks/footer/use-text-input.tsx中做如下修改:更新
attachedImages的 state 类型,使其包含一个客户端 ID:在
handleAttachFiles函数中,当图片被读取时,为其生成一个唯一的 ID:然后在这里,你就可以使用这个稳定的
clientId作为key: