managarten/apps/picture/apps/mobile/hooks/useImageGeneration.ts
Wuesteon 9c47119535 Fix wrong type
import, make auth and chat work
2025-12-04 23:25:25 +01:00

236 lines
6.5 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Alert } from 'react-native';
import { router } from 'expo-router';
import { generateAndWait } from '~/services/api/generate';
import type { GenerationStatus } from '~/services/api/generate';
import { useAuth } from '~/contexts/AuthContext';
import { useModelSelection } from '~/store/modelStore';
import { useTagStore, Tag } from '~/store/tagStore';
import { usePromptStore } from '~/store/promptStore';
import { useGenerationDefaults } from '~/store/generationDefaultsStore';
import { useGeneratingImagesStore } from '~/store/generatingImagesStore';
// Define aspect ratios with common presets
export const aspectRatios = [
{ label: '1:1', value: '1:1', width: 1024, height: 1024, icon: '⬜' },
{ label: '16:9', value: '16:9', width: 1344, height: 768, icon: '📺' },
{ label: '9:16', value: '9:16', width: 768, height: 1344, icon: '📱' },
{ label: '4:3', value: '4:3', width: 1152, height: 896, icon: '🖼️' },
{ label: '3:4', value: '3:4', width: 896, height: 1152, icon: '📷' },
{ label: '3:2', value: '3:2', width: 1216, height: 832, icon: '🎞️' },
{ label: '2:3', value: '2:3', width: 832, height: 1216, icon: '📸' },
{ label: '21:9', value: '21:9', width: 1536, height: 640, icon: '🎬' },
{ label: '9:21', value: '9:21', width: 640, height: 1536, icon: '📲' },
];
export function useImageGeneration() {
const { user } = useAuth();
const { prompt, setPrompt } = usePromptStore();
const [isGenerating, setIsGenerating] = useState(false);
// Get generation defaults
const { defaultModelId, defaultAspectRatio } = useGenerationDefaults();
// Initialize with defaults
const defaultRatio = aspectRatios.find((r) => r.value === defaultAspectRatio) || aspectRatios[0];
const [selectedAspectRatio, setSelectedAspectRatio] = useState(defaultRatio);
const [selectedTags, setSelectedTags] = useState<Tag[]>([]);
const [width, setWidth] = useState(defaultRatio.width);
const [height, setHeight] = useState(defaultRatio.height);
const [steps, setSteps] = useState(4);
const [guidanceScale, setGuidanceScale] = useState(3.5);
// Model selection
const {
models,
selectedModel,
isLoading: loadingModels,
error: modelError,
hasModels,
setSelectedModel,
loadModels,
clearError,
} = useModelSelection();
// Tag management
const { addTagsToImage } = useTagStore();
// Models are now preloaded at app start in _layout.tsx
// Only load if we somehow don't have models (e.g., after error)
useEffect(() => {
if (!loadingModels && !hasModels && !modelError) {
const initModels = async () => {
try {
await loadModels();
} catch (error) {
console.error('Failed to load models initially:', error);
}
};
initModels();
}
}, [loadingModels, hasModels, modelError]);
// Set default model when models are loaded
useEffect(() => {
if (defaultModelId && models.length > 0 && !selectedModel) {
const defaultModel = models.find((m) => m.id === defaultModelId);
if (defaultModel) {
setSelectedModel(defaultModel);
}
}
}, [defaultModelId, models, selectedModel]);
useEffect(() => {
if (selectedModel) {
setSteps(selectedModel.defaultSteps ?? 4);
setGuidanceScale(selectedModel.defaultGuidanceScale ?? 3.5);
const maxDimension = Math.min(
selectedModel.maxWidth ?? 1024,
selectedModel.maxHeight ?? 1024
);
const minDimension = Math.max(selectedModel.minWidth ?? 256, selectedModel.minHeight ?? 256);
let newWidth = selectedAspectRatio.width;
let newHeight = selectedAspectRatio.height;
if (newWidth > maxDimension || newHeight > maxDimension) {
const scale = maxDimension / Math.max(newWidth, newHeight);
newWidth = Math.round(newWidth * scale);
newHeight = Math.round(newHeight * scale);
}
if (newWidth < minDimension || newHeight < minDimension) {
const scale = minDimension / Math.min(newWidth, newHeight);
newWidth = Math.round(newWidth * scale);
newHeight = Math.round(newHeight * scale);
}
setWidth(newWidth);
setHeight(newHeight);
}
}, [selectedModel, selectedAspectRatio]);
useEffect(() => {
setWidth(selectedAspectRatio.width);
setHeight(selectedAspectRatio.height);
}, [selectedAspectRatio]);
const { addGeneratingImage, completeGeneratingImage, failGeneratingImage } =
useGeneratingImagesStore();
const handleGenerate = async (options?: {
navigateToGallery?: boolean;
onSuccess?: (generationTime: number) => void;
}) => {
if (!prompt.trim()) {
Alert.alert('Fehler', 'Bitte gib einen Prompt ein');
return;
}
if (!user) {
Alert.alert('Fehler', 'Du musst angemeldet sein');
return;
}
if (!selectedModel) {
Alert.alert('Fehler', 'Bitte wähle ein Modell aus');
return;
}
setIsGenerating(true);
// Add optimistic placeholder image IMMEDIATELY
const tempId = addGeneratingImage({
prompt: prompt.trim(),
model_id: selectedModel.id,
width,
height,
});
// Navigate to gallery IMMEDIATELY
if (options?.navigateToGallery !== false) {
router.push('/(tabs)');
}
try {
// Generate image via Backend API (synchronous mode)
const result = await generateAndWait({
prompt: prompt.trim(),
modelId: selectedModel.id,
width,
height,
steps,
guidanceScale,
});
// Add tags if needed
if (result.image?.id && selectedTags.length > 0) {
await addTagsToImage(
result.image.id,
selectedTags.map((tag) => tag.id)
);
}
// Mark as completed with real image data
completeGeneratingImage(tempId, result.image ?? {}, result.generationTimeSeconds || 0);
// Clear form
setPrompt('');
setSelectedTags([]);
// Call success callback with generation time
options?.onSuccess?.(result.generationTimeSeconds || 0);
} catch (error: any) {
console.error('Generation error:', error);
// Mark as failed
failGeneratingImage(tempId, error.message || 'Generation failed');
Alert.alert(
'Fehler bei der Generierung',
error.message || 'Etwas ist schiefgelaufen. Bitte versuche es erneut.'
);
} finally {
setIsGenerating(false);
}
};
const resetForm = () => {
setPrompt('');
setSelectedTags([]);
setSelectedAspectRatio(aspectRatios[0]);
};
return {
// State
prompt,
setPrompt,
isGenerating,
selectedAspectRatio,
setSelectedAspectRatio,
selectedTags,
setSelectedTags,
width,
height,
steps,
setSteps,
guidanceScale,
setGuidanceScale,
// Model management
models,
selectedModel,
loadingModels,
modelError,
hasModels,
setSelectedModel,
loadModels,
clearError,
// Actions
handleGenerate,
resetForm,
};
}