Skip to content

Commit

Permalink
Add multiple image compression options (#513)
Browse files Browse the repository at this point in the history
* add compress options in settings

* add option for maxWidthOrHeight

* fix missing dependencies

* fieldset for same group

* using option object instead of flat vars

* split form control

* update helper texts
  • Loading branch information
mingming-ma authored Mar 23, 2024
1 parent 9897e9d commit 62da804
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 9 deletions.
16 changes: 14 additions & 2 deletions src/components/OptionsButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useCopyToClipboard } from "react-use";
import { ChatCraftChat } from "../lib/ChatCraftChat";
import { useUser } from "../hooks/use-user";
import { useAlert } from "../hooks/use-alert";
import { useSettings } from "../hooks/use-settings";
import ShareModal from "./ShareModal";
import { download, compressImageToBase64 } from "../lib/utils";
import theme from "../theme";
Expand Down Expand Up @@ -91,6 +92,7 @@ function OptionsButton({
const { info, error } = useAlert();
const [, copyToClipboard] = useCopyToClipboard();
const fileInputRef = useRef<HTMLInputElement>(null);
const { settings } = useSettings();

const handleFileChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -105,7 +107,11 @@ function OptionsButton({
const file = files[i];
if (file.type.startsWith("image/")) {
onFileSelected("");
compressImageToBase64(file)
compressImageToBase64(file, {
compressionFactor: settings.compressionFactor,
maxSizeMB: settings.maxCompressedFileSizeMB,
maxWidthOrHeight: settings.maxImageDimension,
})
.then((base64) => onFileSelected(base64))
.catch((err) => {
console.error(err);
Expand All @@ -123,7 +129,13 @@ function OptionsButton({
event.target.value = "";
}
},
[error, onFileSelected]
[
error,
onFileSelected,
settings.compressionFactor,
settings.maxCompressedFileSizeMB,
settings.maxImageDimension,
]
);

const handleAttachFiles = useCallback(() => {
Expand Down
87 changes: 87 additions & 0 deletions src/components/PreferencesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,93 @@ function PreferencesModal({ isOpen, onClose, finalFocusRef }: PreferencesModalPr
</FormHelperText>
</FormControl>

<FormControl as="fieldset">
<FormLabel as="legend">Image Compression</FormLabel>
<Stack>
<Box px="5">
<FormControl>
<FormLabel>
Maximum file size after compression: {settings.maxCompressedFileSizeMB} (MB)
</FormLabel>
<Slider
id="max-compressed-file-size"
value={settings.maxCompressedFileSizeMB}
onChange={(value) =>
setSettings({ ...settings, maxCompressedFileSizeMB: value })
}
min={1}
max={20}
step={1}
>
<SliderTrack>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb />
</Slider>
<FormErrorMessage>
Maximum file size must be between 1 and 20 MB.
</FormErrorMessage>
<FormHelperText>
After compression, each attached image will be under your chosen maximum
file size (1-20 MB).
</FormHelperText>
</FormControl>
</Box>
<Box px="5">
<FormControl>
<FormLabel>
Maximum image dimension: {settings.maxImageDimension} (px)
</FormLabel>
<Slider
id="max-image-dimension"
value={settings.maxImageDimension}
onChange={(value) => setSettings({ ...settings, maxImageDimension: value })}
min={16}
max={2048}
step={16}
>
<SliderTrack>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb />
</Slider>
<FormErrorMessage>
Maximum Image dimension must be between 16 and 2048
</FormErrorMessage>
<FormHelperText>
Your compressed image&apos;s maximum width or height will be within the
dimension you choose (16-2048 pixels).
</FormHelperText>
</FormControl>
</Box>
<Box px="5">
<FormControl>
<FormLabel>Compression factor: {settings.compressionFactor}</FormLabel>
<Slider
id="compression-factor"
value={settings.compressionFactor}
onChange={(value) => setSettings({ ...settings, compressionFactor: value })}
min={0.1}
max={1}
step={0.1}
>
<SliderTrack>
<SliderFilledTrack />
</SliderTrack>
<SliderThumb />
</Slider>
<FormErrorMessage>
Compression factor must be between 0.1 and 1.0
</FormErrorMessage>
<FormHelperText>
Set the maximum file size based on the original size multiplied by the
factor you choose (0.1-1.0).
</FormHelperText>
</FormControl>
</Box>
</Stack>
</FormControl>

<FormControl>
<FormLabel>
When writing a prompt, press <Kbd>Enter</Kbd> to...
Expand Down
11 changes: 9 additions & 2 deletions src/components/PromptForm/DesktopPromptForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,15 @@ function DesktopPromptForm({

const processImages = (imageFiles: File[]) => {
setInputImageUrls((prevImageUrls) => [...prevImageUrls, ...imageFiles.map(() => "")]);

Promise.all(imageFiles.map((file) => compressImageToBase64(file)))
Promise.all(
imageFiles.map((file) =>
compressImageToBase64(file, {
compressionFactor: settings.compressionFactor,
maxSizeMB: settings.maxCompressedFileSizeMB,
maxWidthOrHeight: settings.maxImageDimension,
})
)
)
.then((base64Strings) => {
setInputImageUrls((prevImageUrls) => {
const newImageUrls = [...prevImageUrls];
Expand Down
6 changes: 6 additions & 0 deletions src/lib/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export type Settings = {
textToSpeech: TextToSpeechSettings;
providers: ProviderData;
currentProvider: ChatCraftProvider;
compressionFactor: number;
maxCompressedFileSizeMB: number;
maxImageDimension: number;
};

export const defaults: Settings = {
Expand All @@ -56,6 +59,9 @@ export const defaults: Settings = {
},
providers: {},
currentProvider: new FreeModelProvider(),
compressionFactor: 1,
maxCompressedFileSizeMB: 20,
maxImageDimension: 2048,
};

export const key = "settings";
Expand Down
20 changes: 15 additions & 5 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,28 @@ export const screenshotElement = (element: HTMLElement): Promise<Blob> => {
);
};

interface ImageCompressionOptions {
compressionFactor?: number;
maxSizeMB?: number;
maxWidthOrHeight?: number;
}
// Make sure image's size is within 20MB and 2048x2048 resolution
// https://platform.openai.com/docs/guides/vision/is-there-a-limit-to-the-size-of-the-image-i-can-upload
export const compressImageToBase64 = (file: File): Promise<string> => {
const imageCompressionOptions = {
maxSizeMB: 20,
maxWidthOrHeight: 2048,
export const compressImageToBase64 = (
file: File,
options: ImageCompressionOptions = {}
): Promise<string> => {
const { compressionFactor = 1, maxSizeMB = 20, maxWidthOrHeight = 2048 } = options;

const libOptions = {
maxSizeMB: Math.min((file.size / 1024 / 1024) * compressionFactor, maxSizeMB, 20),
maxWidthOrHeight: Math.min(maxWidthOrHeight, 2048),
};

return import("browser-image-compression")
.then((imageCompressionModule) => {
const imageCompression = imageCompressionModule.default;
return imageCompression(file, imageCompressionOptions);
return imageCompression(file, libOptions);
})
.then((compressedFile: File) => {
return new Promise<string>((resolve, reject) => {
Expand Down

0 comments on commit 62da804

Please sign in to comment.