storage.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import { useState } from 'react';
  2. // Safe localStorage hook that handles quota errors
  3. export function useLocalStorage<T>(
  4. key: string,
  5. initialValue: T,
  6. options?: {
  7. excludeLargeData?: boolean;
  8. maxSize?: number; // in bytes
  9. }
  10. ) {
  11. const { excludeLargeData = true, maxSize = 1024 * 1024 } = options || {}; // 1MB default
  12. // Get stored value
  13. const getStoredValue = (): T => {
  14. if (typeof window === 'undefined') {
  15. return initialValue;
  16. }
  17. try {
  18. const item = window.localStorage.getItem(key);
  19. if (item === null) {
  20. return initialValue;
  21. }
  22. return JSON.parse(item);
  23. } catch (error) {
  24. console.warn(`Error reading localStorage key "${key}":`, error);
  25. // If there's an error, clear the key and return initial value
  26. try {
  27. window.localStorage.removeItem(key);
  28. } catch (clearError) {
  29. console.warn(`Error clearing localStorage key "${key}":`, clearError);
  30. }
  31. return initialValue;
  32. }
  33. };
  34. const [storedValue, setStoredValue] = useState<T>(getStoredValue);
  35. // Set value to localStorage
  36. const setValue = (value: T | ((val: T) => T)) => {
  37. try {
  38. const valueToStore = value instanceof Function ? value(storedValue) : value;
  39. // Check if we should exclude large data
  40. if (excludeLargeData) {
  41. const serialized = JSON.stringify(valueToStore);
  42. // Check for large base64 images or data URLs
  43. if (serialized.includes('data:image/') || serialized.length > maxSize) {
  44. console.warn(`Skipping localStorage save for "${key}" - data too large or contains images`);
  45. setStoredValue(valueToStore);
  46. return;
  47. }
  48. }
  49. setStoredValue(valueToStore);
  50. if (typeof window !== 'undefined') {
  51. window.localStorage.setItem(key, JSON.stringify(valueToStore));
  52. }
  53. } catch (error) {
  54. console.warn(`Error saving localStorage key "${key}":`, error);
  55. // Still update the state even if localStorage fails
  56. const valueToStore = value instanceof Function ? value(storedValue) : value;
  57. setStoredValue(valueToStore);
  58. // Try to clear some space if it's a quota error
  59. if (error instanceof Error && error.name === 'QuotaExceededError') {
  60. try {
  61. // Clear some non-essential keys
  62. const keysToClear = ['img2img-form-data', 'text2img-form-data', 'inpainting-form-data'];
  63. keysToClear.forEach(k => {
  64. if (k !== key) {
  65. window.localStorage.removeItem(k);
  66. }
  67. });
  68. } catch (clearError) {
  69. console.warn('Error clearing localStorage space:', clearError);
  70. }
  71. }
  72. }
  73. };
  74. return [storedValue, setValue] as const;
  75. }
  76. // Session storage alternative for large data
  77. export function useSessionStorage<T>(key: string, initialValue: T) {
  78. const [storedValue, setStoredValue] = useState<T>(() => {
  79. if (typeof window === 'undefined') {
  80. return initialValue;
  81. }
  82. try {
  83. const item = window.sessionStorage.getItem(key);
  84. return item ? JSON.parse(item) : initialValue;
  85. } catch (error) {
  86. console.warn(`Error reading sessionStorage key "${key}":`, error);
  87. return initialValue;
  88. }
  89. });
  90. const setValue = (value: T | ((val: T) => T)) => {
  91. try {
  92. const valueToStore = value instanceof Function ? value(storedValue) : value;
  93. setStoredValue(valueToStore);
  94. if (typeof window !== 'undefined') {
  95. window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
  96. }
  97. } catch (error) {
  98. console.warn(`Error saving sessionStorage key "${key}":`, error);
  99. // Still update the state even if sessionStorage fails
  100. const valueToStore = value instanceof Function ? value(storedValue) : value;
  101. setStoredValue(valueToStore);
  102. }
  103. };
  104. return [storedValue, setValue] as const;
  105. }
  106. // Memory-only storage for large data
  107. export function useMemoryStorage<T>(initialValue: T) {
  108. const [storedValue, setStoredValue] = useState<T>(initialValue);
  109. const setValue = (value: T | ((val: T) => T)) => {
  110. const valueToStore = value instanceof Function ? value(storedValue) : value;
  111. setStoredValue(valueToStore);
  112. };
  113. return [storedValue, setValue] as const;
  114. }
  115. // Storage for generated images with session persistence
  116. interface StoredImage {
  117. url: string;
  118. timestamp: number;
  119. pageType: 'text2img' | 'img2img' | 'upscaler' | 'inpainting';
  120. jobId?: string;
  121. }
  122. interface GeneratedImagesState {
  123. images: StoredImage[];
  124. lastJobId?: string;
  125. }
  126. export function useGeneratedImages(pageType: 'text2img' | 'img2img' | 'upscaler' | 'inpainting') {
  127. const storageKey = `generated-images-${pageType}`;
  128. const [state, setState] = useSessionStorage<GeneratedImagesState>(
  129. storageKey,
  130. { images: [] }
  131. );
  132. // Clean up old images (older than 1 hour) on mount
  133. useState(() => {
  134. const now = Date.now();
  135. const oneHour = 60 * 60 * 1000;
  136. setState((prevState) => ({
  137. ...prevState,
  138. images: prevState.images.filter(img => now - img.timestamp < oneHour)
  139. }));
  140. });
  141. const addImages = (urls: string[], jobId?: string) => {
  142. const newImages: StoredImage[] = urls.map(url => ({
  143. url,
  144. timestamp: Date.now(),
  145. pageType,
  146. jobId
  147. }));
  148. setState((prevState) => ({
  149. lastJobId: jobId,
  150. images: [...newImages, ...prevState.images.slice(0, 23)] // Keep max 24 images
  151. }));
  152. };
  153. const clearImages = () => {
  154. setState({ images: [] });
  155. };
  156. const getLatestImages = (count: number = 1): string[] => {
  157. return state.images.slice(0, count).map(img => img.url);
  158. };
  159. return {
  160. images: state.images,
  161. lastJobId: state.lastJobId,
  162. addImages,
  163. clearImages,
  164. getLatestImages
  165. };
  166. }