feat: column suggestions modal.

This commit is contained in:
Darshan
2025-10-19 19:05:07 +05:30
parent f3457e6731
commit 63ac081467
10 changed files with 156 additions and 79 deletions
+2 -2
View File
@@ -24,9 +24,9 @@
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@754a996",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@6faeea0",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@754a996",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@6faeea0",
"@faker-js/faker": "^9.9.0",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
+10 -10
View File
@@ -18,14 +18,14 @@ importers:
specifier: 0.25.0
version: 0.25.0
'@appwrite.io/pink-icons-svelte':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@754a996
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@754a996(svelte@5.25.3)
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@6faeea0
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@6faeea0(svelte@5.25.3)
'@appwrite.io/pink-legacy':
specifier: ^1.0.3
version: 1.0.3
'@appwrite.io/pink-svelte':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@754a996
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@754a996(svelte@5.25.3)
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@6faeea0
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@6faeea0(svelte@5.25.3)
'@faker-js/faker':
specifier: ^9.9.0
version: 9.9.0
@@ -269,8 +269,8 @@ packages:
peerDependencies:
svelte: ^4.0.0
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@754a996':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@754a996}
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@6faeea0':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@6faeea0}
version: 2.0.0-RC.1
peerDependencies:
svelte: ^4.0.0
@@ -284,8 +284,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.3':
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@754a996':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@754a996}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@6faeea0':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@6faeea0}
version: 2.0.0-RC.2
peerDependencies:
svelte: ^4.0.0
@@ -3709,7 +3709,7 @@ snapshots:
dependencies:
svelte: 5.25.3
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@754a996(svelte@5.25.3)':
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@6faeea0(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
@@ -3722,7 +3722,7 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@754a996(svelte@5.25.3)':
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@6faeea0(svelte@5.25.3)':
dependencies:
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
'@floating-ui/dom': 1.6.13
@@ -0,0 +1,62 @@
<script lang="ts">
import { page } from '$app/state';
import { resolve } from '$app/paths';
import { goto } from '$app/navigation';
import Input from './input.svelte';
import { Modal } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { tableColumnSuggestions } from './store';
let {
show = $bindable(false)
}: {
show?: boolean;
} = $props();
const isOnRowsPage = $derived(page.route?.id?.endsWith('table-[table]'));
function resetSuggestionsStore() {
show = false;
$tableColumnSuggestions.table = null;
$tableColumnSuggestions.context = null;
$tableColumnSuggestions.enabled = false;
$tableColumnSuggestions.thinking = false;
$tableColumnSuggestions.force = undefined;
}
async function triggerColumnSuggestions() {
if (!isOnRowsPage) {
await goto(
resolve(
'/(console)/project-[region]-[project]/databases/database-[database]/table-[table]',
{
region: page.params.region,
project: page.params.project,
database: page.params.database,
table: page.params.table
}
)
);
}
show = false;
$tableColumnSuggestions.enabled = true;
$tableColumnSuggestions.table = {
id: page.params.table,
name: page.data.table?.name ?? 'Table'
};
$tableColumnSuggestions.force = true;
}
</script>
<Modal bind:show title="Suggest columns" onSubmit={triggerColumnSuggestions}>
<Input isModal />
<svelte:fragment slot="footer">
<Button text on:click={resetSuggestionsStore}>Cancel</Button>
<Button submit>Generate columns</Button>
</svelte:fragment>
</Modal>
@@ -43,6 +43,12 @@
import IconAINotification from './icon/aiNotification.svelte';
let {
userColumns = []
}: {
userColumns?: Column[];
} = $props();
let resizeObserver: ResizeObserver;
let spreadsheetContainer: HTMLElement;
@@ -394,6 +400,7 @@
...baseColProps
},
...finalCustomColumns,
/*...userColumns,*/
{
id: 'actions',
title: '',
@@ -409,6 +416,8 @@
const emptyCells = $derived(($isSmallViewport ? 14 : 17) + (!$expandTabs ? 2 : 0));
onMount(async () => {
userColumns; /* silences lint check, variable not read */
if (spreadsheetContainer) {
resizeObserver = new ResizeObserver(recalcAll);
resizeObserver.observe(spreadsheetContainer);
@@ -430,6 +439,7 @@
$tableColumnSuggestions.enabled = false;
}
$tableColumnSuggestions.force = undefined;
$tableColumnSuggestions.context = null;
$tableColumnSuggestions.thinking = false;
@@ -739,7 +749,7 @@
tick().then(() => {
selectedColumnId = columnData.key;
selectedColumnName = columnData.key;
})
});
});
}
@@ -888,6 +898,8 @@
timeout: NOTIFICATION_AND_MOCK_DELAY
});
resetSuggestionsStore(true);
// show index notification!
showIndexSuggestionsNotification();
@@ -7,6 +7,12 @@
import { Button, InputTextarea } from '$lib/elements/forms';
import { Card, Layout, Selector, Typography } from '@appwrite.io/pink-svelte';
const {
isModal = false
}: {
isModal?: boolean;
} = $props();
onMount(() => {
if (featureActive) {
$tableColumnSuggestions.enabled = true;
@@ -23,7 +29,9 @@
const subtitle = $derived.by(() => {
return featureActive
? 'Enable AI to suggest useful columns based on your table name'
? isModal
? 'Use AI to suggest useful columns'
: 'Enable AI to suggest useful columns based on your table name'
: 'Sign up for Cloud to generate columns based on your table name';
});
</script>
@@ -42,7 +50,7 @@
</Typography.Text>
</Layout.Stack>
{#if featureActive}
{#if featureActive && !isModal}
<div class="suggestions-switch">
<Selector.Switch
id="suggestions"
@@ -3,6 +3,7 @@ import { IndexType } from '@appwrite.io/console';
import { columnOptions } from '../table-[table]/columns/store';
export type TableColumnSuggestions = {
force?: boolean;
enabled: boolean;
thinking: boolean;
context?: string | undefined;
@@ -48,6 +49,8 @@ export const tableColumnSuggestions = writable<TableColumnSuggestions>({
export const showIndexesSuggestions = writable<boolean>(false);
export const showColumnsSuggestionsModal = writable<boolean>(false);
export const mockSuggestions: { total: number; columns: ColumnInput[] } = {
total: 7,
columns: [
@@ -65,7 +65,12 @@
import { Submit, trackEvent } from '$lib/actions/analytics';
import IndexesSuggestions from '../(suggestions)/indexes.svelte';
import { showIndexesSuggestions, tableColumnSuggestions } from '../(suggestions)';
import ColumnsSuggestions from '../(suggestions)/columns.svelte';
import {
showColumnsSuggestionsModal,
showIndexesSuggestions,
tableColumnSuggestions
} from '../(suggestions)';
let editRow: EditRow;
let editRelatedRow: EditRelatedRow;
@@ -482,4 +487,6 @@
</svelte:fragment>
</Dialog>
<ColumnsSuggestions bind:show={$showColumnsSuggestionsModal} />
<IndexesSuggestions />
@@ -38,7 +38,11 @@
import CreateRow from './rows/create.svelte';
import { onDestroy } from 'svelte';
import { isCloud } from '$lib/system';
import { Empty as SuggestionsEmptySheet, tableColumnSuggestions } from '../(suggestions)';
import {
Empty as SuggestionsEmptySheet,
tableColumnSuggestions,
showColumnsSuggestionsModal
} from '../(suggestions)';
import EmptySheetCards from './layout/emptySheetCards.svelte';
import IconAI from '../(suggestions)/icon/aiForButton.svelte';
@@ -201,7 +205,7 @@
</Container>
<div class="databases-spreadsheet">
{#if hasColumns && hasValidColumns}
{#if hasColumns && hasValidColumns && $tableColumnSuggestions.force === false}
{#if data.rows.total}
<Divider />
<SpreadSheet {data} bind:showRowCreateSheet={$showRowCreateSheet} />
@@ -250,7 +254,7 @@
</EmptySheet>
{/if}
{:else if isCloud && canShowSuggestionsSheet}
<SuggestionsEmptySheet />
<SuggestionsEmptySheet userColumns={$tableColumns} />
{:else}
<EmptySheet mode="rows" showActions={$canWriteTables} title="You have no columns yet">
{#snippet actions()}
@@ -267,7 +271,7 @@
title="Suggest columns"
subtitle="Use AI to generate columns"
onClick={() => {
// todo: add a modal and show input, no toggle.
$showColumnsSuggestionsModal = true;
}} />
<EmptySheetCards
@@ -1,9 +1,8 @@
<script lang="ts">
import { page } from '$app/state';
import { type Columns, type ColumnDirection } from './store';
import { goto, invalidate } from '$app/navigation';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { Layout } from '@appwrite.io/pink-svelte';
import { Alert, Layout, Link } from '@appwrite.io/pink-svelte';
import { InputSelect, InputText } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
@@ -11,8 +10,10 @@
import type { Column } from '$lib/helpers/types';
import { preferences } from '$lib/stores/preferences';
import { onMount } from 'svelte';
import { resolve } from '$app/paths';
import { Input as SuggestionsInput, tableColumnSuggestions } from '../(suggestions)/index';
import { showColumnsSuggestionsModal } from '../(suggestions)/store';
import IconAINotification from '../(suggestions)/icon/aiNotification.svelte';
import { type Columns, type ColumnDirection, showCreateColumnSheet } from './store';
let {
direction = null,
@@ -52,16 +53,6 @@
columnOptions.find((option) => option.name === selectedOption).component
);
function initSuggestionsStore() {
if (!isSuggestionsFeatureEnabled) return;
$tableColumnSuggestions.table = null;
$tableColumnSuggestions.enabled = false;
$tableColumnSuggestions.context = null;
$tableColumnSuggestions.thinking = false;
}
function init() {
key = null;
$option = null;
@@ -74,9 +65,6 @@
/* default to string */
selectedOption = 'String';
$option = columnOptions[0];
/* init suggestions */
initSuggestionsStore();
}
function insertColumnInOrder() {
@@ -155,33 +143,6 @@
}
export async function submit() {
if (isSuggestionsEnabled) {
const tableInUse = page.data.table;
// TODO: confirm what flow to use!
tableColumnSuggestions.update((store) => ({
...store,
thinking: true,
table: {
id: tableInUse.$id,
name: tableInUse.name
}
}));
const { region, project, database, table } = page.params;
await goto(
resolve(
'/(console)/project-[region]-[project]/databases/database-[database]/table-[table]',
{
region,
project,
database,
table
}
)
);
return true;
}
try {
await $option.create(databaseId, tableId, key, data);
@@ -222,15 +183,23 @@
$option = columnOptions.find((option) => option.name === selectedOption);
}
});
const isSuggestionsEnabled = $derived(
isSuggestionsFeatureEnabled && $tableColumnSuggestions.enabled
);
</script>
<Layout.Stack gap="xl">
{#if isSuggestionsFeatureEnabled}
<SuggestionsInput />
<div class="custom-inline-alert">
<Alert.Inline>
<svelte:fragment slot="icon">
<IconAINotification />
</svelte:fragment>
Need help? Let AI <Link.Button
on:click={() => {
$showCreateColumnSheet.show = false;
$showColumnsSuggestionsModal = true;
}}>suggest columns</Link.Button> based on your data
</Alert.Inline>
</div>
{/if}
<Layout.Stack direction="row">
@@ -240,7 +209,7 @@
placeholder="Enter key"
bind:value={key}
autofocus
disabled={selectedOption === 'Relationship' || isSuggestionsEnabled}
disabled={selectedOption === 'Relationship'}
required
pattern="^[A-Za-z0-9][A-Za-z0-9._\-]*$" />
@@ -248,7 +217,6 @@
id="type"
label="Type"
bind:value={selectedOption}
disabled={isSuggestionsEnabled}
options={columnOptions.map((attr) => {
return {
label: attr.name,
@@ -260,9 +228,22 @@
</Layout.Stack>
{#if selectedOption}
<ColumnComponent
disabled={isSuggestionsEnabled}
bind:data
onclose={() => ($option = null)} />
<ColumnComponent bind:data onclose={() => ($option = null)} />
{/if}
</Layout.Stack>
<style lang="scss">
.custom-inline-alert {
& :global(article) {
padding: var(--space-4, 8px);
}
& :global(div:first-child > :nth-child(2)) {
align-self: center;
}
& :global(.ai-icon-holder.notification) {
height: 36px !important;
}
}
</style>
@@ -37,9 +37,9 @@
import { showCreateColumnSheet } from '../store';
import { isSmallViewport } from '$lib/stores/viewport';
import { page } from '$app/state';
import { showIndexesSuggestions } from '../../(suggestions)';
import { showIndexesSuggestions, showColumnsSuggestionsModal } from '../../(suggestions)';
import IconAI from '../../(suggestions)/icon/aiForButton.svelte';
import EmptySheetCards from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheetCards.svelte';
import EmptySheetCards from '../layout/emptySheetCards.svelte';
let {
data
@@ -339,7 +339,7 @@
title="Suggest columns"
subtitle="Use AI to generate columns"
onClick={() => {
// TODO: add a modal and show input, no toggle.
$showColumnsSuggestionsModal = true;
}} />
{/snippet}
</EmptySheet>