Skip to content
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

⚡️ perf: Optimize the image upload size for gpt-4-vision #669

Merged
merged 7 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/FileList/EditableFileList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useResponsive } from 'antd-style';
import { memo } from 'react';
import { Flexbox } from 'react-layout-kit';

import FileItem from '@/components/FileList/FileItem';
import ImageFileItem from '@/components/FileList/ImageFileItem';

interface EditableFileListProps {
alwaysShowClose?: boolean;
Expand All @@ -24,7 +24,7 @@ const EditableFileList = memo<EditableFileListProps>(
>
<ImageGallery>
{items.map((i) => (
<FileItem alwaysShowClose={alwaysShowClose} editable={editable} id={i} key={i} />
<ImageFileItem alwaysShowClose={alwaysShowClose} editable={editable} id={i} key={i} />
))}
</ImageGallery>
</Flexbox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface FileItemProps {
onClick?: () => void;
style?: CSSProperties;
}
const FileItem = memo<FileItemProps>(({ editable, id, alwaysShowClose }) => {
const ImageFileItem = memo<FileItemProps>(({ editable, id, alwaysShowClose }) => {
const [useFetchFile, removeFile] = useFileStore((s) => [s.useFetchFile, s.removeFile]);
const IMAGE_SIZE = editable ? MIN_IMAGE_SIZE : '100%';
const { data, isLoading } = useFetchFile(id);
Expand Down Expand Up @@ -72,4 +72,4 @@ const FileItem = memo<FileItemProps>(({ editable, id, alwaysShowClose }) => {
);
});

export default FileItem;
export default ImageFileItem;
6 changes: 3 additions & 3 deletions src/components/FileList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Flexbox } from 'react-layout-kit';
import { MAX_SIZE_DESKTOP, MAX_SIZE_MOBILE } from '@/components/FileList/style';

import FileGrid from './FileGrid';
import FileItem from './FileItem';
import ImageFileItem from './ImageFileItem';

interface FileListProps {
items: string[];
Expand Down Expand Up @@ -44,13 +44,13 @@ const FileList = memo<FileListProps>(({ items }) => {
<Flexbox gap={gap}>
<FileGrid col={firstRow.length} gap={gap} max={max}>
{firstRow.map((i) => (
<FileItem id={i} key={i} />
<ImageFileItem id={i} key={i} />
))}
</FileGrid>
{lastRow.length > 0 && (
<FileGrid col={lastRow.length > 2 ? 3 : lastRow.length} gap={gap} max={max}>
{lastRow.map((i) => (
<FileItem id={i} key={i} />
<ImageFileItem id={i} key={i} />
))}
</FileGrid>
)}
Expand Down
33 changes: 33 additions & 0 deletions src/services/file.ts
mushan0x0 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
import { FileModel } from '@/database/models/file';
import { DB_File } from '@/database/schemas/files';
import { FilePreview } from '@/types/files';
import compressImage from '@/utils/compressImage';

class FileService {
private isImage(fileType: string) {
const imageRegex = /^image\//;
return imageRegex.test(fileType);
}
async uploadFile(file: DB_File) {
// 跳过图片上传测试
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

图片压缩这里测试时只能跳过了,应该很难 mock

Copy link
Contributor

@arvinxx arvinxx Dec 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以使用 LobeChat 测试工程师 来帮你写单测:

现在 LobeChat 中很多单测都是它帮忙写的: https://shareg.pt/xHPM9NJ

const isTestData = file.size === 1;
if (this.isImage(file.fileType) && !isTestData) {
return this.uploadImageFile(file);
}

Check warning on line 16 in src/services/file.ts

View check run for this annotation

Codecov / codecov/patch

src/services/file.ts#L15-L16

Added lines #L15 - L16 were not covered by tests
// save to local storage
// we may want to save to a remote server later
return FileModel.create(file);
}

async uploadImageFile(file: DB_File) {
// 加载图片
const url = file.url || URL.createObjectURL(new Blob([file.data]));
const img = new Image();
img.src = url;
await (() =>
new Promise((resolve) => {
img.addEventListener('load', resolve);
}))();

// 压缩图片
const fileType = 'image/webp';
const base64String = compressImage({
img,
type: fileType,
});
const binaryString = atob(base64String.split('base64,')[1]);
const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
file.data = uint8Array.buffer;

return FileModel.create(file);
}

Check warning on line 43 in src/services/file.ts

View check run for this annotation

Codecov / codecov/patch

src/services/file.ts#L23-L43

Added lines #L23 - L43 were not covered by tests

async removeFile(id: string) {
return FileModel.delete(id);
}
Expand Down
29 changes: 29 additions & 0 deletions src/utils/compressImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const compressImage = ({ img, type = 'image/webp' }: { img: HTMLImageElement; type?: string }) => {
// 设置最大宽高
const maxWidth = 2160;
const maxHeight = 2160;
let width = img.width;
let height = img.height;

if (width > height && width > maxWidth) {
// 如果图片宽度大于高度且大于最大宽度限制
width = maxWidth;
height = Math.round((maxWidth / img.width) * img.height);
} else if (height > width && height > maxHeight) {
// 如果图片高度大于宽度且大于最大高度限制
height = maxHeight;
width = Math.round((maxHeight / img.height) * img.width);
}

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;

canvas.width = width;
canvas.height = height;

ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, width, height);

return canvas.toDataURL(type);
};

Check warning on line 27 in src/utils/compressImage.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/compressImage.ts#L2-L27

Added lines #L2 - L27 were not covered by tests

export default compressImage;