diff --git a/bun.lock b/bun.lock index 28909921de..8981b43888 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "github:appwrite/sdk-for-console#2291c59", + "@appwrite.io/console": "github:appwrite/sdk-for-console#6b7d06d", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", @@ -121,7 +121,7 @@ "@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="], - "@appwrite.io/console": ["@appwrite.io/console@github:appwrite/sdk-for-console#2291c59", { "dependencies": { "json-bigint": "1.0.0" } }, "appwrite-sdk-for-console-2291c59"], + "@appwrite.io/console": ["@appwrite.io/console@github:appwrite/sdk-for-console#6b7d06d", { "dependencies": { "json-bigint": "1.0.0" } }, "appwrite-sdk-for-console-6b7d06d"], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], diff --git a/package.json b/package.json index 9de000847a..98afa76df9 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "github:appwrite/sdk-for-console#2291c59", + "@appwrite.io/console": "github:appwrite/sdk-for-console#6b7d06d", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/documents-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/documents-db.svg index 87cbe9afba..b1ff60975d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/documents-db.svg +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/documents-db.svg @@ -1,102 +1,79 @@ - - - - - - - - + + + + + + + + - - + + - - + + - - - - - - - - - - - - - - - - + + + + + - - - + + + + - - - - - - + - + - + - + - + - + - + - + - - - - - + - + - + - - - - - + - - + + - - + + - - + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/tables-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/tables-db.svg index 6b2739c52b..4537085e39 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/tables-db.svg +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/tables-db.svg @@ -1,164 +1,169 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - + + + + + - - + + - - + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - -
- - - - - + + + + + +
+ + + + +
+ + + + + + +
+ + + + + - - - + + + - - - - - - - - - + + + + + + + + + - - + + - - + + - + - - - - + + + + - - + + - - + + + + + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - - + + - - + + - - + + - - + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/vectors-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/vectors-db.svg new file mode 100644 index 0000000000..25c24b789c --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/vectors-db.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/documents-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/documents-db.svg index a5de231492..2a7dda63eb 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/(assets)/documents-db.svg +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/documents-db.svg @@ -1,98 +1,75 @@ - - - - - - - - + + + + + + + + - - + + - - + + - - - - - - - - - - - - - - - - + + + + + - - - + + + + - - - - - - + - + - + - + - + - + - + - - - - - + - + - + - - - - - + - - + + - - + + - - + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/tables-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/tables-db.svg index 32519e9ab5..69362a7e65 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/(assets)/tables-db.svg +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/tables-db.svg @@ -1,167 +1,172 @@ - - - - - - - + + + + + + + - - - - + + + + - - - - - - + + + + + - - + + - - + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - -
- - - - - + + + + + +
+ + + + +
+ + + + + + +
+ + + + + - - - + + + - - - - - - - - - + + + + + + + + + - - + + - - + + - + - - - - + + + + - - + + - - + + + + + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - - + + - - + + - - + + - - + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/vectors-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/vectors-db.svg new file mode 100644 index 0000000000..a0e777d9eb --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/vectors-db.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/create/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/create/+page.svelte index be114c2354..81b59b07e0 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/create/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/create/+page.svelte @@ -58,6 +58,12 @@ title: 'DocumentsDB', subtitle: 'Store flexible data without a fixed schema. Best for unstructured data and simple querying.' + }, + { + type: 'vectorsdb', + title: 'VectorsDB', + subtitle: + 'Store data as vectors to find similar results. Best for semantic search and recommendations.' } ]; @@ -276,7 +282,7 @@ {/snippet} {#snippet selectDatabaseType(disabled = false)} - + {#each databaseTypes as databaseType}
Promise; getEntity: (params: { databaseId: string; @@ -53,6 +55,15 @@ export type DatabaseSdkResult = { entityId: string; databaseType?: DatabaseType; }) => Promise<{}>; + updateEntity: (params: { + databaseId: string; + entityId: string; + name?: string; + permissions?: string[]; + documentSecurity?: boolean; + enabled?: boolean; + databaseType?: DatabaseType; + }) => Promise; createRecord: (params: { databaseId: string; entityId: string; @@ -98,8 +109,30 @@ export type DatabaseSdkResult = { orders?: OrderBy[]; databaseType?: DatabaseType; }) => Promise; + deleteIndex: (params: { + databaseId: string; + entityId: string; + key: string; + databaseType?: DatabaseType; + }) => Promise<{}>; }; +/** + * Returns the raw DocumentsDB or VectorsDB SDK service for a given database type. + * Use in load functions (.ts) where Svelte runes aren't available. + */ +export function getCollectionService(region: string, project: string, type: DatabaseType) { + const projectSdk = sdk.forProject(region, project); + switch (type) { + case 'documentsdb': + return projectSdk.documentsDB; + case 'vectorsdb': + return projectSdk.vectorsDB; + default: + throw new Error(`Unsupported collection database type: ${type}`); + } +} + export function useDatabaseSdk( regionOrPage: string | Page, projectOrTerminology: string | TerminologyResult, @@ -131,8 +164,9 @@ export function useDatabaseSdk( case 'documentsdb': { return await baseSdk.documentsDB.create(params); } - case 'vectorsdb': - throw new Error('Database type not supported yet'); + case 'vectorsdb': { + return await baseSdk.vectorsDB.create(params); + } default: throw new Error('Unknown database type'); } @@ -140,10 +174,9 @@ export function useDatabaseSdk( async list(params): Promise { const results = await Promise.all([ - baseSdk.tablesDB.list(params) - - // not available just yet! - // baseSdk.documentsDB.list(params), + baseSdk.tablesDB.list(params), + baseSdk.documentsDB.list(params), + baseSdk.vectorsDB.list(params) ]); return results.reduce( @@ -173,8 +206,15 @@ export function useDatabaseSdk( return toSupportiveEntity(table); } - case 'vectorsdb': - throw new Error('Database type not supported yet'); + case 'vectorsdb': { + const collection = await baseSdk.vectorsDB.createCollection({ + ...params, + dimension: params.dimension, + collectionId: params.entityId + }); + + return toSupportiveEntity(collection); + } default: throw new Error('Unknown database type'); } @@ -192,8 +232,10 @@ export function useDatabaseSdk( await baseSdk.documentsDB.listCollections(params); return { total, entities: collections.map(toSupportiveEntity) }; } - case 'vectorsdb': - throw new Error(`Database type not supported yet`); + case 'vectorsdb': { + const { total, collections } = await baseSdk.vectorsDB.listCollections(params); + return { total, entities: collections.map(toSupportiveEntity) }; + } default: throw new Error(`Unknown database type`); } @@ -217,8 +259,14 @@ export function useDatabaseSdk( return toSupportiveEntity(collection); } - case 'vectorsdb': - throw new Error(`Database type not supported yet`); + case 'vectorsdb': { + const collection = await baseSdk.vectorsDB.getCollection({ + databaseId: params.databaseId, + collectionId: params.entityId + }); + + return toSupportiveEntity(collection); + } default: throw new Error(`Unknown database type`); } @@ -232,7 +280,7 @@ export function useDatabaseSdk( case 'documentsdb': return await baseSdk.documentsDB.delete(params); case 'vectorsdb': - throw new Error('Database type not supported yet'); + return await baseSdk.vectorsDB.delete(params); default: throw new Error(`Unknown database type`); } @@ -252,7 +300,51 @@ export function useDatabaseSdk( collectionId: params.entityId }); case 'vectorsdb': - throw new Error('Database type not supported yet'); + return await baseSdk.vectorsDB.deleteCollection({ + databaseId: params.databaseId, + collectionId: params.entityId + }); + default: + throw new Error(`Unknown database type`); + } + }, + + async updateEntity(params) { + switch (type ?? params.databaseType) { + case 'legacy': /* databases api */ + case 'tablesdb': + return toSupportiveEntity( + await baseSdk.tablesDB.updateTable({ + databaseId: params.databaseId, + tableId: params.entityId, + name: params.name, + permissions: params.permissions, + rowSecurity: params.documentSecurity, + enabled: params.enabled + }) + ); + case 'documentsdb': + return toSupportiveEntity( + await baseSdk.documentsDB.updateCollection({ + databaseId: params.databaseId, + collectionId: params.entityId, + name: params.name, + permissions: params.permissions, + documentSecurity: params.documentSecurity, + enabled: params.enabled + }) + ); + case 'vectorsdb': + return toSupportiveEntity( + await baseSdk.vectorsDB.updateCollection({ + databaseId: params.databaseId, + collectionId: params.entityId, + name: params.name, + permissions: params.permissions, + documentSecurity: params.documentSecurity, + enabled: params.enabled + }) + ); default: throw new Error(`Unknown database type`); } @@ -277,8 +369,15 @@ export function useDatabaseSdk( data: params.data, permissions: params.permissions }); - case 'vectorsdb': - throw new Error('Database type not supported yet'); + case 'vectorsdb': { + return await baseSdk.vectorsDB.createDocument({ + databaseId: params.databaseId, + collectionId: params.entityId, + documentId: params.recordId, + data: params.data, + permissions: params.permissions + }); + } default: throw new Error(`Unknown database type`); } @@ -303,8 +402,15 @@ export function useDatabaseSdk( data: params.data, permissions: params.permissions }); - case 'vectorsdb': - throw new Error('Database type not supported yet'); + case 'vectorsdb': { + return await baseSdk.vectorsDB.upsertDocument({ + databaseId: params.databaseId, + collectionId: params.entityId, + documentId: params.recordId, + data: params.data, + permissions: params.permissions + }); + } default: throw new Error(`Unknown database type`); } @@ -327,8 +433,14 @@ export function useDatabaseSdk( documentId: params.recordId, permissions: params.permissions }); - case 'vectorsdb': - throw new Error('Database type not supported yet'); + case 'vectorsdb': { + return await baseSdk.vectorsDB.upsertDocument({ + databaseId: params.databaseId, + collectionId: params.entityId, + documentId: params.recordId, + permissions: params.permissions + }); + } default: throw new Error(`Unknown database type`); } @@ -353,8 +465,19 @@ export function useDatabaseSdk( }); return toSupportiveRecord(document); } - case 'vectorsdb': - throw new Error('Database type not supported yet'); + case 'vectorsdb': { + if (!params.recordId) { + throw new Error('Record ID is required to delete a VectorsDB document'); + } + + const document = await baseSdk.vectorsDB.deleteDocument({ + databaseId: params.databaseId, + collectionId: params.entityId, + documentId: params.recordId + }); + + return toSupportiveRecord(document); + } default: throw new Error(`Unknown database type`); } @@ -379,8 +502,15 @@ export function useDatabaseSdk( }); return { total, records: documents.map(toSupportiveRecord) }; } - case 'vectorsdb': - throw new Error('Database type not supported yet'); + case 'vectorsdb': { + const { total, documents } = await baseSdk.vectorsDB.deleteDocuments({ + databaseId: params.databaseId, + collectionId: params.entityId, + queries: params.queries + }); + + return { total, records: documents.map(toSupportiveRecord) }; + } default: throw new Error(`Unknown database type`); } @@ -413,8 +543,45 @@ export function useDatabaseSdk( }); return toSupportiveIndex(index); } + case 'vectorsdb': { + const index = await baseSdk.vectorsDB.createIndex({ + databaseId: params.databaseId, + collectionId: params.entityId, + key: params.key, + type: params.type as VectorsDBIndexType, + attributes: params.attributes, + lengths: params.lengths, + orders: params.orders + }); + + return toSupportiveIndex(index); + } + default: + throw new Error(`Unknown database type`); + } + }, + + async deleteIndex(params) { + switch (type ?? params.databaseType) { + case 'legacy': /* databases api */ + case 'tablesdb': + return await baseSdk.tablesDB.deleteIndex({ + databaseId: params.databaseId, + tableId: params.entityId, + key: params.key + }); + case 'documentsdb': + return await baseSdk.documentsDB.deleteIndex({ + databaseId: params.databaseId, + collectionId: params.entityId, + key: params.key + }); case 'vectorsdb': - throw new Error('Database type not supported yet'); + return await baseSdk.vectorsDB.deleteIndex({ + databaseId: params.databaseId, + collectionId: params.entityId, + key: params.key + }); default: throw new Error(`Unknown database type`); } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts index b76de1b741..7de58a271f 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts @@ -6,10 +6,13 @@ import type { Attributes, Collection, Columns, Table } from '$database/store'; import type { Term, TerminologyResult, TerminologyShape } from '$database/(entity)/helpers/types'; type BaseTerminology = typeof baseTerminology; -type ImplementedDBTypes = Omit; +type ImplementedDBTypes = Omit; /* manual type for the time being because vectorsdb is pending */ export type DatabaseType = 'legacy' | 'tablesdb' | 'documentsdb' | 'vectorsdb'; +export type CollectionDatabaseType = Extract; + +export const DEFAULT_VECTOR_DIMENSION = 768; export type RecordType = ImplementedDBTypes[keyof ImplementedDBTypes]['record']; @@ -18,6 +21,7 @@ export type Entity = Partial & { indexes?: Index[]; fields?: (Attributes | Columns)[]; recordSecurity?: Models.Collection['documentSecurity'] | Models.Table['rowSecurity']; + dimension?: number; }; export type Field = Partial | Partial; @@ -62,7 +66,11 @@ export const baseTerminology = { field: 'attribute', record: 'document' }, - vectordb: {} + vectorsdb: { + entity: 'collection', + field: 'attribute', + record: 'document' + } } as const; const createTerm = (singular: string, pluralForm: string): Term => { diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/create.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/create.svelte index 6c5af55ba5..1f3d84826a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/create.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/create.svelte @@ -4,14 +4,14 @@ import { Modal, CustomId } from '$lib/components'; import { subNavigation } from '$lib/stores/database'; import { ID } from '@appwrite.io/console'; - import { Button, InputText } from '$lib/elements/forms'; + import { Button, InputNumber, InputText } from '$lib/elements/forms'; import { addNotification } from '$lib/stores/notifications'; import { Input as SuggestionsInput, entityColumnSuggestions } from '$database/(suggestions)/index'; - import { getTerminologies } from '../helpers'; + import { getTerminologies, DEFAULT_VECTOR_DIMENSION } from '$database/(entity)'; import { resetSampleFieldsConfig } from '$database/store'; let { @@ -21,7 +21,7 @@ }: { show: boolean; useSuggestions?: boolean; - onCreateEntity: (id: string, name: string) => Promise; + onCreateEntity: (id: string, name: string, dimension?: number) => Promise; } = $props(); const { analytics, terminology } = getTerminologies(); @@ -29,12 +29,14 @@ const lower = terminology.entity.lower.singular; const title = terminology.entity.title.singular; const analyticsCreateSubmit = analytics.submit.entity('Create'); + const isVectorsDb = terminology.type === 'vectorsdb'; // example - `table-[table]`, `collection-[collection]` const isOnEntitiesPage = $derived(page.route?.id.endsWith(`${lower}-[${lower}]`)); let name = $state(''); let id = $state(null); + let dimension = $state(DEFAULT_VECTOR_DIMENSION); let error = $state(null); let creatingEntity = $state(false); @@ -64,7 +66,7 @@ enableThinkingModeForSuggestions(finalId, name); // create entity. - await onCreateEntity(finalId, name); + await onCreateEntity(finalId, name, isVectorsDb ? dimension : undefined); // cleanup updateAndCleanup(); @@ -121,6 +123,16 @@ + {#if isVectorsDb} + + {/if} + {#if useSuggestions} {/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/activity.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/activity.svelte index a9fc197224..544f7dbfcc 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/activity.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/field/activity.svelte @@ -7,6 +7,7 @@ import { Skeleton } from '@appwrite.io/pink-svelte'; import { type Models, Query } from '@appwrite.io/console'; import { getTerminologies, type Record, toSupportiveRecord } from '$database/(entity)'; + import { getCollectionService } from '$database/(entity)/helpers/sdk'; const { record @@ -32,15 +33,18 @@ const { $databaseId: databaseId, entityId, $id: recordId } = toSupportiveRecord(record); - if (terminology.type === 'documentsdb') { - recordActivityLogs = await sdk - .forProject(page.params.region, page.params.project) - .documentsDB.listDocumentLogs({ - databaseId: databaseId, - collectionId: entityId, - documentId: recordId, - queries: [Query.limit(limit), Query.offset(offset)] - }); + if (terminology.type === 'documentsdb' || terminology.type === 'vectorsdb') { + const collectionService = getCollectionService( + page.params.region, + page.params.project, + terminology.type + ); + recordActivityLogs = await collectionService.listDocumentLogs({ + databaseId: databaseId, + collectionId: entityId, + documentId: recordId, + queries: [Query.limit(limit), Query.offset(offset)] + }); } else { recordActivityLogs = await sdk .forProject(page.params.region, page.params.project) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/empty.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/empty.svelte index afa952b020..5498131b15 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/empty.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/views/layouts/empty.svelte @@ -292,7 +292,7 @@ const spreadsheetColumns = $derived.by(() => { return isRecordMode - ? type !== 'documentsdb' + ? type === 'tablesdb' || type === 'legacy' ? getRowColumns() : getDocumentsDbColumns() : getIndexesColumns(); @@ -382,7 +382,7 @@ {#snippet noSqlEditor()} {#if showNoSqlEditor} - {#if type === 'documentsdb' && mode === 'records'} + {#if (type === 'documentsdb' || type === 'vectorsdb') && mode === 'records'} {/if} {/if} @@ -411,7 +411,8 @@ {#if showActions && actions} - {@const isOnlyIndexes = mode === 'indexes' && type === 'documentsdb'} + {@const isOnlyIndexes = + mode === 'indexes' && (type === 'documentsdb' || type === 'vectorsdb')} {@const inline = mode === 'records-filtered' || isOnlyIndexes}
{ switch (type) { default: @@ -45,20 +47,19 @@ : `Smart ${field.singular} suggestions available on Cloud`; case 'documentsdb': + case 'vectorsdb': return featureActive ? `Sample Data` : `Sample Data available on Cloud`; } }); const subtitle = $derived.by(() => { - const isDocs = type === 'documentsdb'; - if (featureActive) { - return isDocs + return isSchemaless ? `Generate sample ${record.plural} based on your ${entity} name` : `Enable AI to suggest useful ${field.plural} based on your ${entity} name`; } - return isDocs + return isSchemaless ? `Sign up for Cloud to generate sample documents based on your ${entity} name` : `Sign up for Cloud to generate ${field.plural} based on your ${entity} name`; }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.svelte index 9c2b218714..ca7bcdabc9 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.svelte @@ -42,11 +42,12 @@ $registerSearchers(tablesSearcher); - async function createEntity(entityId: string, name: string) { + async function createEntity(entityId: string, name: string, dimension?: number) { const entity = await databaseSdk.createEntity({ databaseId, entityId, - name + name, + dimension }); await invalidate(Dependencies.DATABASE); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/embeddingModal.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/embeddingModal.svelte new file mode 100644 index 0000000000..51d427291b --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/embeddingModal.svelte @@ -0,0 +1,84 @@ + + + +

+ Enter the content you want to convert into a vector. This will allow semantic search and + similarity matching. +

+ + + + + + Embeddings are generated using Embedding Gemma model + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/view.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/view.svelte index b08c1244e4..682f31ce5a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/view.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/view.svelte @@ -15,6 +15,7 @@ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/spreadsheet.svelte index 14a00e2c3e..82bf056fc5 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/spreadsheet.svelte @@ -60,6 +60,7 @@ type JsonValue, NoSqlEditor } from '$database/collection-[collection]/(components)/editor'; + import EmbeddingModal from '$database/collection-[collection]/(components)/editor/embeddingModal.svelte'; import { buildFieldUrl } from '$database/(entity)/helpers/navigation'; import { SpreadsheetOptions, @@ -75,13 +76,15 @@ $: if ($documents) { paginatedDocuments.clear(); + const docs = $documents.documents; + // If we have a new document, add it at the start if ($noSqlDocument.isDirty && $noSqlDocument.isNew) { const tempDoc = $noSqlDocument.document as Models.DefaultDocument; - const docsWithTemp = [tempDoc, ...$documents.documents]; + const docsWithTemp = [tempDoc, ...docs]; paginatedDocuments.setPage(1, docsWithTemp); } else { - paginatedDocuments.setPage(1, $documents.documents); + paginatedDocuments.setPage(1, docs); } } @@ -89,6 +92,25 @@ const collectionId = page.params.collection; const databaseSdk = useDatabaseSdk(page.params.region, page.params.project, data.database.type); + const isVectorsDb = data.database.type === 'vectorsdb'; + let showEmbeddingModal = false; + let editorRef: { replaceData: (data: JsonValue) => void } | undefined; + + const projectSdk = sdk.forProject(page.params.region, page.params.project); + const listDocumentsFn = isVectorsDb + ? projectSdk.vectorsDB.listDocuments.bind(projectSdk.vectorsDB) + : projectSdk.documentsDB.listDocuments.bind(projectSdk.documentsDB); + const getDocumentFn = isVectorsDb + ? projectSdk.vectorsDB.getDocument.bind(projectSdk.vectorsDB) + : projectSdk.documentsDB.getDocument.bind(projectSdk.documentsDB); + + function handleEmbeddingGenerated(embeddings: number[]) { + if ($noSqlDocument.document && typeof $noSqlDocument.document === 'object') { + const updated = { ...$noSqlDocument.document, embeddings }; + editorRef?.replaceData(updated); + } + } + const emptyCellsLimit = $spreadsheetLoading ? 30 : $isSmallViewport @@ -115,13 +137,11 @@ const documentId = $noSqlDocument.documentId; noSqlDocument.update({ documentId: null }); // reset for later! - const loadedDocument = await sdk - .forProject(page.params.region, page.params.project) - .documentsDB.getDocument({ - databaseId: page.params.database, - collectionId: page.params.collection, - documentId - }); + const loadedDocument = await getDocumentFn({ + databaseId: page.params.database, + collectionId: page.params.collection, + documentId + }); if (loadedDocument) { noSqlDocument.edit(loadedDocument); @@ -462,7 +482,9 @@ spreadsheetRenderKey.set(hash(Date.now().toString())); const firstDocument = $documents?.documents?.[0]; if (firstDocument) { - noSqlDocument.update({ document: firstDocument }); + noSqlDocument.update({ + document: firstDocument + }); } } catch (error) { addNotification({ @@ -492,19 +514,17 @@ const filterQueries = parsedQueries.size ? data.parsedQueries.values() : []; $paginatedDocumentsLoading = true; - const loadedRows = await sdk - .forProject(page.params.region, page.params.project) - .documentsDB.listDocuments({ - databaseId, - collectionId, - queries: [ - getCorrectOrderQuery(), - Query.limit(SPREADSHEET_PAGE_LIMIT), - Query.offset(pageToOffset(pageNumber, SPREADSHEET_PAGE_LIMIT)), - ...filterQueries /* filter queries */, - ...buildWildcardEntitiesQuery(collection) - ] - }); + const loadedRows = await listDocumentsFn({ + databaseId, + collectionId, + queries: [ + getCorrectOrderQuery(), + Query.limit(SPREADSHEET_PAGE_LIMIT), + Query.offset(pageToOffset(pageNumber, SPREADSHEET_PAGE_LIMIT)), + ...filterQueries /* filter queries */, + ...buildWildcardEntitiesQuery(collection) + ] + }); paginatedDocuments.setPage(pageNumber, loadedRows.documents); $paginatedDocumentsLoading = false; @@ -520,18 +540,16 @@ paginatedDocuments.setMaxPage(targetPageNum); $paginatedDocumentsLoading = true; - const loadedRows = await sdk - .forProject(page.params.region, page.params.project) - .documentsDB.listDocuments({ - databaseId, - collectionId, - queries: [ - getCorrectOrderQuery(), - Query.limit(SPREADSHEET_PAGE_LIMIT), - Query.offset(pageToOffset(targetPageNum, SPREADSHEET_PAGE_LIMIT)), - ...buildWildcardEntitiesQuery(collection) - ] - }); + const loadedRows = await listDocumentsFn({ + databaseId, + collectionId, + queries: [ + getCorrectOrderQuery(), + Query.limit(SPREADSHEET_PAGE_LIMIT), + Query.offset(pageToOffset(targetPageNum, SPREADSHEET_PAGE_LIMIT)), + ...buildWildcardEntitiesQuery(collection) + ] + }); paginatedDocuments.setPage(targetPageNum, loadedRows.documents); $paginatedDocumentsLoading = false; @@ -553,14 +571,29 @@ const MIN_DOCS_FOR_FUZZY_SUGGESTIONS = 5; $: useMockSuggestions = + !isVectorsDb && $noSqlDocument.isNew && ($documents?.documents?.length ?? 0) < MIN_DOCS_FOR_FUZZY_SUGGESTIONS; + $: metadataKeys = + isVectorsDb && $documents?.documents + ? (fuzzySearchKeys( + $documents.documents.map((d) => d.metadata ?? {}), + { minOccurrences: 2 } + ) ?? []) + : []; + + $: vectorsDbMetadataDefaults = isVectorsDb + ? Object.fromEntries(metadataKeys.map((key) => [key, ''])) + : {}; + $: suggestedAttributes = $noSqlDocument.isNew && $documents?.documents - ? useMockSuggestions - ? mockSuggestions.columns.map((column) => column.name) - : (fuzzySearchKeys($documents.documents, { minOccurrences: 2 }) ?? []) + ? isVectorsDb + ? ['metadata', 'embeddings'] + : useMockSuggestions + ? mockSuggestions.columns.map((column) => column.name) + : (fuzzySearchKeys($documents.documents, { minOccurrences: 2 }) ?? []) : []; $: showSuggestions = $noSqlDocument.isNew && suggestedAttributes.length > 0; @@ -863,6 +896,7 @@ {#snippet noSqlEditor()} { const firstDocument = $documents?.documents?.[0]; if (firstDocument) { @@ -881,7 +921,8 @@ } }} onSave={async (document) => await createOrUpdateDocument(document)} - onChange={(_, hasDataChanged) => noSqlDocument.update({ hasDataChanged })} /> + onChange={(_, hasDataChanged) => noSqlDocument.update({ hasDataChanged })} + onGenerateEmbedding={isVectorsDb ? () => (showEmbeddingModal = true) : undefined} /> {/snippet} {#snippet sideSheetHeaderAction()} @@ -981,6 +1022,10 @@ {/if} +{#if isVectorsDb} + +{/if} + diff --git a/src/routes/(console)/project-[region]-[project]/databases/empty.svelte b/src/routes/(console)/project-[region]-[project]/databases/empty.svelte index 52d058931f..85a01d15b4 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/empty.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/empty.svelte @@ -14,7 +14,10 @@ import DocumentsDB from './(assets)/documents-db.svg'; import DocumentsDBDark from './(assets)/dark/documents-db.svg'; - import { isSmallViewport, isViewPortWidthInRange } from '$lib/stores/viewport'; + import VectorsDB from './(assets)/vectors-db.svg'; + import VectorsDBDark from './(assets)/dark/vectors-db.svg'; + + import { isSmallViewport } from '$lib/stores/viewport'; import type { DatabaseType } from '$database/(entity)'; const { @@ -29,8 +32,7 @@ /*const mongoDbImage = $derived(isDark ? MongoDBDark : MongoDB);*/ const tablesDbImage = $derived(isDark ? TablesDBDark : TablesDB); const documentsDbImage = $derived(isDark ? DocumentsDBDark : DocumentsDB); - - const inRangeStore = isViewPortWidthInRange(1024, 1280); + const vectorsDbImage = $derived(isDark ? VectorsDBDark : VectorsDB); {#if $isSmallViewport} @@ -50,7 +52,7 @@ >Store, organize, and manage your app data
- + {@render databaseTypeCard({ type: 'tablesdb', @@ -68,6 +70,15 @@ 'Store flexible data without a fixed schema. Best for unstructured data and simple querying.', image: documentsDbImage })} + + + {@render databaseTypeCard({ + type: 'vectorsdb', + title: 'VectorsDB', + subtitle: + 'Store data as vectors to find similar results. Best for semantic search and recommendations.', + image: vectorsDbImage + })} {/snippet} @@ -79,25 +90,16 @@ padding="none" {disabled} on:click={() => onDatabaseTypeSelected?.(type)}> - {@const direction = $isSmallViewport || $inRangeStore ? 'column' : 'row'} - - database type artwork - - {#if $isSmallViewport || $inRangeStore} - - {/if} + + database type artwork + + + style="padding: var(--gap-xl); flex: 1;"> + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/images/databases/empty-vectorsdb-light.svg b/static/images/databases/empty-vectorsdb-light.svg new file mode 100644 index 0000000000..a3827de2ce --- /dev/null +++ b/static/images/databases/empty-vectorsdb-light.svg @@ -0,0 +1,82 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +