From 594a673337a421f713753039014679ff5175b92f Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 15 Sep 2025 20:02:39 +0530 Subject: [PATCH 1/9] feat: ai suggestions [wip]. --- .../database-[database]/createTable.svelte | 9 +++ .../database-[database]/icon/ai.svelte | 68 +++++++++++++++++++ .../database-[database]/suggestions.svelte | 55 +++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte index 4e42ee4425..2a5f509ebd 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte @@ -9,6 +9,8 @@ import { ID } from '@appwrite.io/console'; import { createEventDispatcher } from 'svelte'; import { subNavigation } from '$lib/stores/database'; + import Suggestions from './suggestions.svelte'; + import type { TableColumnSuggestions } from './suggestions.svelte'; let { showCreate = $bindable(false) @@ -24,6 +26,11 @@ let touchedId = $state(false); let error: string = $state(null); + let suggestions: TableColumnSuggestions = $state({ + enabled: false, + context: null + }) + const create = async () => { error = null; try { @@ -104,6 +111,8 @@ } }} /> + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte new file mode 100644 index 0000000000..07614291cc --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte @@ -0,0 +1,68 @@ + +
+ + + + + + + + + + + + + + + + + + + +
+ + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte new file mode 100644 index 0000000000..38cfbec081 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte @@ -0,0 +1,55 @@ + + + + + + + + + + Smart column suggestions + + + + + + Enable AI to suggest useful columns based on your table name + + + + + {#if suggestions.enabled} +
+ +
+ {/if} +
+
From 3ed568d777b33f37f6f5a44d012eb0dffb6aa885 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 16 Sep 2025 19:42:34 +0530 Subject: [PATCH 2/9] add: some nice stuff. --- package.json | 2 +- pnpm-lock.yaml | 10 +- .../database-[database]/createTable.svelte | 8 +- .../database-[database]/icon/ai.svelte | 19 +- .../databases/database-[database]/store.ts | 12 + .../database-[database]/subNavigation.svelte | 5 +- .../database-[database]/suggestions.svelte | 39 +- .../table-[table]/+page.svelte | 4 + .../layout/suggestionsEmptySheet.svelte | 468 ++++++++++++++++++ 9 files changed, 524 insertions(+), 43 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/suggestionsEmptySheet.svelte diff --git a/package.json b/package.json index cd4e9b0600..183d358a8e 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134", + "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f4da718", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7477762221..3a65d67e93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.25.3)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134 - version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134 + specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407 + version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 @@ -260,8 +260,8 @@ packages: '@analytics/type-utils@0.6.2': resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134': - resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407': + resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3700,7 +3700,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134': {} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte index 2a5f509ebd..e6d7dd08eb 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte @@ -10,7 +10,6 @@ import { createEventDispatcher } from 'svelte'; import { subNavigation } from '$lib/stores/database'; import Suggestions from './suggestions.svelte'; - import type { TableColumnSuggestions } from './suggestions.svelte'; let { showCreate = $bindable(false) @@ -26,11 +25,6 @@ let touchedId = $state(false); let error: string = $state(null); - let suggestions: TableColumnSuggestions = $state({ - enabled: false, - context: null - }) - const create = async () => { error = null; try { @@ -111,7 +105,7 @@ } }} /> - + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte index 07614291cc..27bc311989 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte @@ -1,5 +1,8 @@ - -
+ + + -
+ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts index 36e755d77e..c06d8be468 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts @@ -4,9 +4,21 @@ import type { Models } from '@appwrite.io/console'; import { derived, writable } from 'svelte/store'; import { IconChartBar, IconCloudUpload, IconCog } from '@appwrite.io/pink-icons-svelte'; +export type TableColumnSuggestions = { + enabled: boolean; + thinking: boolean; + context?: string | null; +}; + export const database = derived(page, ($page) => $page.data.database as Models.Database); export const showCreateTable = writable(false); +export const tableColumnSuggestions = writable({ + enabled: false, + context: null, + thinking: false +}); + export const tableViewColumns = writable([ { id: '$id', title: 'Table ID', type: 'string', width: 200 }, { id: 'name', title: 'Name', type: 'string', width: { min: 120 } }, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte index 864a133cc1..a9d1abc29e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte @@ -145,7 +145,10 @@ - +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte index 38cfbec081..0baab18915 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte @@ -1,19 +1,14 @@ @@ -26,11 +21,12 @@ Smart column suggestions - +
+ +
@@ -39,17 +35,22 @@
- {#if suggestions.enabled} + {#if $tableColumnSuggestions.enabled}
+ bind:value={$tableColumnSuggestions.context} + placeholder="Optional: Add context to improve suggestions" />
{/if} + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte index 7a52f59e90..f52a0eb0a3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte @@ -29,8 +29,10 @@ import { IconChevronDown, IconChevronUp, IconPlus } from '@appwrite.io/pink-icons-svelte'; import type { Models } from '@appwrite.io/console'; import EmptySheet from './layout/emptySheet.svelte'; + import SuggestionsEmptySheet from './layout/suggestionsEmptySheet.svelte'; import CreateRow from './rows/create.svelte'; import { onDestroy } from 'svelte'; + import { tableColumnSuggestions } from '../store'; export let data: PageData; @@ -215,6 +217,8 @@ } }} /> {/if} + {:else if $tableColumnSuggestions.thinking} + {:else} + import { + Button, + Icon, + Layout, + Spinner, + Spreadsheet, + Typography, + FloatingActionBar + } from '@appwrite.io/pink-svelte'; + import { IconCalendar, IconFingerPrint, IconPlus } from '@appwrite.io/pink-icons-svelte'; + import { isSmallViewport } from '$lib/stores/viewport'; + import { SortButton } from '$lib/components'; + import type { Column } from '$lib/helpers/types'; + import { expandTabs } from '../store'; + import SpreadsheetContainer from './spreadsheet.svelte'; + import { onDestroy, onMount } from 'svelte'; + import { debounce } from '$lib/helpers/debounce'; + import { tableColumnSuggestions } from '../../store'; + + const { customColumns = [] }: { customColumns?: Column[] } = $props(); + + // can also be `__select_undefined` if `$id` needs to be covered on overlay! + const firstColumn = '$id'; + + let resizeObserver: ResizeObserver; + let spreadsheetContainer: HTMLElement; + + let hScroller: HTMLElement | null = null; + let headerElement: HTMLElement | null = null; + let rangeOverlayEl: HTMLDivElement | null = null; + + const baseColProps = { draggable: false, resizable: false }; + + const findHorizontalScroller = (root: HTMLElement | null): HTMLElement | null => { + let el = root as HTMLElement | null; + while (el && el !== document.body) { + const st = getComputedStyle(el); + if ( + (st.overflowX === 'auto' || st.overflowX === 'scroll') && + el.scrollWidth > el.clientWidth + ) { + return el; + } + el = el.parentElement as HTMLElement | null; + } + return null; + }; + + const updateOverlayHeight = () => { + if (!spreadsheetContainer) return; + if (!headerElement || !headerElement.isConnected) { + headerElement = spreadsheetContainer.querySelector('[role="rowheader"]'); + } + if (!headerElement) return; + + const headerRect = headerElement.getBoundingClientRect(); + const containerRect = spreadsheetContainer.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + const dynamicHeightPx = viewportHeight - headerRect.bottom; + const overlayTop = Math.round(headerRect.bottom - containerRect.top); + const overlayHeight = $expandTabs + ? `${dynamicHeightPx}px` + : `calc(${dynamicHeightPx}px - 89px)`; + + spreadsheetContainer.style.setProperty('--overlay-top', `${overlayTop}px`); + spreadsheetContainer.style.setProperty('--overlay-height', overlayHeight); + }; + + // Measure first content header + // and the cell before `actions`|`empty` + const updateOverlayBounds = () => { + if (!spreadsheetContainer) return; + if (!headerElement || !headerElement.isConnected) { + headerElement = spreadsheetContainer.querySelector('[role="rowheader"]'); + if (!headerElement) return; + } + + // hook the actual horizontal scroller once + if (!hScroller || !hScroller.isConnected) { + hScroller = findHorizontalScroller(headerElement); + if (hScroller) hScroller.addEventListener('scroll', debouncedRecalc, { passive: true }); + } + + const containerRect = spreadsheetContainer.getBoundingClientRect(); + const getById = (id: string) => + headerElement!.querySelector( + `[role="cell"][data-header="true"][data-column-id="${id}"]` + ); + + const firstColumnCell = getById(firstColumn); + const actionsCell = getById('actions'); + const emptyCell = getById('empty'); + + // start = first content cell after `firstColumn` + // (skip actions/empty if they were there) + let startCell: HTMLElement | null = null; + if (firstColumnCell) { + let node = firstColumnCell.nextElementSibling as HTMLElement | null; + while ( + node && + (node.dataset.columnId === 'actions' || node.dataset.columnId === 'empty') + ) { + node = node.nextElementSibling as HTMLElement | null; + } + startCell = node; + } + + // end = cell right BEFORE actions; + // else right BEFORE empty; else last content cell + const before = (el: HTMLElement | null) => { + if (!el) return null; + let previous = el.previousElementSibling as HTMLElement | null; + while ( + previous && + (previous.dataset.columnId === firstColumn || + previous.getAttribute('data-select') === 'true') + ) { + previous = previous.previousElementSibling as HTMLElement | null; + } + return previous; + }; + + let endCell = before(actionsCell) ?? before(emptyCell); + if (!endCell) { + const cells = Array.from( + headerElement.querySelectorAll('[role="cell"][data-header="true"]') + ); + for (let index = cells.length - 1; index >= 0; index--) { + const cell = cells[index]; + const cellId = cell.dataset.columnId; + const isSelect = cell.getAttribute('data-select') === 'true'; + if (!isSelect && cellId !== firstColumn && cellId !== 'actions' && cellId !== 'empty') { + endCell = cell; + break; + } + } + } + + if (!startCell || !endCell) { + if (rangeOverlayEl) rangeOverlayEl.style.display = 'none'; + return; + } + + const start = startCell.getBoundingClientRect(); + const end = endCell.getBoundingClientRect(); + const left = Math.round(start.left - containerRect.left); + const width = Math.max(0, Math.round(end.right - end.left)); + + spreadsheetContainer.style.setProperty('--group-left', `${left}px`); + spreadsheetContainer.style.setProperty('--group-width', `${width}px`); + + if (rangeOverlayEl) rangeOverlayEl.style.display = width > 0 ? 'block' : 'none'; + }; + + const recalcAll = () => { + updateOverlayHeight(); + updateOverlayBounds(); + }; + + const debouncedRecalc = debounce(recalcAll, 50); + + onMount(() => { + if (spreadsheetContainer) { + resizeObserver = new ResizeObserver(debouncedRecalc); + resizeObserver.observe(spreadsheetContainer); + } + + requestAnimationFrame(recalcAll); + }); + + onDestroy(() => { + resizeObserver?.disconnect(); + hScroller?.removeEventListener('scroll', debouncedRecalc); + }); + + const getCustomColumns = (): Column[] => + customColumns.map((col: Column) => ({ ...col, width: 180, hide: false, ...baseColProps })); + + const getRowColumns = (): Column[] => [ + { + id: '$id', + title: '$id', + type: 'string', + width: 180, + icon: IconFingerPrint, + ...baseColProps + }, + ...getCustomColumns(), + { + id: '$createdAt', + title: '$createdAt', + type: 'datetime', + width: 180, + icon: IconCalendar, + ...baseColProps + }, + { + id: '$updatedAt', + title: '$updatedAt', + type: 'datetime', + width: 180, + icon: IconCalendar, + ...baseColProps + }, + { + id: 'actions', + title: '', + type: 'string', + icon: IconPlus, + width: customColumns.length ? 555 : 832, + ...baseColProps + }, + { id: 'empty', title: '', type: 'string', ...baseColProps } + ]; + + const spreadsheetColumns = getRowColumns(); + const emptyCells = $derived(($isSmallViewport ? 14 : 17) + (!$expandTabs ? 2 : 0)); + + + + +
+
+ + + {#if $tableColumnSuggestions.thinking} +
+ + + + + + Thinking of column suggestions + + + + + { + $tableColumnSuggestions.context = null; + $tableColumnSuggestions.enabled = false; + $tableColumnSuggestions.thinking = false; + }} + >Cancel + + + +
+ {/if} +
+ + + {}}> + + {#each spreadsheetColumns as column (column.id)} + + {#if column.isAction} + + + + {:else if column.id === 'actions' || column.id === 'empty'} + {column.title} + {:else} + + {column.title} + + + {/if} + + {/each} + + + + +
+
+
+ + From d2761825dd698c2ee66caadcc6885b9569456329 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 16 Sep 2025 19:46:27 +0530 Subject: [PATCH 3/9] add: some nice stuff. --- .../databases/database-[database]/subNavigation.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte index a9d1abc29e..7fea1eb73b 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte @@ -145,6 +145,7 @@ + Date: Wed, 17 Sep 2025 16:39:48 +0530 Subject: [PATCH 4/9] update: misc fixes and updates. --- .../database-[database]/subNavigation.svelte | 9 +- .../layout/suggestionsEmptySheet.svelte | 93 ++++++++++--------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte index 7fea1eb73b..554e12e468 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte @@ -4,6 +4,8 @@ import { showCreateTable, databaseSubNavigationItems } from './store'; import type { PageData } from './$types'; import { showSubNavigation } from '$lib/stores/layout'; + import { bannerSpacing } from '$lib/layout/headerAlert.svelte'; + import { Icon, Sidebar, @@ -51,6 +53,11 @@ const isMainDatabaseScreen = $derived(page.route.id.endsWith('database-[database]')); + // If banner open, `-1rem` to adjust banner size, else `-70.5px`. + // 70.5px is the size of the container of the banner holder and not just the banner! + // Needed because things vary a bit much on how different browsers treat bottom layouts. + const bottomNavHeight = $derived(`calc(20% ${$bannerSpacing ? '- 1rem' : '- 70.5px'})`); + async function loadTables() { tables = await sdk.forProject(region, project).tablesDB.listTables({ databaseId: databaseId, @@ -149,7 +156,7 @@ + style="bottom: 1rem; position: relative; height: {bottomNavHeight}">
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/suggestionsEmptySheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/suggestionsEmptySheet.svelte index 432339fd3b..f138ef4af6 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/suggestionsEmptySheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/suggestionsEmptySheet.svelte @@ -18,10 +18,20 @@ import { debounce } from '$lib/helpers/debounce'; import { tableColumnSuggestions } from '../../store'; - const { customColumns = [] }: { customColumns?: Column[] } = $props(); + const { + customColumns = [] + }: { + customColumns?: Column[]; + } = $props(); + + /** + * flip this when you want to + * exclude or include the `$id` for colored overlay! + */ + const useFirstColumnAsId = false; // can also be `__select_undefined` if `$id` needs to be covered on overlay! - const firstColumn = '$id'; + const firstColumn = useFirstColumnAsId ? '$id' : '__select_undefined'; let resizeObserver: ResizeObserver; let spreadsheetContainer: HTMLElement; @@ -33,16 +43,17 @@ const baseColProps = { draggable: false, resizable: false }; const findHorizontalScroller = (root: HTMLElement | null): HTMLElement | null => { - let el = root as HTMLElement | null; - while (el && el !== document.body) { - const st = getComputedStyle(el); + let element = root as HTMLElement | null; + while (element && element !== document.body) { + const computedStyles = getComputedStyle(element); if ( - (st.overflowX === 'auto' || st.overflowX === 'scroll') && - el.scrollWidth > el.clientWidth + (computedStyles.overflowX === 'auto' || computedStyles.overflowX === 'scroll') && + element.scrollWidth > element.clientWidth ) { - return el; + return element; } - el = el.parentElement as HTMLElement | null; + + element = element.parentElement as HTMLElement | null; } return null; }; @@ -52,6 +63,7 @@ if (!headerElement || !headerElement.isConnected) { headerElement = spreadsheetContainer.querySelector('[role="rowheader"]'); } + if (!headerElement) return; const headerRect = headerElement.getBoundingClientRect(); @@ -89,8 +101,8 @@ ); const firstColumnCell = getById(firstColumn); - const actionsCell = getById('actions'); - const emptyCell = getById('empty'); + // const emptyCell = getById('empty'); + // const actionsCell = getById('actions'); // start = first content cell after `firstColumn` // (skip actions/empty if they were there) @@ -108,49 +120,38 @@ // end = cell right BEFORE actions; // else right BEFORE empty; else last content cell - const before = (el: HTMLElement | null) => { - if (!el) return null; - let previous = el.previousElementSibling as HTMLElement | null; - while ( - previous && - (previous.dataset.columnId === firstColumn || - previous.getAttribute('data-select') === 'true') - ) { - previous = previous.previousElementSibling as HTMLElement | null; - } - return previous; - }; - - let endCell = before(actionsCell) ?? before(emptyCell); - if (!endCell) { - const cells = Array.from( - headerElement.querySelectorAll('[role="cell"][data-header="true"]') - ); - for (let index = cells.length - 1; index >= 0; index--) { - const cell = cells[index]; - const cellId = cell.dataset.columnId; - const isSelect = cell.getAttribute('data-select') === 'true'; - if (!isSelect && cellId !== firstColumn && cellId !== 'actions' && cellId !== 'empty') { - endCell = cell; - break; - } - } - } - - if (!startCell || !endCell) { + // const before = (element: HTMLElement | null) => { + // if (!element) return null; + // let previous = element.previousElementSibling as HTMLElement | null; + // while ( + // previous && + // (previous.dataset.columnId === firstColumn || + // previous.getAttribute('data-select') === 'true') + // ) { + // previous = previous.previousElementSibling as HTMLElement | null; + // } + // return previous; + // }; + + // let endCell = before(actionsCell) ?? before(emptyCell); + // let endCell = before(emptyCell); + + if (!startCell /* || !endCell*/) { if (rangeOverlayEl) rangeOverlayEl.style.display = 'none'; return; } const start = startCell.getBoundingClientRect(); - const end = endCell.getBoundingClientRect(); + // const end = endCell.getBoundingClientRect(); const left = Math.round(start.left - containerRect.left); - const width = Math.max(0, Math.round(end.right - end.left)); + // const width = Math.max(0, Math.round(end.right - start.right)); spreadsheetContainer.style.setProperty('--group-left', `${left}px`); - spreadsheetContainer.style.setProperty('--group-width', `${width}px`); + // spreadsheetContainer.style.setProperty('--group-width', `${width}px`); - if (rangeOverlayEl) rangeOverlayEl.style.display = width > 0 ? 'block' : 'none'; + // if (rangeOverlayEl) { + // rangeOverlayEl.style.display = width > 0 ? 'block' : 'none'; + // } }; const recalcAll = () => { @@ -379,7 +380,7 @@ } & .floating-action-wrapper :global(:first-child) { - left: 55%; + left: 45%; /* change this value if the firstColumn is changed for overlay logic.*/ z-index: 21; } From c2782497b6ebd96dd34d2844b0edddf927c03b14 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 22 Sep 2025 10:43:40 +0530 Subject: [PATCH 5/9] update: progress. --- package.json | 2 +- pnpm-lock.yaml | 10 +- .../empty.svelte} | 117 +++++++++--------- .../{ => (suggestions)}/icon/ai.svelte | 0 .../(suggestions)/index.ts | 3 + .../input.svelte} | 5 - .../(suggestions)/store.ts | 13 ++ .../database-[database]/createTable.svelte | 4 +- .../databases/database-[database]/store.ts | 12 -- .../table-[table]/+page.svelte | 5 +- 10 files changed, 82 insertions(+), 89 deletions(-) rename src/routes/(console)/project-[region]-[project]/databases/database-[database]/{table-[table]/layout/suggestionsEmptySheet.svelte => (suggestions)/empty.svelte} (89%) rename src/routes/(console)/project-[region]-[project]/databases/database-[database]/{ => (suggestions)}/icon/ai.svelte (100%) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/index.ts rename src/routes/(console)/project-[region]-[project]/databases/database-[database]/{suggestions.svelte => (suggestions)/input.svelte} (92%) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts diff --git a/package.json b/package.json index 183d358a8e..2461a3927f 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407", + "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f4da718", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a65d67e93..b13d20c86b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.25.3)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407 - version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407 + specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74 + version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 @@ -260,8 +260,8 @@ packages: '@analytics/type-utils@0.6.2': resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407': - resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74': + resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3700,7 +3700,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407': {} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/suggestionsEmptySheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte similarity index 89% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/suggestionsEmptySheet.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte index f138ef4af6..49b3b6291a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/suggestionsEmptySheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte @@ -12,16 +12,19 @@ import { isSmallViewport } from '$lib/stores/viewport'; import { SortButton } from '$lib/components'; import type { Column } from '$lib/helpers/types'; - import { expandTabs } from '../store'; - import SpreadsheetContainer from './spreadsheet.svelte'; + import { type Columns, expandTabs } from '../table-[table]/store'; + import SpreadsheetContainer from '../table-[table]/layout/spreadsheet.svelte'; import { onDestroy, onMount } from 'svelte'; import { debounce } from '$lib/helpers/debounce'; - import { tableColumnSuggestions } from '../../store'; + import { sdk } from '$lib/stores/sdk'; + import { page } from '$app/state'; + import { tableColumnSuggestions } from './store'; + import type { Models } from '@appwrite.io/console'; const { - customColumns = [] + onColumnsFinalized = undefined }: { - customColumns?: Column[]; + onColumnsFinalized?: (columns: Exclude[]) => Promise; } = $props(); /** @@ -161,23 +164,6 @@ const debouncedRecalc = debounce(recalcAll, 50); - onMount(() => { - if (spreadsheetContainer) { - resizeObserver = new ResizeObserver(debouncedRecalc); - resizeObserver.observe(spreadsheetContainer); - } - - requestAnimationFrame(recalcAll); - }); - - onDestroy(() => { - resizeObserver?.disconnect(); - hScroller?.removeEventListener('scroll', debouncedRecalc); - }); - - const getCustomColumns = (): Column[] => - customColumns.map((col: Column) => ({ ...col, width: 180, hide: false, ...baseColProps })); - const getRowColumns = (): Column[] => [ { id: '$id', @@ -187,7 +173,6 @@ icon: IconFingerPrint, ...baseColProps }, - ...getCustomColumns(), { id: '$createdAt', title: '$createdAt', @@ -209,7 +194,7 @@ title: '', type: 'string', icon: IconPlus, - width: customColumns.length ? 555 : 832, + width: 832, ...baseColProps }, { id: 'empty', title: '', type: 'string', ...baseColProps } @@ -217,6 +202,49 @@ const spreadsheetColumns = getRowColumns(); const emptyCells = $derived(($isSmallViewport ? 14 : 17) + (!$expandTabs ? 2 : 0)); + + onMount(async () => { + if (spreadsheetContainer) { + resizeObserver = new ResizeObserver(debouncedRecalc); + resizeObserver.observe(spreadsheetContainer); + } + + requestAnimationFrame(recalcAll); + await suggestColumns(); + }); + + async function suggestColumns() { + console.log('tableColumnSuggestions', $tableColumnSuggestions.enabled); + + if (!$tableColumnSuggestions.enabled) return; + + $tableColumnSuggestions.thinking = true; + + try { + const suggestedColumns = await sdk + .forConsoleIn(page.params.repository) + .console.suggestColumns({ + databaseId: page.params.database, + tableId: page.params.table, + context: $tableColumnSuggestions.context + }); + + console.log(JSON.stringify(suggestedColumns, null, 2)); + + await onColumnsFinalized?.(suggestedColumns.columns); + } catch (e) { + // `null` means + // we couldn't make columns! + await onColumnsFinalized?.(null); + } finally { + $tableColumnSuggestions.thinking = false; + } + } + + onDestroy(() => { + resizeObserver?.disconnect(); + hScroller?.removeEventListener('scroll', debouncedRecalc); + }); @@ -338,28 +366,6 @@ } &.thinking { - &::before { - content: ''; - position: absolute; - top: -2px; - left: -2px; - right: -2px; - bottom: -2px; - border-radius: inherit; - padding: 2px; - background: linear-gradient( - 90deg, - rgba(253, 54, 110, 0.02), - rgba(254, 149, 103, 0.05), - rgba(253, 54, 110, 0.02), - rgba(254, 149, 103, 0.05), - rgba(253, 54, 110, 0.02) - ); - background-size: 400% 100%; - animation: border-shimmer 4s ease-in-out infinite; - z-index: -1; - } - &::after { content: ''; position: absolute; @@ -373,15 +379,16 @@ rgba(255, 255, 255, 0.08), transparent ); - animation: inner-shimmer 4s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite; - z-index: 1; + animation: inner-shimmer 2s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite; } } } & .floating-action-wrapper :global(:first-child) { - left: 45%; /* change this value if the firstColumn is changed for overlay logic.*/ z-index: 21; + left: calc( + 50% - 40px + ); /* change this value if the firstColumn is changed for overlay logic.*/ } & :global(.spreadsheet-container) { @@ -421,18 +428,6 @@ ); } - @keyframes border-shimmer { - 0% { - background-position: 400% 0; - } - 50% { - background-position: -50% 0; - } - 100% { - background-position: -400% 0; - } - } - @keyframes inner-shimmer { 0% { left: -100%; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/ai.svelte similarity index 100% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/ai.svelte diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/index.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/index.ts new file mode 100644 index 0000000000..6a0f6f7d80 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/index.ts @@ -0,0 +1,3 @@ +export * from './store'; +export { default as Empty } from './empty.svelte'; +export { default as Input } from './input.svelte'; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte similarity index 92% rename from src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte rename to src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte index 0baab18915..83acf03771 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte @@ -4,11 +4,6 @@ import { tableColumnSuggestions } from './store'; import { InputTextarea } from '$lib/elements/forms'; import { Card, Layout, Selector, Typography } from '@appwrite.io/pink-svelte'; - - $effect(() => { - // TODO: update later, this is for mock only atm! - $tableColumnSuggestions.thinking = $tableColumnSuggestions.enabled; - }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts new file mode 100644 index 0000000000..cb042cf76d --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts @@ -0,0 +1,13 @@ +import { writable } from 'svelte/store'; + +export type TableColumnSuggestions = { + enabled: boolean; + thinking: boolean; + context?: string | null; +}; + +export const tableColumnSuggestions = writable({ + enabled: false, + context: null, + thinking: false +}); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte index e6d7dd08eb..f354cbcce3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte @@ -9,7 +9,7 @@ import { ID } from '@appwrite.io/console'; import { createEventDispatcher } from 'svelte'; import { subNavigation } from '$lib/stores/database'; - import Suggestions from './suggestions.svelte'; + import { Input as SuggestionsInput } from './(suggestions)/index'; let { showCreate = $bindable(false) @@ -105,7 +105,7 @@ } }} /> - + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts index c06d8be468..36e755d77e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts @@ -4,21 +4,9 @@ import type { Models } from '@appwrite.io/console'; import { derived, writable } from 'svelte/store'; import { IconChartBar, IconCloudUpload, IconCog } from '@appwrite.io/pink-icons-svelte'; -export type TableColumnSuggestions = { - enabled: boolean; - thinking: boolean; - context?: string | null; -}; - export const database = derived(page, ($page) => $page.data.database as Models.Database); export const showCreateTable = writable(false); -export const tableColumnSuggestions = writable({ - enabled: false, - context: null, - thinking: false -}); - export const tableViewColumns = writable([ { id: '$id', title: 'Table ID', type: 'string', width: 200 }, { id: 'name', title: 'Name', type: 'string', width: { min: 120 } }, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte index f52a0eb0a3..e62eda0d54 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte @@ -29,10 +29,9 @@ import { IconChevronDown, IconChevronUp, IconPlus } from '@appwrite.io/pink-icons-svelte'; import type { Models } from '@appwrite.io/console'; import EmptySheet from './layout/emptySheet.svelte'; - import SuggestionsEmptySheet from './layout/suggestionsEmptySheet.svelte'; import CreateRow from './rows/create.svelte'; import { onDestroy } from 'svelte'; - import { tableColumnSuggestions } from '../store'; + import { Empty as SuggestionsEmptySheet, tableColumnSuggestions } from '../(suggestions)'; export let data: PageData; @@ -218,7 +217,7 @@ }} /> {/if} {:else if $tableColumnSuggestions.thinking} - + {:else} Date: Mon, 22 Sep 2025 10:44:45 +0530 Subject: [PATCH 6/9] lint. --- .../databases/database-[database]/(suggestions)/empty.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte index 49b3b6291a..cf28ca0eac 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte @@ -24,7 +24,9 @@ const { onColumnsFinalized = undefined }: { - onColumnsFinalized?: (columns: Exclude[]) => Promise; + onColumnsFinalized?: ( + columns: Exclude[] + ) => Promise; } = $props(); /** From 60d993a532c0db9297f89cfe10811ade3cf4bc7e Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 22 Sep 2025 18:32:59 +0530 Subject: [PATCH 7/9] add: column suggestions. --- src/lib/actions/analytics.ts | 3 + src/lib/stores/sdk.ts | 3 +- .../(suggestions)/empty.svelte | 56 +-- .../(suggestions)/index.ts | 1 + .../(suggestions)/review.svelte | 348 ++++++++++++++++++ .../(suggestions)/store.ts | 56 ++- .../database-[database]/+layout.svelte | 18 +- .../database-[database]/createTable.svelte | 58 +-- .../table-[table]/+layout.svelte | 2 + .../table-[table]/+page.svelte | 22 +- .../table-[table]/columns/+page.svelte | 1 - .../table-[table]/createColumn.svelte | 3 +- 12 files changed, 510 insertions(+), 61 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/review.svelte diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 1f47229654..b511e59c8d 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -274,9 +274,12 @@ export enum Submit { DatabaseDelete = 'submit_database_delete', DatabaseUpdateName = 'submit_database_update_name', DatabaseImportCsv = 'submit_database_import_csv', + ColumnCreate = 'submit_column_create', ColumnUpdate = 'submit_column_update', ColumnDelete = 'submit_column_delete', + ColumnSuggestions = 'submit_column_suggestions', + RowCreate = 'submit_row_create', RowDelete = 'submit_row_delete', RowUpdate = 'submit_row_update', diff --git a/src/lib/stores/sdk.ts b/src/lib/stores/sdk.ts index 20ec5b83cc..3ea68fa620 100644 --- a/src/lib/stores/sdk.ts +++ b/src/lib/stores/sdk.ts @@ -126,7 +126,8 @@ const sdkForProject = { proxy: new Proxy(clientProject), migrations: new Migrations(clientProject), sites: new Sites(clientProject), - tablesDB: new TablesDB(clientProject) + tablesDB: new TablesDB(clientProject), + console: new Console(clientProject) // for suggestions API }; export const realtime = { diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte index cf28ca0eac..e25d3105b6 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte @@ -12,21 +12,25 @@ import { isSmallViewport } from '$lib/stores/viewport'; import { SortButton } from '$lib/components'; import type { Column } from '$lib/helpers/types'; - import { type Columns, expandTabs } from '../table-[table]/store'; + import { expandTabs } from '../table-[table]/store'; import SpreadsheetContainer from '../table-[table]/layout/spreadsheet.svelte'; import { onDestroy, onMount } from 'svelte'; import { debounce } from '$lib/helpers/debounce'; import { sdk } from '$lib/stores/sdk'; import { page } from '$app/state'; - import { tableColumnSuggestions } from './store'; - import type { Models } from '@appwrite.io/console'; + import { + type ColumnInput, + mapSuggestedColumns, + type SuggestedColumnSchema, + tableColumnSuggestions + } from './store'; + import { addNotification } from '$lib/stores/notifications'; + import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; const { onColumnsFinalized = undefined }: { - onColumnsFinalized?: ( - columns: Exclude[] - ) => Promise; + onColumnsFinalized?: (columns: SuggestedColumnSchema[]) => void | Promise; } = $props(); /** @@ -216,29 +220,37 @@ }); async function suggestColumns() { - console.log('tableColumnSuggestions', $tableColumnSuggestions.enabled); - - if (!$tableColumnSuggestions.enabled) return; - $tableColumnSuggestions.thinking = true; try { - const suggestedColumns = await sdk - .forConsoleIn(page.params.repository) + const suggestedColumns = (await sdk + .forProject(page.params.region, page.params.project) .console.suggestColumns({ databaseId: page.params.database, tableId: page.params.table, - context: $tableColumnSuggestions.context - }); - - console.log(JSON.stringify(suggestedColumns, null, 2)); - - await onColumnsFinalized?.(suggestedColumns.columns); - } catch (e) { - // `null` means - // we couldn't make columns! - await onColumnsFinalized?.(null); + context: $tableColumnSuggestions.context ?? undefined + })) as unknown as { + total: number; + columns: ColumnInput[]; + }; + + trackEvent(Submit.ColumnSuggestions, { + total: suggestedColumns.total, + tableName: $tableColumnSuggestions.table?.name ?? undefined + }); + + await onColumnsFinalized(mapSuggestedColumns(suggestedColumns.columns)); + } catch (error) { + addNotification({ + type: 'error', + message: error.message + }); + + trackError(error, Submit.ColumnSuggestions); } finally { + $tableColumnSuggestions.table = null; + $tableColumnSuggestions.context = null; + $tableColumnSuggestions.enabled = false; $tableColumnSuggestions.thinking = false; } } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/index.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/index.ts index 6a0f6f7d80..80654e5221 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/index.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/index.ts @@ -1,3 +1,4 @@ export * from './store'; export { default as Empty } from './empty.svelte'; export { default as Input } from './input.svelte'; +export { default as Review } from './review.svelte'; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/review.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/review.svelte new file mode 100644 index 0000000000..e95462724b --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/review.svelte @@ -0,0 +1,348 @@ + + + + + Review and edit suggested columns before applying + + + + {#each draft as column, index (index)} + + + + { + draft[index] = normalizeColumnType(event.detail, draft[index]); + draft = [...draft]; + }} /> + + + + + {#if index !== draft.length - 1} + + {/if} + {/each} + + + + + + + + + + + + + + + + { + resetDraft(); + show = false; + columns = null; + }}> + Are you sure you want to dismiss these columns suggested by AI? This action is irreversible. + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts index cb042cf76d..67bd268ba3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts @@ -3,11 +3,63 @@ import { writable } from 'svelte/store'; export type TableColumnSuggestions = { enabled: boolean; thinking: boolean; - context?: string | null; + context?: string | undefined; + + /* for safety when in tables page */ + table: { + id: string; + name: string; + }; +}; + +export type SuggestedColumnSchema = { + key: string; + type: string; + required: boolean; + default?: string | number | boolean | number[] | number[][] | number[][][] | null; + size?: number; + min?: number; + max?: number; + format?: string | null; }; export const tableColumnSuggestions = writable({ enabled: false, context: null, - thinking: false + thinking: false, + table: null }); + +export type ColumnInput = { + name: string; + type: string; + required?: boolean; + default?: string | number | boolean | number[] | number[][] | number[][][] | null; + size?: number; + min?: number; + max?: number; + format?: string; + formatOptions?: { + min?: number; + max?: number; + }; +}; + +export function mapSuggestedColumns(columns: T[]): SuggestedColumnSchema[] { + return columns.map((col) => ({ + key: col.name, + type: col.type, + required: col.required ?? false, + default: col.default ?? null, + size: col.type === 'string' ? (col.size ?? undefined) : undefined, + min: + col.type === 'integer' || col.type === 'double' + ? (col.min ?? col.formatOptions?.min ?? undefined) + : undefined, + max: + col.type === 'integer' || col.type === 'double' + ? (col.max ?? col.formatOptions?.max ?? undefined) + : undefined, + format: col.format ?? null + })); +} 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 c4b66e957f..dc39232b0d 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 @@ -10,7 +10,6 @@ } from '$lib/commandCenter'; import { tablesSearcher } from '$lib/commandCenter/searchers'; import { Dependencies } from '$lib/constants'; - import type { Models } from '@appwrite.io/console'; import CreateTable from './createTable.svelte'; import { showCreateTable } from './store'; import { TablesPanel } from '$lib/commandCenter/panels'; @@ -24,14 +23,6 @@ const project = page.params.project; const databaseId = page.params.database; - async function handleCreate(event: CustomEvent) { - $showCreateTable = false; - await invalidate(Dependencies.DATABASE); - await goto( - `${base}/project-${page.params.region}-${project}/databases/database-${databaseId}/table-${event.detail.$id}` - ); - } - $: $registerCommands([ { label: 'Create table', @@ -152,4 +143,11 @@ - + { + await invalidate(Dependencies.DATABASE); + await goto( + `${base}/project-${page.params.region}-${project}/databases/database-${databaseId}/table-${table.$id}` + ); + }} /> diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte index f354cbcce3..0f545f8909 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte @@ -6,26 +6,28 @@ import { Button, InputText } from '$lib/elements/forms'; import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; - import { ID } from '@appwrite.io/console'; - import { createEventDispatcher } from 'svelte'; + import { ID, type Models } from '@appwrite.io/console'; import { subNavigation } from '$lib/stores/database'; - import { Input as SuggestionsInput } from './(suggestions)/index'; + import { Input as SuggestionsInput, tableColumnSuggestions } from './(suggestions)/index'; let { - showCreate = $bindable(false) + showCreate = $bindable(false), + onTableCreated }: { showCreate: boolean; + onTableCreated: (table: Models.Table) => void | Promise; } = $props(); const databaseId = page.params.database; - const dispatch = createEventDispatcher(); let name = $state(''); let id: string = $state(null); let touchedId = $state(false); let error: string = $state(null); - const create = async () => { + let creatingTable = $state(false); + + async function createTable() { error = null; try { const table = await sdk @@ -36,23 +38,35 @@ name }); + $tableColumnSuggestions.table = { + id: table.$id, + name: table.name + }; + + updateAndCleanup(); + + await onTableCreated(table); + showCreate = false; - subNavigation.update(); - - dispatch('created', table); - addNotification({ - type: 'success', - message: `${name} has been created` - }); - name = id = null; - trackEvent(Submit.TableCreate, { - customId: !!id - }); + creatingTable = false; } catch (e) { error = e.message; trackError(e, Submit.TableCreate); } - }; + } + + function updateAndCleanup() { + subNavigation.update(); + + addNotification({ + type: 'success', + message: `${name} has been created` + }); + + trackEvent(Submit.TableCreate, { customId: !!id }); + + name = id = null; + } function toIdFormat(str: string): string { return str @@ -79,7 +93,7 @@ }); - + - - + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte index cc755911b2..fcc995b71b 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte @@ -62,6 +62,7 @@ import { preferences } from '$lib/stores/preferences'; import { buildRowUrl, isRelationship } from './rows/store'; import { chunks } from '$lib/helpers/array'; + import { Submit, trackEvent } from '$lib/actions/analytics'; let editRow: EditRow; let editRelatedRow: EditRelatedRow; @@ -256,6 +257,7 @@ columns = await generateColumns($project, page.params.database, page.params.table); await invalidate(Dependencies.TABLE); + trackEvent(Submit.ColumnCreate, { type: 'faker' }); } catch (e) { addNotification({ type: 'error', diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte index e62eda0d54..f625e72a7b 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte @@ -31,12 +31,20 @@ import EmptySheet from './layout/emptySheet.svelte'; import CreateRow from './rows/create.svelte'; import { onDestroy } from 'svelte'; - import { Empty as SuggestionsEmptySheet, tableColumnSuggestions } from '../(suggestions)'; + import { + Review as ReviewColumns, + Empty as SuggestionsEmptySheet, + type SuggestedColumnSchema, + tableColumnSuggestions + } from '../(suggestions)'; export let data: PageData; let showImportCSV = false; + let showSuggestionsModal = false; + let columnSuggestionsSchema: SuggestedColumnSchema[] = []; + // todo: might need a type fix here. const filterColumns = writable([]); @@ -216,8 +224,12 @@ } }} /> {/if} - {:else if $tableColumnSuggestions.thinking} - + {:else if $tableColumnSuggestions.enabled && $tableColumnSuggestions.table && $tableColumnSuggestions.table.id === page.params.table} + { + showSuggestionsModal = true; + columnSuggestionsSchema = columns; + }} /> {:else} +{#if showSuggestionsModal && columnSuggestionsSchema} + +{/if} +