mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
DBlobs: simplify
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import Dexie from 'dexie';
|
||||
|
||||
import { DBlobAudioItem, dBlobCacheT256, DBlobDBItem, DBlobId, DBlobImageItem, DBlobItem, DBlobMetaDataType, DBlobMimeType } from './dblobs.types';
|
||||
import { DBlobAsset, DBlobAssetType, DBlobAudioAsset, DBlobDBAsset, DBlobId, DBlobImageAsset, DBlobMimeType } from './dblobs.types';
|
||||
import { resizeBase64ImageIfNeeded } from '~/common/util/imageUtils';
|
||||
|
||||
|
||||
@@ -12,14 +12,15 @@ import { resizeBase64ImageIfNeeded } from '~/common/util/imageUtils';
|
||||
* - indexedDB.deleteDatabase('NAME').onsuccess = console.log;
|
||||
*/
|
||||
class BigAgiDB extends Dexie {
|
||||
largeAssets!: Dexie.Table<DBlobDBItem, string>;
|
||||
largeAssets!: Dexie.Table<DBlobDBAsset, string>;
|
||||
|
||||
constructor() {
|
||||
super('Big-AGI');
|
||||
this.version(1).stores({
|
||||
// Index common properties
|
||||
largeAssets: 'id, uId, wId, cId, sId, [cId+sId], type, [type+cId+sId], data.mimeType, origin.ot, origin.source, createdAt, updatedAt',
|
||||
// Index common properties (and compound indexes)
|
||||
largeAssets: 'id, contextId, scopeId, [contextId+scopeId], type, [type+contextId+scopeId], data.mimeType, origin.ot, origin.source, createdAt, updatedAt',
|
||||
});
|
||||
// Note: can re-add wId and uId in version 2 if needed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,20 +39,17 @@ const assetsTable = _db.largeAssets;
|
||||
|
||||
// CRUD
|
||||
|
||||
const DEFAULT_USER_ID = '1';
|
||||
const DEFAULT_WORKSPACE_ID = '1';
|
||||
|
||||
|
||||
export async function addDBlobItem<T extends DBlobItem>(item: T, cId: DBlobDBItem['cId'], sId: DBlobDBItem['sId']): Promise<DBlobId> {
|
||||
export async function addDBlobItem<T extends DBlobAsset>(item: T, contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']): Promise<DBlobId> {
|
||||
|
||||
// Auto-Thumbnail: when adding an image, generate a thumbnail-256 cache level
|
||||
if (item.type === DBlobMetaDataType.IMAGE) {
|
||||
if (!item.cache?.[dBlobCacheT256]) {
|
||||
const imageItem = item as DBlobImageItem;
|
||||
if (item.type === 'image') {
|
||||
if (!item.cache?.thumb256) {
|
||||
const imageItem = item as DBlobImageAsset;
|
||||
const resizedDataForCache = await resizeBase64ImageIfNeeded(imageItem.data.mimeType, imageItem.data.base64, 'thumbnail-256', DBlobMimeType.IMG_WEBP, 0.9)
|
||||
.catch((error: any) => console.error('addDBlobItem: Error resizing image', error));
|
||||
if (resizedDataForCache) {
|
||||
item.cache[dBlobCacheT256] = {
|
||||
item.cache.thumb256 = {
|
||||
base64: resizedDataForCache.base64,
|
||||
mimeType: DBlobMimeType.IMG_WEBP,
|
||||
};
|
||||
@@ -63,10 +61,8 @@ export async function addDBlobItem<T extends DBlobItem>(item: T, cId: DBlobDBIte
|
||||
// returns the id of the added item
|
||||
return await assetsTable.add({
|
||||
...item,
|
||||
uId: DEFAULT_USER_ID,
|
||||
wId: DEFAULT_WORKSPACE_ID,
|
||||
cId,
|
||||
sId,
|
||||
contextId,
|
||||
scopeId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('addDBlobItem: Error adding item', error);
|
||||
@@ -78,25 +74,25 @@ export async function getDBlobItemIDs() {
|
||||
return assetsTable.toCollection().primaryKeys();
|
||||
}
|
||||
|
||||
export async function getItemById<T extends DBlobItem = DBlobItem>(id: DBlobId) {
|
||||
export async function getItemById<T extends DBlobAsset = DBlobAsset>(id: DBlobId) {
|
||||
return await assetsTable.get(id) as T | undefined;
|
||||
}
|
||||
|
||||
export async function getDBlobItemsByType<T extends DBlobItem>(type: T['type']) {
|
||||
export async function getDBlobItemsByType<T extends DBlobAsset>(type: T['type']) {
|
||||
return await assetsTable.where({ type }).toArray() as unknown as T[];
|
||||
}
|
||||
|
||||
export async function getDBlobItemsByTypeCIdSid<T extends DBlobItem>(type: T['type'], cId: DBlobDBItem['cId'], sId: DBlobDBItem['sId']) {
|
||||
const items = await assetsTable.where({ type, cId, sId }).sortBy('createdAt');
|
||||
export async function getDBlobItemsByTypeContextIdScopeId<T extends DBlobAsset>(type: T['type'], contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']) {
|
||||
const items = await assetsTable.where({ type, contextId, scopeId }).sortBy('createdAt');
|
||||
return items.reverse() as unknown as T[];
|
||||
}
|
||||
|
||||
export async function getItemsByMimeType<T extends DBlobItem>(mimeType: T['data']['mimeType']) {
|
||||
export async function getItemsByMimeType<T extends DBlobAsset>(mimeType: T['data']['mimeType']) {
|
||||
return await assetsTable.where('data.mimeType').equals(mimeType).toArray() as unknown as T[];
|
||||
}
|
||||
|
||||
|
||||
export async function updateDBlobItem(id: DBlobId, updates: Partial<DBlobItem>) {
|
||||
export async function updateDBlobItem(id: DBlobId, updates: Partial<DBlobAsset>) {
|
||||
return assetsTable.update(id, updates);
|
||||
}
|
||||
|
||||
@@ -108,14 +104,14 @@ export async function deleteDBlobItems(ids: DBlobId[]) {
|
||||
return assetsTable.bulkDelete(ids);
|
||||
}
|
||||
|
||||
export async function deleteAllDBlobsInScopeId(cId: DBlobDBItem['cId'], sId: DBlobDBItem['sId']) {
|
||||
return assetsTable.where({ cId, sId }).delete();
|
||||
export async function deleteAllDBlobsInScopeId(contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']) {
|
||||
return assetsTable.where({ contextId, scopeId }).delete();
|
||||
}
|
||||
|
||||
|
||||
// Specific item types
|
||||
async function getImageItemById(id: DBlobId) {
|
||||
return await getItemById<DBlobImageItem>(id);
|
||||
return await getItemById<DBlobImageAsset>(id);
|
||||
}
|
||||
|
||||
export async function getImageDataURLById(id: DBlobId) {
|
||||
@@ -139,18 +135,18 @@ export async function getImageBlobURLById(id: DBlobId) {
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
async function getAllImages(): Promise<DBlobImageItem[]> {
|
||||
return await getDBlobItemsByType<DBlobImageItem>(DBlobMetaDataType.IMAGE);
|
||||
async function getAllImages(): Promise<DBlobImageAsset[]> {
|
||||
return await getDBlobItemsByType<DBlobImageAsset>(DBlobAssetType.IMAGE);
|
||||
}
|
||||
|
||||
async function getAllAudio(): Promise<DBlobAudioItem[]> {
|
||||
return await getDBlobItemsByType<DBlobAudioItem>(DBlobMetaDataType.AUDIO);
|
||||
async function getAllAudio(): Promise<DBlobAudioAsset[]> {
|
||||
return await getDBlobItemsByType<DBlobAudioAsset>(DBlobAssetType.AUDIO);
|
||||
}
|
||||
|
||||
async function getHighResImages() {
|
||||
return await assetsTable
|
||||
.where('data.mimeType')
|
||||
.startsWith('image/')
|
||||
.and(item => (item as DBlobImageItem).metadata.width > 1920)
|
||||
.toArray() as DBlobImageItem[];
|
||||
.and(item => (item as DBlobImageAsset).metadata.width > 1920)
|
||||
.toArray() as DBlobImageAsset[];
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useLiveQuery } from 'dexie-react-hooks';
|
||||
|
||||
import type { DBlobDBItem, DBlobItem } from './dblobs.types';
|
||||
import { addDBlobItem, deleteDBlobItem, getDBlobItemsByTypeCIdSid, getItemById, updateDBlobItem } from './dblobs.db';
|
||||
import type { DBlobAsset, DBlobDBAsset } from './dblobs.types';
|
||||
import { addDBlobItem, deleteDBlobItem, getDBlobItemsByTypeContextIdScopeId, getItemById, updateDBlobItem } from './dblobs.db';
|
||||
|
||||
|
||||
// export function useDBlobItems<T extends DBlobItem>(type: T['type']): [T[] | undefined, (item: T, cId: DBlobDBItem['cId'], sId: DBlobDBItem['sId']) => Promise<void>, (id: string) => Promise<void>] {
|
||||
// export function useDBlobItems<T extends DBlobItem>(type: T['type']): [T[] | undefined, (item: T, contextId: DBlobDBItem['contextId'], scopeId: DBlobDBItem['scopeId']) => Promise<void>, (id: string) => Promise<void>] {
|
||||
// const items = useLiveQuery(() => getDBlobItemsByType<T>(type), [type]);
|
||||
//
|
||||
// const addDBlobItemHandler = async (item: T, cId: DBlobDBItem['cId'], sId: DBlobDBItem['sId']) => {
|
||||
// await addDBlobItem(item, cId, sId);
|
||||
// const addDBlobItemHandler = async (item: T, contextId: DBlobDBItem['contextId'], scopeId: DBlobDBItem['scopeId']) => {
|
||||
// await addDBlobItem(item, contextId, scopeId);
|
||||
// };
|
||||
//
|
||||
// const deleteDBlobItemHandler = async (id: string) => {
|
||||
@@ -18,14 +18,14 @@ import { addDBlobItem, deleteDBlobItem, getDBlobItemsByTypeCIdSid, getItemById,
|
||||
// return [items, addDBlobItemHandler, deleteDBlobItemHandler];
|
||||
// }
|
||||
|
||||
export function useDBlobItemsByTypeCIdSId<T extends DBlobItem>(type: T['type'], cId: DBlobDBItem['cId'], sId: DBlobDBItem['sId']): [T[] | undefined, (item: T, cId: DBlobDBItem['cId'], sId: DBlobDBItem['sId']) => Promise<void>, (id: string) => Promise<void>] {
|
||||
export function useDBlobItemsByTypeContextIdScopeId<T extends DBlobAsset>(type: T['type'], contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']): [T[] | undefined, (item: T, contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']) => Promise<void>, (id: string) => Promise<void>] {
|
||||
const items = useLiveQuery(
|
||||
() => getDBlobItemsByTypeCIdSid<T>(type, cId, sId),
|
||||
[type, cId, sId],
|
||||
() => getDBlobItemsByTypeContextIdScopeId<T>(type, contextId, scopeId),
|
||||
[type, contextId, scopeId],
|
||||
);
|
||||
|
||||
const addDBlobItemHandler = async (item: T, cId: DBlobDBItem['cId'], sId: DBlobDBItem['sId']) => {
|
||||
await addDBlobItem(item, cId, sId);
|
||||
const addDBlobItemHandler = async (item: T, contextId: DBlobDBAsset['contextId'], scopeId: DBlobDBAsset['scopeId']) => {
|
||||
await addDBlobItem(item, contextId, scopeId);
|
||||
};
|
||||
|
||||
const deleteDBlobItemHandler = async (id: string) => {
|
||||
@@ -36,7 +36,7 @@ export function useDBlobItemsByTypeCIdSId<T extends DBlobItem>(type: T['type'],
|
||||
}
|
||||
|
||||
|
||||
export function useDBlobItem<T extends DBlobItem>(id: string): [T | undefined, (updates: Partial<T>) => Promise<void>] {
|
||||
export function useDBlobItem<T extends DBlobAsset>(id: string): [T | undefined, (updates: Partial<T>) => Promise<void>] {
|
||||
const item = useLiveQuery(
|
||||
() => getItemById<T>(id),
|
||||
[id],
|
||||
|
||||
@@ -1,17 +1,72 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
|
||||
// Blob
|
||||
// DB - Assets
|
||||
|
||||
export enum DBlobMimeType {
|
||||
IMG_PNG = 'image/png', IMG_JPEG = 'image/jpeg', IMG_WEBP = 'image/webp',
|
||||
AUDIO_MPEG = 'audio/mpeg', AUDIO_WAV = 'audio/wav',
|
||||
// VIDEO_MP4 = 'video/mp4',
|
||||
// DOCUMENT_PDF = 'application/pdf', DOCUMENT_PLAIN = 'text/plain', DOCUMENT_HTML = 'text/html',
|
||||
// ...
|
||||
/**
|
||||
* This is the asset when stored/loaded in the DB. Carries some more context out of band from the asset itself.
|
||||
*/
|
||||
export type DBlobDBAsset = {
|
||||
contextId: 'global';
|
||||
scopeId: 'app-chat' | 'app-draw' | 'attachment-drafts';
|
||||
} & DBlobAsset;
|
||||
|
||||
|
||||
// Assets
|
||||
|
||||
export type DBlobAsset = DBlobImageAsset | DBlobAudioAsset; // | DBlobVideoAsset | DBlobDocumentAsset | DBlobTextAsset;
|
||||
|
||||
export type DBlobImageAsset = DBlobAssetImplV1<
|
||||
/* type: */ DBlobAssetType.IMAGE,
|
||||
/* data: <mime> */ DBlobMimeType.IMG_PNG | DBlobMimeType.IMG_JPEG | DBlobMimeType.IMG_WEBP,
|
||||
/* metadata: */ ImageAssetMetadata
|
||||
>;
|
||||
|
||||
export type DBlobAudioAsset = DBlobAssetImplV1<
|
||||
/* type: */ DBlobAssetType.AUDIO,
|
||||
/* data: <mime> */ DBlobMimeType.AUDIO_MPEG | DBlobMimeType.AUDIO_WAV,
|
||||
/* metadata: */ AudioAssetMetadata
|
||||
>;
|
||||
|
||||
// type DBlobVideoAsset = DBlobAssetImplV1<DBlobAssetType.VIDEO, DBlobDataMimeType.VIDEO_MP4, VideoAssetMetadata>;
|
||||
// type DBlobDocumentAsset = DBlobAssetImplV1<DBlobAssetType.DOCUMENT, DBlobDataMimeType.DOCUMENT_PDF, DocumentAssetMetadata>;
|
||||
// type DBlobTextAsset = DBlobAssetImplV1<DBlobAssetType.TEXT, DBlobDataMimeType.DOCUMENT_PLAIN, {}>;
|
||||
|
||||
|
||||
// DB - Asset Generic Type
|
||||
|
||||
interface DBlobAssetImplV1<TType extends DBlobAssetType, TMime extends DBlobMimeType, TMeta extends Record<string, any>> {
|
||||
id: DBlobId; // Unique identifier
|
||||
type: TType; // Type of asset, used for discrimination
|
||||
|
||||
label: string; // Textual representation
|
||||
data: DBlobAssetData<TMime>; // Original data as a BlobData object
|
||||
origin: DBlobAssetOrigin; // Source of the data (e.g., "upload", "generated")
|
||||
|
||||
createdAt: Date; // Creation date
|
||||
updatedAt: Date; // Last updated date
|
||||
|
||||
metadata: TMeta; // Flexible metadata for specific .type(s)
|
||||
// cache: Record<string, DBlobData<DBlobMimeType>>; // Cached conversions as BlobData objects
|
||||
cache: {
|
||||
thumb256?: DBlobAssetData<DBlobMimeType.IMG_WEBP>; // Cache for the thumbnail-256 conversion
|
||||
};
|
||||
}
|
||||
|
||||
interface DBlobData<M extends DBlobMimeType> {
|
||||
export type DBlobId = string;
|
||||
|
||||
export enum DBlobAssetType {
|
||||
IMAGE = 'image',
|
||||
AUDIO = 'audio',
|
||||
// VIDEO = 'video',
|
||||
// DOCUMENT = 'document',
|
||||
// EGO = 'ego',
|
||||
}
|
||||
|
||||
|
||||
// Asset Data
|
||||
|
||||
interface DBlobAssetData<M extends DBlobMimeType> {
|
||||
mimeType: M;
|
||||
base64: string; // Base64 encoded content (not a data URL)
|
||||
// NOTE: the data url will be "data:${mimeType};base64,${base64}"
|
||||
@@ -20,8 +75,17 @@ interface DBlobData<M extends DBlobMimeType> {
|
||||
altData?: string; // Alternative data for the input (optional)
|
||||
}
|
||||
|
||||
export enum DBlobMimeType {
|
||||
IMG_PNG = 'image/png', IMG_JPEG = 'image/jpeg', IMG_WEBP = 'image/webp',
|
||||
AUDIO_MPEG = 'audio/mpeg', AUDIO_WAV = 'audio/wav',
|
||||
// VIDEO_MP4 = 'video/mp4',
|
||||
// DOCUMENT_PDF = 'application/pdf', DOCUMENT_PLAIN = 'text/plain', DOCUMENT_HTML = 'text/html',
|
||||
}
|
||||
|
||||
// Item Origin
|
||||
|
||||
// Asset Origin
|
||||
|
||||
type DBlobAssetOrigin = UserOrigin | GeneratedOrigin;
|
||||
|
||||
interface UserOrigin {
|
||||
ot: 'user';
|
||||
@@ -76,58 +140,10 @@ interface EgoOrigin {
|
||||
messageId?: string; // ID of the message (optional)
|
||||
}*/
|
||||
|
||||
// Union type for ItemDataOrigin
|
||||
type ItemDataOrigin = UserOrigin | GeneratedOrigin;
|
||||
|
||||
// Asset Metadata
|
||||
|
||||
// Item Base type
|
||||
|
||||
interface DBlobBase<TType extends DBlobMetaDataType, TMime extends DBlobMimeType, TMeta extends Record<string, any>> {
|
||||
id: DBlobId; // Unique identifier
|
||||
type: TType; // Type of item, used for discrimination
|
||||
|
||||
label: string; // Textual representation
|
||||
data: DBlobData<TMime>; // Original data as a BlobData object
|
||||
origin: ItemDataOrigin; // Source of the data (e.g., "upload", "generated")
|
||||
|
||||
createdAt: Date; // Creation date
|
||||
updatedAt: Date; // Last updated date
|
||||
|
||||
metadata: TMeta; // Flexible metadata for specific .type(s)
|
||||
cache: Record<string, DBlobData<DBlobMimeType>>; // Cached conversions as BlobData objects
|
||||
}
|
||||
|
||||
export type DBlobId = string;
|
||||
|
||||
export function createDBlobBase<TType extends DBlobMetaDataType, TMime extends DBlobMimeType, TMeta extends Record<string, any>>(type: TType, label: string, data: DBlobData<TMime>, origin: ItemDataOrigin, metadata: TMeta): DBlobBase<TType, TMime, TMeta> {
|
||||
const creationDate = new Date();
|
||||
return {
|
||||
id: nanoid(),
|
||||
type,
|
||||
label,
|
||||
data,
|
||||
origin,
|
||||
createdAt: creationDate,
|
||||
updatedAt: creationDate,
|
||||
metadata,
|
||||
cache: {},
|
||||
};
|
||||
}
|
||||
|
||||
export const dBlobCacheT256 = 'thumbnail-256';
|
||||
|
||||
|
||||
// Item Specialization
|
||||
|
||||
export enum DBlobMetaDataType {
|
||||
IMAGE = 'image',
|
||||
AUDIO = 'audio',
|
||||
// VIDEO = 'video',
|
||||
// DOCUMENT = 'document',
|
||||
// EGO = 'ego',
|
||||
}
|
||||
|
||||
interface ImageMetadata {
|
||||
interface ImageAssetMetadata {
|
||||
width: number;
|
||||
height: number;
|
||||
averageColor?: string; // Average html color of the image (optional)
|
||||
@@ -136,7 +152,7 @@ interface ImageMetadata {
|
||||
description?: string; // Description of the image (optional)
|
||||
}
|
||||
|
||||
interface AudioMetadata {
|
||||
interface AudioAssetMetadata {
|
||||
duration: number; // Duration in seconds
|
||||
sampleRate: number; // Sample rate of the audio
|
||||
bitrate?: number; // Bitrate of the audio (optional)
|
||||
@@ -167,25 +183,29 @@ interface DocumentMetadata {
|
||||
}*/
|
||||
|
||||
|
||||
// Item Data
|
||||
|
||||
export type DBlobImageItem = DBlobBase<DBlobMetaDataType.IMAGE, DBlobMimeType.IMG_PNG | DBlobMimeType.IMG_JPEG | DBlobMimeType.IMG_WEBP, ImageMetadata>;
|
||||
export type DBlobAudioItem = DBlobBase<DBlobMetaDataType.AUDIO, DBlobMimeType.AUDIO_MPEG | DBlobMimeType.AUDIO_WAV, AudioMetadata>;
|
||||
// type DBlobVideoItem = DBlobBase<ItemDataType.VIDEO, BlobMimeType.VIDEO_MP4, VideoMetadata>;
|
||||
// type DBlobDocumentItem = DBlobBase<ItemDataType.DOCUMENT, BlobMimeType.DOCUMENT_PDF, DocumentMetadata>;
|
||||
// type DBlobTextItem = DBlobBase<ItemDataType.TEXT, BlobMimeType.DOCUMENT_PLAIN, {}>;
|
||||
|
||||
|
||||
// DB Item Data
|
||||
|
||||
export type DBlobItem = DBlobImageItem | DBlobAudioItem; // | DBlobVideoItem | DBlobDocumentItem | DBlobTextItem | DBlobEgoItem;
|
||||
|
||||
export const createDBlobImageItem = (label: string, data: DBlobImageItem['data'], origin: ItemDataOrigin, metadata: ImageMetadata): DBlobImageItem =>
|
||||
createDBlobBase(DBlobMetaDataType.IMAGE, label, data, origin, metadata);
|
||||
|
||||
export type DBlobDBItem = DBlobItem & {
|
||||
uId: '1';
|
||||
wId: '1';
|
||||
cId: 'global';
|
||||
sId: 'app-chat' | 'app-draw' | 'attachment-drafts'; // scope ID
|
||||
export function createDBlobImageAsset(label: string, data: DBlobImageAsset['data'], origin: DBlobAssetOrigin, metadata: ImageAssetMetadata): DBlobImageAsset {
|
||||
return _createAssetImpl(DBlobAssetType.IMAGE, label, data, origin, metadata);
|
||||
}
|
||||
|
||||
function _createAssetImpl<TType extends DBlobAssetType, TMime extends DBlobMimeType, TMeta extends Record<string, any>>(
|
||||
type: TType,
|
||||
label: string,
|
||||
data: DBlobAssetData<TMime>,
|
||||
origin: DBlobAssetOrigin,
|
||||
metadata: TMeta,
|
||||
): DBlobAssetImplV1<TType, TMime, TMeta> {
|
||||
const creationDate = new Date();
|
||||
return {
|
||||
id: nanoid(),
|
||||
type,
|
||||
label,
|
||||
data,
|
||||
origin,
|
||||
createdAt: creationDate,
|
||||
updatedAt: creationDate,
|
||||
metadata,
|
||||
cache: {},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user