Skip to content

Commit

Permalink
Added possibility to open image
Browse files Browse the repository at this point in the history
Some refactoring
  • Loading branch information
wladimiiir committed Nov 4, 2024
1 parent cf836b2 commit 0350d72
Show file tree
Hide file tree
Showing 12 changed files with 56 additions and 46 deletions.
17 changes: 11 additions & 6 deletions src/main/ipcHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { dialog, ipcMain } from 'electron';
import { v4 as uuidv4 } from 'uuid';
import { dialog, ipcMain, shell } from 'electron';
import path from 'path';
import fs from 'fs/promises';
import { Image, Settings } from '@shared/types';
Expand All @@ -19,13 +20,13 @@ const scanFolder = async (store: Store, folderPath: string, includeSubdirectorie
if (entry.isDirectory() && includeSubdirectories) {
await scanRecursively(imagePath);
} else if (entry.isFile() && imageExtensions.includes(path.extname(entry.name).toLowerCase())) {
let image = images.find((image) => image.id === imagePath);
let image = images.find((image) => image.path === imagePath);
if (image) {
image.processing = true;
} else {
image = {
id: imagePath,
src: `file://${imagePath}`,
id: uuidv4(),
path: imagePath,
caption: '',
tags: [],
processing: true,
Expand Down Expand Up @@ -105,10 +106,14 @@ export const setupIpcHandlers = (webContents: Electron.WebContents, store: Store

ipcMain.handle('remove-images-in-folder', async (_event, folderPath: string) => {
const images = store.getImages();
const imagesToRemove = images.filter((image) => image.id.startsWith(folderPath));
const updatedImages = images.filter((image) => !image.id.startsWith(folderPath));
const imagesToRemove = images.filter((image) => image.path.startsWith(folderPath));
const updatedImages = images.filter((image) => !image.path.startsWith(folderPath));
store.setImages(updatedImages);
modelManager.removeQueuedImages(imagesToRemove);
return updatedImages;
});

ipcMain.handle('open-file', async (_event, filePath: string) => {
shell.showItemInFolder(filePath);
});
};
8 changes: 4 additions & 4 deletions src/main/modelManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,22 @@ export class ModelManager {

if (image) {
try {
console.log(`Generating caption for image ${image.id}`);
console.log(`Generating caption for image ${image.path}`);
const info = await this.generateImageInfo(image);
console.log(`Generated caption for image ${image.id}: ${info.caption}, tags: ${info.tags}, total cost: $${this.totalCost.toFixed(4)}`);
console.log(`Generated caption for image ${image.path}: ${info.caption}, tags: ${info.tags}, total cost: $${this.totalCost.toFixed(4)}`);
image.caption = info.caption;
image.tags = info.tags;
image.processing = false;
eventManager.emit('update-image', image);

this.totalCost += info.cost;
} catch (error) {
console.error(`Failed to generate caption for image ${image.id}:`, error);
console.error(`Failed to generate caption for image ${image.path}:`, error);
// add again to the queue as the last one
if (retryCount < MAX_RETRY_COUNT) {
this.queue.push({ image, retryCount: retryCount + 1 });
} else {
console.error(`Failed to generate caption for image ${image.id} after ${MAX_RETRY_COUNT} retries.`);
console.error(`Failed to generate caption for image ${image.path} after ${MAX_RETRY_COUNT} retries.`);
image.caption = 'Unknown';
image.tags = [];
image.processing = false;
Expand Down
4 changes: 2 additions & 2 deletions src/main/models/ollamaModelProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class OllamaModelProvider implements ModelProvider {
});

if (!response.ok) {
throw new Error(`Failed to generate caption for image ${image.id}: ${response.status} ${response.statusText}`);
throw new Error(`Failed to generate caption for image ${image.path}: ${response.status} ${response.statusText}`);
}

const data = await response.json();
Expand Down Expand Up @@ -85,7 +85,7 @@ export class OllamaModelProvider implements ModelProvider {
}

private async getBase64Image(image: Image): Promise<string> {
const imageBuffer = await fs.readFile(image.src.replace('file://', ''));
const imageBuffer = await fs.readFile(image.path);
return imageBuffer.toString('base64');
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/models/openAIModelProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class OpenAIModelProvider implements ModelProvider {
return !!config.openAIBaseURL && !!config.openAIApiKey && !!config.model;
}

async getModels(_settings: Settings): Promise<string[]> {
async getModels(): Promise<string[]> {
return ['gpt-4o', 'gpt-4-turbo'];
}

Expand All @@ -75,8 +75,8 @@ export class OpenAIModelProvider implements ModelProvider {
}

private async getBase64Image(image: Image): Promise<string> {
const imageBuffer = await fs.readFile(image.src.replace('file://', ''));
return `data:image/${image.src.split('.').pop()};base64,${imageBuffer.toString('base64')}`;
const imageBuffer = await fs.readFile(image.path);
return `data:image/${image.path.split('.').pop()};base64,${imageBuffer.toString('base64')}`;
}

private getPromptCost(model: string, promptTokens: number, completionTokens: number) {
Expand Down
1 change: 1 addition & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const api = {
},
getModels: (settings: Settings): Promise<string[]> => ipcRenderer.invoke('get-models', settings),
removeImagesInFolder: (folderPath: string): Promise<Image[]> => ipcRenderer.invoke('remove-images-in-folder', folderPath),
openFile: (path: string): Promise<void> => ipcRenderer.invoke('open-file', path),
};

// Use `contextBridge` APIs to expose Electron APIs to
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/components/FolderSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const FolderSettings = () => {

// Process each image path to build the tree structure
images.forEach((image) => {
const imagePath = image.id;
const imagePath = image.path;
// Handle Windows drive letters and root paths
const parts = imagePath.split(path.sep);
const dirParts = parts.slice(0, -1); // Skip the last part (filename)
Expand Down
26 changes: 10 additions & 16 deletions src/renderer/src/components/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ const Home = () => {

const handleSearch = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
// The filtering is now done in the filteredImages variable
};

const [isAddFolderModalOpen, setIsAddFolderModalOpen] = useState(false);
Expand All @@ -62,15 +61,10 @@ const Home = () => {
}
};

const filteredImages = images
.filter(
(image) =>
image.caption.toLowerCase().includes(searchQuery.toLowerCase()) || image.tags.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase())),
)
.map((image) => ({
...image,
src: image.src.startsWith('file://') || image.src.startsWith('http://') || image.src.startsWith('https://') ? image.src : `file://${image.src}`,
}));
const filteredImages = images.filter(
(image) =>
image.caption.toLowerCase().includes(searchQuery.toLowerCase()) || image.tags.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase())),
);

const indexOfLastImage = currentPage * imagesPerPage;
const indexOfFirstImage = indexOfLastImage - imagesPerPage;
Expand All @@ -88,27 +82,27 @@ const Home = () => {
<div className="relative">
<input
type="text"
placeholder="Search your photos..."
placeholder="Search your photos by caption or tag..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-teal-500"
/>
<HiSearch className="absolute left-3 top-2.5 text-gray-400" />
</div>
</form>
<button
onClick={() => setIsAddFolderModalOpen(true)}
className="flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-teal-600 hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
className="flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-teal-600 hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500"
>
<HiFolderAdd className="mr-2 h-5 w-5" />
Add Folder
Add folder
</button>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
onClick={() => navigate('/folder-settings')}
className="p-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
className="p-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500"
>
<HiCog className="h-5 w-5" />
</button>
Expand All @@ -135,7 +129,7 @@ const Home = () => {
{currentImages.map((image) => (
<div key={image.id} className="bg-white rounded-lg shadow-md overflow-hidden">
<img
src={image.src}
src={`file://${image.path}`}
alt={image.caption}
className="w-full h-48 object-cover transition-transform duration-300 ease-in-out hover:scale-110 cursor-pointer"
onClick={() => setSelectedImage(image)}
Expand Down
16 changes: 12 additions & 4 deletions src/renderer/src/components/ImageDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Image } from '@shared/types';
import { HiOutlineRefresh } from 'react-icons/hi';
import { HiOutlineRefresh, HiOutlineFolder } from 'react-icons/hi';
import { Tooltip } from 'react-tooltip';

type Props = {
Expand All @@ -11,10 +11,18 @@ const ImageDetails = ({ image }: Props) => {
await window.api.generateImageCaption(image);
};

const handleOpenDirectory = (): void => {
window.api.openFile(image.path);
};

return (
<div className="flex flex-col bg-white p-4 rounded-lg shadow-lg max-w-3xl mx-auto relative max-h-[calc(100vh-5rem)] overflow-y-auto">
<img src={image.src} alt={image.caption} className="w-full h-auto" />
<div className="mt-8">
<img src={`file://${image.path}`} alt={image.caption} className="w-full h-auto" />
<div className="mt-2">
<div className="mb-4 flex items-center text-sm text-gray-600 cursor-pointer hover:text-gray-800" onClick={handleOpenDirectory}>
<HiOutlineFolder className="mr-2" />
<span>{image.path}</span>
</div>
<div className="flex items-center">
<div className="text-sm flex-grow">{image.processing ? 'Processing...' : image.caption}</div>
<div className={`${image.processing ? '' : 'cursor-pointer'} ml-4 shrink-0`}>
Expand All @@ -26,7 +34,7 @@ const ImageDetails = ({ image }: Props) => {
/>
</div>
</div>
<div className="mt-4 flex flex-wrap gap-2">
<div className="mt-5 flex flex-wrap gap-2">
{image.tags.map((tag, index) => (
<span key={index} className="px-2 py-1 bg-gray-200 text-xs rounded-full">
{tag}
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/components/Pager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const Pager = ({ currentPage, totalPages, itemsPerPage, onItemsPerPageChange, on
onItemsPerPageChange(Number(e.target.value));
handlePageChange(1);
}}
className="px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
className="px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-teal-500"
>
<option value={10}>10</option>
<option value={25}>25</option>
Expand All @@ -37,14 +37,14 @@ const Pager = ({ currentPage, totalPages, itemsPerPage, onItemsPerPageChange, on
<button
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
className="px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-teal-600 hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
className="px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-teal-600 hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 disabled:opacity-50"
>
<HiArrowLeft className="h-5 w-5" />
</button>
<button
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
className="px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-teal-600 hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
className="px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-teal-600 hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 disabled:opacity-50"
>
<HiArrowRight className="h-5 w-5" />
</button>
Expand Down
12 changes: 6 additions & 6 deletions src/renderer/src/components/SettingsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const SettingsScreen: React.FC = () => {
name="name"
value={settings.modelProviderConfig.name}
onChange={handleChange}
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-teal-500 focus:border-teal-500 sm:text-sm rounded-md"
>
<option value={ModelProviderName.OpenAI}>OpenAI</option>
<option value={ModelProviderName.Ollama}>Ollama</option>
Expand All @@ -109,7 +109,7 @@ const SettingsScreen: React.FC = () => {
name="openAIBaseURL"
value={(settings.modelProviderConfig as OpenAIProviderConfig).openAIBaseURL}
onChange={handleChange}
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-teal-500 focus:border-teal-500 sm:text-sm"
/>
</div>

Expand All @@ -123,7 +123,7 @@ const SettingsScreen: React.FC = () => {
name="openAIApiKey"
value={(settings.modelProviderConfig as OpenAIProviderConfig).openAIApiKey}
onChange={handleChange}
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-teal-500 focus:border-teal-500 sm:text-sm"
/>
</div>
</>
Expand All @@ -141,7 +141,7 @@ const SettingsScreen: React.FC = () => {
value={(settings.modelProviderConfig as OllamaProviderConfig).ollamaBaseURL}
onChange={handleChange}
onBlur={() => void loadModels()}
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-teal-500 focus:border-teal-500 sm:text-sm"
/>
</div>
)}
Expand All @@ -155,7 +155,7 @@ const SettingsScreen: React.FC = () => {
name="model"
value={settings.modelProviderConfig.model}
onChange={handleChange}
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-teal-500 focus:border-teal-500 sm:text-sm rounded-md"
>
{availableModels.map((model) => (
<option key={model} value={model}>
Expand All @@ -170,7 +170,7 @@ const SettingsScreen: React.FC = () => {
<div className="flex justify-end">
<button
onClick={handleSave}
className="flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-teal-600 hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
className="flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-teal-600 hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500"
>
<HiSave className="mr-2 h-5 w-5" />
Save Settings
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/types/electron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface ElectronAPI {
removeImageUpdatedListener: (listenerId: string) => void;
getModels: (settings: Settings) => Promise<string[]>;
removeImagesInFolder: (folderPath: string) => Promise<Image[]>;
removeImage: (image: Image) => Promise<void>;
openFile: (path: string) => Promise<void>;
}

declare global {
Expand Down
2 changes: 1 addition & 1 deletion src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface WindowState {

export interface Image {
id: string;
src: string;
path: string;
caption: string;
tags: string[];
processing: boolean;
Expand Down

0 comments on commit 0350d72

Please sign in to comment.