mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-05 12:29:44 +08:00
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
acba7d56eb
commit
e5100e9547
@ -65,10 +65,7 @@ afterSign: scripts/notarize.js
|
|||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
本次更新:
|
本次更新:
|
||||||
支持更多的数学公式显示
|
支持图片的预览和下载
|
||||||
可以通过搜索快速切换模型
|
|
||||||
增加 Bolt 小程序
|
|
||||||
修复 Azure OpenAI 默认模型无法使用问题
|
|
||||||
近期更新:
|
近期更新:
|
||||||
增加 WebDAV 备份功能 by @DrayChou
|
增加 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 remarkMath from 'remark-math'
|
||||||
|
|
||||||
import CodeBlock from './CodeBlock'
|
import CodeBlock from './CodeBlock'
|
||||||
|
import ImagePreview from './ImagePreview'
|
||||||
import Link from './Link'
|
import Link from './Link'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -25,7 +26,8 @@ const remarkPlugins = [remarkMath, remarkGfm]
|
|||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
code: CodeBlock,
|
code: CodeBlock,
|
||||||
a: Link
|
a: Link,
|
||||||
|
img: ImagePreview
|
||||||
}
|
}
|
||||||
|
|
||||||
const Markdown: FC<Props> = ({ message }) => {
|
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…
Reference in New Issue
Block a user