feat: update release notes and add image preview component
- Updated release notes to reflect changes including image preview and download. - Added interactive image preview component with toolbar for rotation, zooming, and downloading. - Added support for image previews in Markdown rendering. - Added functionality to download files from a URL with automatic filename detection and handling.
This commit is contained in:
parent
85152cbcd7
commit
d41667b599
@ -65,10 +65,7 @@ afterSign: scripts/notarize.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
本次更新:
|
||||
支持更多的数学公式显示
|
||||
可以通过搜索快速切换模型
|
||||
增加 Bolt 小程序
|
||||
修复 Azure OpenAI 默认模型无法使用问题
|
||||
支持图片的预览和下载
|
||||
近期更新:
|
||||
增加 WebDAV 备份功能 by @DrayChou
|
||||
增加话题历史记录
|
||||
|
||||
62
src/renderer/src/pages/home/Markdown/ImagePreview.tsx
Normal file
62
src/renderer/src/pages/home/Markdown/ImagePreview.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import {
|
||||
DownloadOutlined,
|
||||
RotateLeftOutlined,
|
||||
RotateRightOutlined,
|
||||
SwapOutlined,
|
||||
UndoOutlined,
|
||||
ZoomInOutlined,
|
||||
ZoomOutOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { download } from '@renderer/utils/download'
|
||||
import { Image, Space } from 'antd'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface ImagePreviewProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
src: string
|
||||
}
|
||||
|
||||
const ImagePreview: React.FC<ImagePreviewProps> = ({ src }) => {
|
||||
return (
|
||||
<Image
|
||||
src={src}
|
||||
preview={{
|
||||
toolbarRender: (
|
||||
_,
|
||||
{
|
||||
transform: { scale },
|
||||
actions: { onFlipY, onFlipX, onRotateLeft, onRotateRight, onZoomOut, onZoomIn, onReset }
|
||||
}
|
||||
) => (
|
||||
<ToobarWrapper size={12} className="toolbar-wrapper">
|
||||
<SwapOutlined rotate={90} onClick={onFlipY} />
|
||||
<SwapOutlined onClick={onFlipX} />
|
||||
<RotateLeftOutlined onClick={onRotateLeft} />
|
||||
<RotateRightOutlined onClick={onRotateRight} />
|
||||
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
|
||||
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
|
||||
<UndoOutlined onClick={onReset} />
|
||||
<DownloadOutlined onClick={() => download(src)} />
|
||||
</ToobarWrapper>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ToobarWrapper = styled(Space)`
|
||||
padding: 0px 24px;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 100px;
|
||||
.anticon {
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.anticon:hover {
|
||||
opacity: 0.3;
|
||||
}
|
||||
`
|
||||
|
||||
export default ImagePreview
|
||||
@ -14,6 +14,7 @@ import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
|
||||
import CodeBlock from './CodeBlock'
|
||||
import ImagePreview from './ImagePreview'
|
||||
import Link from './Link'
|
||||
|
||||
interface Props {
|
||||
@ -25,7 +26,8 @@ const remarkPlugins = [remarkMath, remarkGfm]
|
||||
|
||||
const components = {
|
||||
code: CodeBlock,
|
||||
a: Link
|
||||
a: Link,
|
||||
img: ImagePreview
|
||||
}
|
||||
|
||||
const Markdown: FC<Props> = ({ message }) => {
|
||||
|
||||
61
src/renderer/src/utils/download.ts
Normal file
61
src/renderer/src/utils/download.ts
Normal file
@ -0,0 +1,61 @@
|
||||
export const download = (url: string) => {
|
||||
fetch(url)
|
||||
.then((response) => {
|
||||
// 尝试从Content-Disposition头获取文件名
|
||||
const contentDisposition = response.headers.get('Content-Disposition')
|
||||
let filename = 'download' // 默认文件名
|
||||
|
||||
if (contentDisposition) {
|
||||
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i)
|
||||
if (filenameMatch) {
|
||||
filename = filenameMatch[1]
|
||||
}
|
||||
}
|
||||
|
||||
// 如果URL中有文件名,使用URL中的文件名
|
||||
const urlFilename = url.split('/').pop()
|
||||
if (urlFilename && urlFilename.includes('.')) {
|
||||
filename = urlFilename
|
||||
}
|
||||
|
||||
// 如果文件名没有后缀,根据Content-Type添加后缀
|
||||
if (!filename.includes('.')) {
|
||||
const contentType = response.headers.get('Content-Type')
|
||||
const extension = getExtensionFromMimeType(contentType)
|
||||
filename += extension
|
||||
}
|
||||
|
||||
// 添加时间戳以确保文件名唯一
|
||||
const timestamp = Date.now()
|
||||
const finalFilename = `${timestamp}_${filename}`
|
||||
|
||||
return response.blob().then((blob) => ({ blob, finalFilename }))
|
||||
})
|
||||
.then(({ blob, finalFilename }) => {
|
||||
const blobUrl = URL.createObjectURL(new Blob([blob]))
|
||||
const link = document.createElement('a')
|
||||
link.href = blobUrl
|
||||
link.download = finalFilename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
link.remove()
|
||||
})
|
||||
}
|
||||
|
||||
// 辅助函数:根据MIME类型获取文件扩展名
|
||||
function getExtensionFromMimeType(mimeType: string | null): string {
|
||||
if (!mimeType) return '.bin' // 默认二进制文件扩展名
|
||||
|
||||
const mimeToExtension: { [key: string]: string } = {
|
||||
'image/jpeg': '.jpg',
|
||||
'image/png': '.png',
|
||||
'image/gif': '.gif',
|
||||
'application/pdf': '.pdf',
|
||||
'text/plain': '.txt',
|
||||
'application/msword': '.doc',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx'
|
||||
}
|
||||
|
||||
return mimeToExtension[mimeType] || '.bin'
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user