Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { open } from '@tauri-apps/plugin-dialog';
import { safeTauriDialogOpen, isTauriEnvironment } from '@/utils/tauriUtils';
import { FolderPlus } from 'lucide-react';
interface FolderPickerProps {
setFolderPath: (path: string[]) => void;
Expand All @@ -14,8 +14,13 @@ const AITaggingFolderPicker: React.FC<FolderPickerProps> = ({
handleDeleteCache,
}) => {
const pickFolder = async () => {
if (!isTauriEnvironment()) {
alert('Folder selection is only available in desktop mode. Please run the app using "npm run tauri dev" instead of "npm run dev".');
return;
}

try {
const selected = await open({
const selected = await safeTauriDialogOpen({
directory: true,
multiple: true,
title: 'Select folders',
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/components/FolderPicker/FolderPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { open } from '@tauri-apps/plugin-dialog';
import { safeTauriDialogOpen, isTauriEnvironment } from '@/utils/tauriUtils';
import { FolderPlus } from 'lucide-react';

interface FolderPickerProps {
Expand All @@ -13,8 +13,13 @@ const FolderPicker: React.FC<FolderPickerProps> = ({
className,
}) => {
const pickFolder = async () => {
if (!isTauriEnvironment()) {
alert('Folder selection is only available in desktop mode. Please run the app using "npm run tauri dev" instead of "npm run dev".');
return;
}

try {
const selected = await open({
const selected = await safeTauriDialogOpen({
directory: true,
multiple: true, // Allow multiple folder selection
title: 'Select folders',
Expand Down
16 changes: 14 additions & 2 deletions frontend/src/hooks/useUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useCallback } from 'react';
import { check, Update } from '@tauri-apps/plugin-updater';
import { Update } from '@tauri-apps/plugin-updater';
import { safeTauriUpdaterCheck, isTauriEnvironment } from '@/utils/tauriUtils';

interface DownloadProgress {
downloaded: number;
Expand Down Expand Up @@ -32,9 +33,15 @@ export const useUpdater = (): UseUpdaterReturn => {
const checkForUpdates = useCallback(async (): Promise<boolean> => {
setError(null);

// Skip update check in browser mode
if (!isTauriEnvironment()) {
console.log('Skipping update check in browser mode');
return false;
}

try {
console.log('Checking for updates...');
const update = await check();
const update = await safeTauriUpdaterCheck();

if (update) {
console.log(
Expand All @@ -61,6 +68,11 @@ export const useUpdater = (): UseUpdaterReturn => {
return;
}

if (!isTauriEnvironment()) {
console.warn('Update download is not available in browser mode');
return;
}

setIsDownloading(true);
setDownloadProgress({ downloaded: 0, total: 0 });
setError(null);
Expand Down
20 changes: 15 additions & 5 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ import ReactDOM from 'react-dom/client';
import App from './App';
import { isProd } from './utils/isProd';
import { stopServer, startServer } from './utils/serverUtils';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { isTauriEnvironment } from './utils/tauriUtils';
import { imagesEndpoints } from '../api/apiEndpoints';
import { store } from './app/store';
import { Provider } from 'react-redux';

//Listen for window close event and stop server.
const onCloseListener = async () => {
await getCurrentWindow().onCloseRequested(async () => {
await stopServer();
});
if (!isTauriEnvironment()) {
console.log('Window close listener is only available in Tauri environment');
return;
}

try {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
await getCurrentWindow().onCloseRequested(async () => {
await stopServer();
});
} catch (error) {
console.error('Error setting up close listener:', error);
}
};

const fetchFilePath = async () => {
Expand Down Expand Up @@ -60,7 +70,7 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
</React.StrictMode>,
);

if (isProd()) {
if (isProd() && isTauriEnvironment()) {
onCloseListener();
console.log('Starting PictoPy Server');
startServer();
Expand Down
106 changes: 106 additions & 0 deletions frontend/src/utils/tauriUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* Utility functions for Tauri environment detection and fallbacks
*/

// Check if we're running in a Tauri environment
export const isTauriEnvironment = (): boolean => {
return typeof window !== 'undefined' && '__TAURI__' in window;
};

// Safe invoke wrapper that only calls Tauri APIs when available
export const safeTauriInvoke = async (command: string, args?: any): Promise<any> => {
if (!isTauriEnvironment()) {
console.warn(`Tauri command "${command}" is not available in browser mode`);
return null;
}

try {
const { invoke } = await import('@tauri-apps/api/core');
return await invoke(command, args);
} catch (error) {
console.error(`Error invoking Tauri command "${command}":`, error);
throw error;
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inconsistent error handling pattern across safe wrapper functions

The safeTauriInvoke function throws errors while other safe wrapper functions (like safeTauriUpdaterCheck) return null on errors. This inconsistency could lead to confusion and different error handling requirements across the codebase.

Consider standardizing the error handling approach. For safe wrapper functions, returning null on errors (as done in other functions) is more appropriate:

-  } catch (error) {
-    console.error(`Error invoking Tauri command "${command}":`, error);
-    throw error;
-  }
+  } catch (error) {
+    console.error(`Error invoking Tauri command "${command}":`, error);
+    return null;
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const safeTauriInvoke = async (command: string, args?: any): Promise<any> => {
if (!isTauriEnvironment()) {
console.warn(`Tauri command "${command}" is not available in browser mode`);
return null;
}
try {
const { invoke } = await import('@tauri-apps/api/core');
return await invoke(command, args);
} catch (error) {
console.error(`Error invoking Tauri command "${command}":`, error);
throw error;
}
};
export const safeTauriInvoke = async (command: string, args?: any): Promise<any> => {
if (!isTauriEnvironment()) {
console.warn(`Tauri command "${command}" is not available in browser mode`);
return null;
}
try {
const { invoke } = await import('@tauri-apps/api/core');
return await invoke(command, args);
} catch (error) {
console.error(`Error invoking Tauri command "${command}":`, error);
return null;
}
};
🤖 Prompt for AI Agents
In frontend/src/utils/tauriUtils.ts between lines 11 and 24, the safeTauriInvoke
function currently throws errors on failure, unlike other safe wrapper functions
that return null. To standardize error handling, modify safeTauriInvoke so that
instead of throwing the error in the catch block, it logs the error and returns
null. This will align its behavior with other safe wrapper functions and
simplify error handling across the codebase.


// Safe updater check that only works in Tauri environment
export const safeTauriUpdaterCheck = async (): Promise<any> => {
if (!isTauriEnvironment()) {
console.warn('Updater is not available in browser mode');
return null;
}

try {
const { check } = await import('@tauri-apps/plugin-updater');
return await check();
} catch (error) {
console.error('Error checking for updates:', error);
return null;
}
};

// Safe dialog open that only works in Tauri environment
export const safeTauriDialogOpen = async (options: any): Promise<string[] | string | null> => {
if (!isTauriEnvironment()) {
console.warn('File dialog is not available in browser mode');
// In browser mode, we could potentially use the HTML5 file input as a fallback
return null;
}

try {
const { open } = await import('@tauri-apps/plugin-dialog');
return await open(options);
} catch (error) {
console.error('Error opening dialog:', error);
return null;
}
};

// Safe convertFileSrc that returns a fallback URL in browser mode
export const safeTauriConvertFileSrc = (src: string): string => {
if (!isTauriEnvironment()) {
// In browser mode, return the src as-is or provide a fallback
return src;
}

try {
// Dynamic import to avoid errors in browser mode
const { convertFileSrc } = require('@tauri-apps/api/core');
return convertFileSrc(src);
} catch (error) {
console.error('Error converting file src:', error);
return src;
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace require() with dynamic import for consistency

The safeTauriConvertFileSrc function uses require() instead of dynamic import, which is inconsistent with other functions and could cause bundling issues in browser environments.

Apply this diff to use dynamic import consistently:

-  try {
-    // Dynamic import to avoid errors in browser mode
-    const { convertFileSrc } = require('@tauri-apps/api/core');
-    return convertFileSrc(src);
-  } catch (error) {
-    console.error('Error converting file src:', error);
-    return src;
-  }
+  try {
+    const { convertFileSrc } = await import('@tauri-apps/api/core');
+    return convertFileSrc(src);
+  } catch (error) {
+    console.error('Error converting file src:', error);
+    return src;
+  }

Note: This change requires making the function async:

-export const safeTauriConvertFileSrc = (src: string): string => {
+export const safeTauriConvertFileSrc = async (src: string): Promise<string> => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const safeTauriConvertFileSrc = (src: string): string => {
if (!isTauriEnvironment()) {
// In browser mode, return the src as-is or provide a fallback
return src;
}
try {
// Dynamic import to avoid errors in browser mode
const { convertFileSrc } = require('@tauri-apps/api/core');
return convertFileSrc(src);
} catch (error) {
console.error('Error converting file src:', error);
return src;
}
};
export const safeTauriConvertFileSrc = async (src: string): Promise<string> => {
if (!isTauriEnvironment()) {
// In browser mode, return the src as-is or provide a fallback
return src;
}
try {
const { convertFileSrc } = await import('@tauri-apps/api/core');
return convertFileSrc(src);
} catch (error) {
console.error('Error converting file src:', error);
return src;
}
};
🤖 Prompt for AI Agents
In frontend/src/utils/tauriUtils.ts around lines 60 to 74, replace the use of
require() with a dynamic import to maintain consistency and avoid bundling
issues in browser environments. To fix this, make the function async and use
await import('@tauri-apps/api/core') to dynamically load convertFileSrc, then
return the converted source. Adjust the function signature to async and update
all call sites accordingly.


// Safe dialog save that only works in Tauri environment
export const safeTauriDialogSave = async (options: any): Promise<string | null> => {
if (!isTauriEnvironment()) {
console.warn('Save dialog is not available in browser mode');
return null;
}

try {
const { save } = await import('@tauri-apps/plugin-dialog');
return await save(options);
} catch (error) {
console.error('Error opening save dialog:', error);
return null;
}
};

// Safe file read that only works in Tauri environment
export const safeTauriReadFile = async (path: string): Promise<Uint8Array | null> => {
if (!isTauriEnvironment()) {
console.warn('File reading is not available in browser mode');
return null;
}

try {
const { readFile } = await import('@tauri-apps/plugin-fs');
return await readFile(path);
} catch (error) {
console.error('Error reading file:', error);
return null;
}
};