feat: add upload image for button, and can preview and remove image by one click.#19
feat: add upload image for button, and can preview and remove image by one click.#19MrXnneHang wants to merge 7 commits intoOpen-LLM-VTuber:mainfrom
Conversation
…chat history (#2) * Move attachment previews above input * Allow footer to grow for attachments * Style attachment preview container * Prevent attachment preview from shrinking * Move attachment previews to dedicated row and lift footer when attachments present (#3) * Adjust footer attachment layout * Refine footer lift and default window size
* Add attachment remove control * make image larger, rise by 0.1vh * 采用反向算法
* Add attachment preview modal * Use dialog for attachment preview
* Show image thumbnails in chat history * Show text with thumbnails in chat history * Place image thumbnails above text in history
Summary of ChangesHello @MrXnneHang, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求主要引入了用户界面层面的图片附件功能,极大地增强了用户与应用程序的交互能力。它允许用户在发送消息时附带图片,并提供了直观的预览、管理和在聊天历史中显示这些图片的方式。此外,还对应用程序的窗口管理进行了优化,使其默认尺寸更具响应性。尽管后端目前不支持图片持久化,但前端的实现为未来的全面支持奠定了基础。 Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
| <HStack spacing="2" flexWrap="wrap"> | ||
| {attachedImages.map((image, index) => ( | ||
| <Box | ||
| key={`${image.data}-${index}`} |
There was a problem hiding this comment.
在 React 中,使用列表的索引 index 或者不稳定的数据(如这里的 base64 字符串 image.data)作为 key 是一种反模式,尤其是在列表项可以被增删的情况下。这可能会导致渲染问题和性能下降。
建议在上传图片时为每个图片生成一个唯一的客户端 ID,并用它作为 key。
你可以在 src/renderer/src/hooks/footer/use-text-input.tsx 中做如下修改:
-
更新
attachedImages的 state 类型,使其包含一个客户端 ID:const [attachedImages, setAttachedImages] = useState<(ImagePayload & { clientId: string })[]>([]);
-
在
handleAttachFiles函数中,当图片被读取时,为其生成一个唯一的 ID:// ... try { const dataUrl = await readFileAsDataUrl(file); newImages.push({ clientId: crypto.randomUUID(), // 生成唯一 ID source: 'upload', data: dataUrl, mime_type: file.type || 'image/*', }); } // ...
-
然后在这里,你就可以使用这个稳定的
clientId作为key:
| key={`${image.data}-${index}`} | |
| key={image.clientId} |
| <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.
这里的 IconButton 样式是内联定义的,并且包含中文注释。为了更好的代码组织和可维护性,建议将这些样式提取到 src/renderer/src/components/footer/footer-styles.tsx 文件中,并移除代码中的注释。
你可以在 footer-styles.tsx 的 FooterStyles 接口和 footerStyles.footer 对象中添加一个新的样式属性 removeAttachmentButton:
// 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" },
},
},
// ...
}然后在这里使用它,这样代码会更整洁。
| <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); | |
| }} | |
| /> | |
| <IconButton | |
| aria-label={t('footer.removeAttachment')} | |
| icon={<BsX />} | |
| {...footerStyles.footer.removeAttachmentButton} | |
| onClick={(event) => { | |
| event.stopPropagation(); | |
| handleRemoveAttachment(index); | |
| }} | |
| /> |
| <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> |
There was a problem hiding this comment.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 885675470e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| wsContext.sendMessage({ | ||
| type: 'text-input', | ||
| text: inputText.trim(), | ||
| text: trimmedText, |
There was a problem hiding this comment.
Send non-empty text for image-only messages
handleSend appends a fallback label for image-only input (messageText) but still sends text: trimmedText, which is an empty string when the user submits only attachments. In environments where the backend/history payload does not retain image blobs, this creates empty persisted user turns that disappear on reload because ChatHistoryPanel filters out messages with no content and no images; sending a non-empty marker text with the outbound payload avoids silently dropping those turns from history.
Useful? React with 👍 / 👎.
似乎原来的上传附件的 button click 事件没写。
这里添加了:
因为我后端对话历史是不存 image 的,所以:
clamp(1vh, calc(110px - 5vh), 1000vh)':这个只是做了近似,某些分辨率,在某些窗口大小下可能表现不佳。维护者似乎不在 =-=
但 PR 我先挂在这了。