Files
openchamber/packages/ui/src/stores/utils/safeStorage.ts
T
Bohdan Triapitsyn 844562749d feat(ui): enable drag-and-drop attachments and image previews in chat (#390)
* feat(BottomTerminalDock): add close button next to the fullscreen toggle in the dock

* style: replace hardcoded gradient with theme value in shine text variant

* fix(header): adapt instance button for desktop only

* refactor(chat): polish sticky turn UX and message action rows for better readability

Switch to stable sticky-only turn behavior and redesign user/assistant action controls (placement, hover rules, ordering, spacing, selection-safe clamp) to reduce visual noise and improve interaction flow.

* feat(chat/message): refactor buttons in messages footer

* feat: enhance image preview functionality in chat messages

- Added a new ImagePreviewDialog component to handle image previews with navigation support.
- Updated ToolOutputDialog to utilize the new ImagePreviewDialog for displaying images.
- Modified the ToolPopupContent type to include a gallery of images and an index for the current image.
- Removed the old inline image display logic from ToolOutputDialog.
- Improved file handling in the file store, including better MIME type guessing and handling of server paths.
- Introduced a new API endpoint for handling large session message payloads, allowing for better management of multi-file attachments.
- Updated the VSCode bridge to support session message requests with appropriate headers and body handling.

* feat(proxy): implement SSE forwarding and enhance generic API request handling

* feat(chat): support submitting only queued messages

* feat: add image preview transition state

* fix: default VSCode view to draft and fixed sessions list regression
2026-02-11 19:28:22 +02:00

225 lines
5.7 KiB
TypeScript

let safeStorageInstance: Storage | null = null;
let safeSessionStorageInstance: Storage | null = null;
const createInMemoryStorage = (): Storage => {
const store = new Map<string, string>();
return {
getItem: (key: string) => store.get(key) ?? null,
setItem: (key: string, value: string) => {
store.set(key, value);
},
removeItem: (key: string) => {
store.delete(key);
},
clear: () => {
store.clear();
},
key: (index: number) => Array.from(store.keys())[index] ?? null,
get length() {
return store.size;
},
} as Storage;
};
const createSafeStorage = (): Storage => {
if (typeof window === 'undefined' || !window.localStorage) {
return createInMemoryStorage();
}
const baseStorage = window.localStorage;
const fallback = createInMemoryStorage();
let storageAvailable = true;
const disableStorage = () => {
storageAvailable = false;
};
const safeGet = (key: string): string | null => {
if (storageAvailable) {
try {
const value = baseStorage.getItem(key);
if (value !== null) {
return value;
}
} catch {
disableStorage();
}
}
return fallback.getItem(key);
};
const safeSet = (key: string, value: string) => {
if (storageAvailable) {
try {
baseStorage.setItem(key, value);
fallback.removeItem(key);
return;
} catch {
disableStorage();
// Prevent stale previous value from surviving when writes fail (e.g. quota).
try {
baseStorage.removeItem(key);
} catch {
// noop
}
}
}
fallback.setItem(key, value);
};
const safeRemove = (key: string) => {
try {
baseStorage.removeItem(key);
} catch {
disableStorage();
}
fallback.removeItem(key);
};
const safeClear = () => {
try {
baseStorage.clear();
} catch {
disableStorage();
}
fallback.clear();
};
const safeKey = (index: number): string | null => {
if (storageAvailable) {
try {
return baseStorage.key(index);
} catch {
disableStorage();
}
}
return fallback.key(index);
};
return {
getItem: safeGet,
setItem: safeSet,
removeItem: safeRemove,
clear: safeClear,
key: safeKey,
get length() {
if (storageAvailable) {
try {
return baseStorage.length + fallback.length;
} catch {
disableStorage();
}
}
return fallback.length;
},
} as Storage;
};
export const getSafeStorage = (): Storage => {
if (!safeStorageInstance) {
safeStorageInstance = createSafeStorage();
}
return safeStorageInstance;
};
const createSafeSessionStorage = (): Storage => {
if (typeof window === 'undefined' || !window.sessionStorage) {
return createInMemoryStorage();
}
const baseStorage = window.sessionStorage;
const fallback = createInMemoryStorage();
let storageAvailable = true;
const disableStorage = () => {
storageAvailable = false;
};
const safeGet = (key: string): string | null => {
if (storageAvailable) {
try {
const value = baseStorage.getItem(key);
if (value !== null) {
return value;
}
} catch {
disableStorage();
}
}
return fallback.getItem(key);
};
const safeSet = (key: string, value: string) => {
if (storageAvailable) {
try {
baseStorage.setItem(key, value);
fallback.removeItem(key);
return;
} catch {
disableStorage();
// Prevent stale previous value from surviving when writes fail (e.g. quota).
try {
baseStorage.removeItem(key);
} catch {
// noop
}
}
}
fallback.setItem(key, value);
};
const safeRemove = (key: string) => {
try {
baseStorage.removeItem(key);
} catch {
disableStorage();
}
fallback.removeItem(key);
};
const safeClear = () => {
try {
baseStorage.clear();
} catch {
disableStorage();
}
fallback.clear();
};
const safeKey = (index: number): string | null => {
if (storageAvailable) {
try {
return baseStorage.key(index);
} catch {
disableStorage();
}
}
return fallback.key(index);
};
return {
getItem: safeGet,
setItem: safeSet,
removeItem: safeRemove,
clear: safeClear,
key: safeKey,
get length() {
if (storageAvailable) {
try {
return baseStorage.length + fallback.length;
} catch {
disableStorage();
}
}
return fallback.length;
},
} as Storage;
};
export const getSafeSessionStorage = (): Storage => {
if (!safeSessionStorageInstance) {
safeSessionStorageInstance = createSafeSessionStorage();
}
return safeSessionStorageInstance;
};