Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions src/lib/commandCenter/panels/ai.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
<script lang="ts">
import Template from './template.svelte';

import { AvatarInitials, Code, LoadingDots, SvgIcon } from '$lib/components';
import { Alert, AvatarInitials, Code, LoadingDots, SvgIcon } from '$lib/components';
import { user } from '$lib/stores/user';
import { useCompletion } from 'ai/svelte';
import { subPanels } from '../subPanels';

import { isLanguage, type Language } from '$lib/components/code.svelte';
import { preferences } from '$lib/stores/preferences';
import { VARS } from '$lib/system';

const endpoint = VARS.APPWRITE_ENDPOINT ?? `${globalThis?.location?.origin}/v1`;

const { input, handleSubmit, completion, isLoading, complete } = useCompletion({
const { input, handleSubmit, completion, isLoading, complete, error } = useCompletion({
api: endpoint + '/console/assistant',
headers: {
'content-type': 'application/json'
Expand Down Expand Up @@ -105,7 +106,9 @@
})}
clearOnCallback={false}
on:keydown={(e) => {
e.detail.cancel();
if (e.detail.key !== 'Escape') {
e.detail.cancel();
}
}}
--min-height="40rem"
--max-height="52.5rem">
Expand All @@ -118,6 +121,21 @@
<span>{option.label}</span>
</div>

{#if !$preferences.hideAiDisclaimer}
<div style="padding: 1rem; padding-block-end: 0;">
<Alert
type="default"
dismissible
on:dismiss={() => {
$preferences.hideAiDisclaimer = true;
}}>
<span slot="title">
We collect user responses to refine our experimental AI feature.
</span>
</Alert>
</div>
{/if}

{#if $isLoading || answer}
<div class="content">
<div class="u-flex u-gap-8 u-cross-center">
Expand All @@ -140,7 +158,13 @@
<div
class="u-margin-block-start-8"
style="margin-block-end: 1rem;">
<Code language={part.language} code={part.value} noMargin />
<Code
label={part.language}
language={part.language}
code={part.value}
noMargin
noBoxPadding
withCopy />
</div>
{/key}
{/if}
Expand All @@ -151,6 +175,18 @@
</div>
{/if}

{#if $error}
<div style="padding: 1rem; padding-block-end: 0;">
<Alert type="error">
<span slot="title">Something went wrong</span>
<p>
An unexpected error occurred while handling your request. Please try again
later.
</p>
</Alert>
</div>
{/if}

<div class="footer" slot="footer">
<div class="u-flex u-cross-center u-gap-4">
<AvatarInitials size={32} name={$user.name} />
Expand Down Expand Up @@ -201,6 +237,14 @@
--logo-bg: #f2f2f8;
}

:global(.theme-dark) .footer {
--sep-clr: hsl(var(--color-neutral-150));
}

:global(.theme-light) .footer {
--sep-clr: hsl(var(--color-neutral-30));
}

.content {
overflow: auto;
padding: 1rem;
Expand Down Expand Up @@ -230,7 +274,7 @@
.sep {
width: 1px;
height: 1.5rem;
background-color: hsl(var(--color-neutral-150));
background-color: var(--sep-clr);
}
}

Expand Down
21 changes: 5 additions & 16 deletions src/lib/commandCenter/panels/template.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import { commandGroupRanks, type CommandGroup, type Command } from '../commands';

import { commandGroupRanks, type Command, type CommandGroup } from '../commands';
// This is the template for all panels used in the command center.
// Use this component when you want to create a new panel.

Expand Down Expand Up @@ -82,19 +81,9 @@
tick().then(() => {
if (!cardEl) return;

const resultEls = Array.from(contentEl.querySelectorAll('.result'));
const selectedEl = contentEl.querySelector('[data-selected]');
const selectedIdx = resultEls.indexOf(selectedEl);

if (selectedIdx === 0) {
contentEl.scrollTo({
top: 0
});
} else if (selectedIdx === resultEls.length - 1) {
contentEl.scrollTo({
top: contentEl.scrollHeight
});
} else if (selectedEl) {
if (selectedEl) {
selectedEl.scrollIntoView({
block: 'nearest'
});
Expand Down Expand Up @@ -276,6 +265,7 @@
</div>

<div class="content" bind:this={contentEl}>
<slot />
{#if groupsAndOptions}
<ul class="options">
{#each groupsAndOptions as item, i}
Expand Down Expand Up @@ -320,8 +310,6 @@
</li>
{/each}
</ul>
{:else}
<slot />
{/if}
</div>

Expand Down Expand Up @@ -386,7 +374,7 @@
--crumb-color: hsl(var(--color-neutral-100));

--result-bg: hsl(var(--color-neutral-10));
--footer-bg: linear-gradient(180deg, #fff 0%, #e8e9f0 100%);
--footer-bg: linear-gradient(180deg, #fff 49.38%, #e8e9f0 100%);

--icon-color: hsl(var(--color-neutral-50));
--label-color: hsl(var(--color-neutral-100));
Expand Down Expand Up @@ -512,6 +500,7 @@

.result {
position: relative;
scroll-margin-block: 0.5rem;

.bg {
position: absolute;
Expand Down
8 changes: 5 additions & 3 deletions src/lib/components/alert.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { Buttons } from '../stores/notifications';

export let dismissible = false;
export let type: 'info' | 'success' | 'warning' | 'error' = 'info';
export let type: 'info' | 'success' | 'warning' | 'error' | 'default' = 'info';
export let buttons: Buttons[] = [];
export let isAction = false;
export let isStandalone = false;
Expand Down Expand Up @@ -31,7 +31,7 @@
</button>
{/if}
<span
class:icon-info={type === 'info'}
class:icon-info={type === 'info' || type === 'default'}
class:icon-check-circle={type === 'success'}
class:icon-exclamation={type === 'warning'}
class:icon-exclamation-circle={type === 'error'}
Expand All @@ -42,7 +42,9 @@
<slot name="title" />
</h6>
{/if}
<p class="alert-message"><slot /></p>
{#if $$slots.default}
<p class="alert-message"><slot /></p>
{/if}
{#if ($$slots.buttons || buttons?.length) && !isAction}
<div class="alert-buttons u-flex">
<slot name="buttons">
Expand Down
13 changes: 11 additions & 2 deletions src/lib/components/code.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
export let withLineNumbers = false;
export let withCopy = false;
export let noMargin = false;
export let noBoxPadding = false;
export let allowScroll = false;

Prism.plugins.customClass.prefix('prism-');
Expand All @@ -38,7 +39,7 @@
});
</script>

<section class="box u-overflow-hidden" class:common-section={!noMargin}>
<section class="box u-overflow-hidden" class:common-section={!noMargin} class:noBoxPadding>
<div
class="controls u-position-absolute u-inset-inline-end-8 u-inset-block-start-8 u-flex u-gap-8">
{#if label}
Expand Down Expand Up @@ -79,11 +80,19 @@
}
}

.noBoxPadding {
padding: 0 !important;
}

.with-scroll {
height: 100%;
overflow: auto;
}

pre {
padding-inline-end: 7rem !important; // Add space for label and copy btn
}

code,
pre {
&[class*='language-'] {
Expand Down Expand Up @@ -116,7 +125,7 @@
:not(pre) > code[class*='language-'],
pre[class*='language-'] {
background: hsl(var(--p-box-background-color));
padding-block-start: 4%;

margin: 0;
}
.prism-token {
Expand Down
33 changes: 18 additions & 15 deletions src/lib/stores/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { get, writable } from 'svelte/store';
import { sdk } from './sdk';
import type { Models } from '@appwrite.io/console';
import { organization } from './organization';
import { onDestroy } from 'svelte';

type Preferences = {
limit?: number;
Expand All @@ -27,31 +28,39 @@ type PreferencesStore = {
[key: string]: TeamPreferences['names'];
};
};
};
} & { hideAiDisclaimer?: boolean };

function createPreferences() {
const { subscribe, set, update } = writable<PreferencesStore>({});
let preferences: PreferencesStore = {};

if (browser) {
set(JSON.parse(globalThis.localStorage.getItem('preferences') ?? '{}'));
}

subscribe((v) => {
preferences = v;
if (browser) {
globalThis.localStorage.setItem('preferences', JSON.stringify(v));
}
});

return {
subscribe,
get: (route: Page['route']): Preferences => {
let preferences: PreferencesStore;
subscribe((n) => (preferences = n))();

set,
update,
get: (route?: Page['route']): Preferences => {
const parsedRoute = route ?? get(page).route;
return (
preferences[sdk.forProject.client.config.project]?.[route.id] ?? {
preferences[sdk.forProject.client.config.project]?.[parsedRoute.id] ?? {
limit: null,
view: null,
columns: null
}
);
},
getCustomCollectionColumns: (collectionId: string): Preferences['columns'] => {
let preferences: PreferencesStore;
subscribe((n) => (preferences = n))();

getCustomCollectionColumns: (collectionId: string): Preferences['columns'] => {
return (
preferences[sdk.forProject.client.config.project]?.collections?.[collectionId] ??
null
Expand Down Expand Up @@ -122,9 +131,7 @@ function createPreferences() {
getDisplayNames: () => {
const id = get(organization)?.$id;
if (!id) return {};
let preferences: PreferencesStore;

subscribe((n) => (preferences = n))();
return preferences?.[id]?.displayNames ?? {};
},
setDisplayNames: async (collectionId: string, names: TeamPreferences['names']) => {
Expand All @@ -147,7 +154,3 @@ function createPreferences() {
}

export const preferences = createPreferences();

if (browser) {
preferences.subscribe((n) => globalThis.localStorage.setItem('preferences', JSON.stringify(n)));
}
21 changes: 21 additions & 0 deletions src/routes/auth/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
<script>
import { addSubPanel, registerCommands } from '$lib/commandCenter';
import { TeamsPanel, UsersPanel } from '$lib/commandCenter/panels';
import { Container } from '$lib/layout';
import { loading } from '../store';

loading.set(false);

$registerCommands([
{
label: 'Find users',
callback: () => {
addSubPanel(UsersPanel);
},
group: 'users',
rank: -1
},
{
label: 'Find teams',
callback: () => {
addSubPanel(TeamsPanel);
},
group: 'teams',
rank: -1
}
]);
</script>

<Container>
Expand Down
19 changes: 18 additions & 1 deletion src/routes/console/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { goto } from '$app/navigation';

import { CommandCenter, registerCommands, registerSearchers } from '$lib/commandCenter';
import { AIPanel } from '$lib/commandCenter/panels';
import { AIPanel, OrganizationsPanel, ProjectsPanel } from '$lib/commandCenter/panels';
import { orgSearcher, projectsSearcher } from '$lib/commandCenter/searchers';
import { addSubPanel } from '$lib/commandCenter/subPanels';
import { addNotification } from '$lib/stores/notifications';
Expand Down Expand Up @@ -204,6 +204,23 @@
disabled: isOnSettingsLayout && $page.url.pathname.includes('smtp'),
group: isOnSettingsLayout ? 'navigation' : 'settings',
rank: -1
},
// Searcher panels
{
label: 'Find organizations',
callback: () => {
addSubPanel(OrganizationsPanel);
},
group: 'organizations',
rank: -1
},
{
label: 'Find projects',
callback: () => {
addSubPanel(ProjectsPanel);
},
group: 'projects',
rank: -1
}
]);
let isOpen = false;
Expand Down
Loading