Skip to content

Commit

Permalink
feat: Add ability edit delete assistants within the UI (#510)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewrisse authored May 20, 2024
1 parent 7ba0e53 commit e8408b7
Show file tree
Hide file tree
Showing 38 changed files with 1,722 additions and 556 deletions.
483 changes: 415 additions & 68 deletions src/leapfrogai_ui/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/leapfrogai_ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"supabase:start": "supabase start && docker exec -u 0 supabase_auth_supabase /bin/sh -c \"echo '100.115.154.78 keycloak.admin.uds.dev' >> /etc/hosts\"",
"supabase:stop": "supabase stop",
"supabase:reset": "supabase db reset",
"supbase:migrate": "supbase migration up"
"supbase:migrate": "supabase migration up"
},
"devDependencies": {
"@commitlint/cli": "^19.1.0",
Expand Down Expand Up @@ -78,6 +78,7 @@
"openai": "^4.41.1",
"playwright": "^1.42.1",
"svelte-forms-lib": "^2.0.1",
"sveltekit-superforms": "^2.13.1",
"uuid": "^9.0.1",
"yup": "^1.4.0"
}
Expand Down
1 change: 1 addition & 0 deletions src/leapfrogai_ui/src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ declare global {
// profile?: any;
conversations?: Conversation[];
assistants?: Assistant[];
assistant?: Assistant;
}
// interface PageState {}
// interface Platform {}
Expand Down
11 changes: 8 additions & 3 deletions src/leapfrogai_ui/src/lib/components/AssistantAvatar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
import { AVATAR_FILE_SIZE_ERROR_TEXT, MAX_AVATAR_SIZE, NO_FILE_ERROR_TEXT } from '$lib/constants';
export let files: File[];
export let avatarUrl = '';
export let selectedPictogramName: string;
let tempAvatarUrl = avatarUrl;
let tempFiles: File[] = [];
let tempPictogram = '';
let tempPictogram = selectedPictogramName || 'default';
let modalOpen = false;
let selectedRadioButton: 'upload' | 'pictogram' = 'pictogram';
let shouldValidate = false;
Expand All @@ -24,10 +26,11 @@
$: fileNotUploaded = !tempFiles[0]; // if on upload tab, you must upload a file to enable save
$: fileTooBig = tempFiles[0]?.size > MAX_AVATAR_SIZE;
$: hideUploader = tempFiles.length > 0;
$: hideUploader = tempAvatarUrl ? true : tempFiles.length > 0;
const handleRemove = () => {
tempFiles = [];
tempAvatarUrl = '';
tempPictogram = 'default';
shouldValidate = false;
};
Expand Down Expand Up @@ -67,13 +70,15 @@
// pictogram tab
selectedPictogramName = tempPictogram;
files = []; // remove saved avatar
tempFiles = [];
modalOpen = false;
shouldValidate = false;
}
};
$: tempImagePreviewUrl = tempFiles?.length > 0 ? URL.createObjectURL(tempFiles[0]) : '';
$: savedImagePreviewUrl = files?.length > 0 ? URL.createObjectURL(files[0]) : '';
$: savedImagePreviewUrl =
files?.length > 0 ? URL.createObjectURL(files[0]) : tempAvatarUrl ? tempAvatarUrl : '';
</script>

<div class="container">
Expand Down
235 changes: 235 additions & 0 deletions src/leapfrogai_ui/src/lib/components/AssistantForm.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
<script lang="ts">
import {
ASSISTANTS_DESCRIPTION_MAX_LENGTH,
ASSISTANTS_INSTRUCTIONS_MAX_LENGTH,
ASSISTANTS_NAME_MAX_LENGTH
} from '$lib/constants';
import { superForm } from 'sveltekit-superforms';
import { Add } from 'carbon-icons-svelte';
import { page } from '$app/stores';
import { beforeNavigate, goto } from '$app/navigation';
import { Button, Modal, Slider, TextArea, TextInput } from 'carbon-components-svelte';
import AssistantAvatar from '$components/AssistantAvatar.svelte';
import { yup } from 'sveltekit-superforms/adapters';
import { toastStore } from '$stores';
import InputTooltip from '$components/InputTooltip.svelte';
import { env } from '$env/dynamic/public';
import { editAssistantInputSchema, supabaseAssistantInputSchema } from '$lib/schemas/assistants';
import type { NavigationTarget } from '@sveltejs/kit';
export let data;
let isEditMode = $page.url.pathname.includes('edit');
let bypassCancelWarning = false;
const { form, errors, enhance, submitting, isTainted } = superForm(data.form, {
validators: yup(isEditMode ? editAssistantInputSchema : supabaseAssistantInputSchema),
onResult({ result }) {
if (result.type === 'redirect') {
toastStore.addToast({
kind: 'success',
title: `Assistant ${isEditMode ? 'Updated' : 'Created'}.`,
subtitle: ''
});
bypassCancelWarning = true;
goto(result.location);
} else if (result.type === 'failure') {
// 400 errors will show errors for the respective fields, do not show toast
if (result.status !== 400) {
toastStore.addToast({
kind: 'error',
title: `Error ${isEditMode ? 'Editing' : 'Creating'} Assistant`,
subtitle: result.data?.message || 'An unknown error occurred.'
});
}
} else if (result.type === 'error') {
toastStore.addToast({
kind: 'error',
title: `Error ${isEditMode ? 'Editing' : 'Creating'} Assistant`,
subtitle: result.error?.message || 'An unknown error occurred.'
});
}
}
});
let cancelModalOpen = false;
let files: File[] = [];
let selectedPictogramName = isEditMode ? data.assistant.metadata.pictogram : 'default';
let avatarPath = isEditMode ? data.assistant.metadata.avatar : '';
let navigateTo: NavigationTarget;
let leavePageConfirmed = false;
// Get image url for Avatar if the assistant has an avatar
$: avatarUrl = avatarPath
? `${env.PUBLIC_SUPABASE_URL}/storage/v1/object/public/assistant_avatars/${avatarPath}`
: '';
// Show cancel modal if form is tainted and user attempts to navigate away
beforeNavigate(({ cancel, to, type }) => {
if (to) {
navigateTo = to;
}
if (!leavePageConfirmed && isTainted() && !bypassCancelWarning) {
cancel();
leavePageConfirmed = false; // reset
if (type !== 'leave') {
// if leaving app, don't show cancel modal (ex. refresh page), triggers the native browser unload confirmation dialog.
cancelModalOpen = true;
}
}
});
</script>

<form method="POST" enctype="multipart/form-data" use:enhance class="assistant-form">
<div class="container">
<div class="inner-container">
<div class="top-row">
<div class="title">{`${isEditMode ? 'Edit' : 'New'} Assistant`}</div>
<AssistantAvatar bind:files bind:selectedPictogramName {avatarUrl} />
</div>
<input type="hidden" name="id" value={$form.id} />
<TextInput
name="name"
autocomplete="off"
labelText="Name"
placeholder="Assistant name"
bind:value={$form.name}
maxlength={ASSISTANTS_NAME_MAX_LENGTH}
invalid={!!$errors.name}
invalidText={$errors.name?.toString()}
/>

<InputTooltip
name="description"
labelText="Tagline"
tooltipText="Taglines display on assistant tiles"
/>

<TextInput
name="description"
autocomplete="off"
placeholder="Here to help..."
labelText="Description"
hideLabel
bind:value={$form.description}
maxlength={ASSISTANTS_DESCRIPTION_MAX_LENGTH}
invalid={!!$errors.description}
invalidText={$errors.description?.toString()}
/>

<InputTooltip
name="instructions"
labelText="Instructions"
tooltipText="Detailed instructions to guide your assistant's responses and behavior"
/>

<TextArea
name="instructions"
autocomplete="off"
labelText="Instructions"
bind:value={$form.instructions}
rows={6}
placeholder="You'll act as..."
hideLabel
invalid={!!$errors.instructions}
invalidText={$errors.instructions?.toString()}
maxlength={ASSISTANTS_INSTRUCTIONS_MAX_LENGTH}
/>

<InputTooltip
name="temperature"
labelText="Temperature"
tooltipText="Adjust the slider to set the creativity level of your assistant's responses"
/>
<Slider
name="temperature"
bind:value={$form.temperature}
hideLabel
hideTextInput
fullWidth
min={0}
max={1}
step={0.1}
minLabel="Min"
maxLabel="Max"
invalid={!!$errors.temperature}
/>

<!--Note - Data Sources is a placeholder and will be completed in a future story-->
<InputTooltip
name="data_sources"
labelText="Data Sources"
tooltipText="Specific files your assistant can search and reference"
/>
<div>
<Button icon={Add} kind="secondary" size="small"
>Add <input name="data_sources" type="hidden" /></Button
>
</div>

<div>
<Button
kind="secondary"
size="small"
on:click={() => {
bypassCancelWarning = true;
goto('/chat/assistants-management');
}}>Cancel</Button
>
<Button kind="primary" size="small" type="submit" disabled={$submitting}>Save</Button>
</div>
</div>
</div>
</form>

<div class="cancel-modal">
<Modal
bind:open={cancelModalOpen}
modalHeading="Unsaved Changes"
primaryButtonText="Leave this page"
secondaryButtonText="Stay on page"
on:click:button--secondary={() => (cancelModalOpen = false)}
on:submit={() => {
leavePageConfirmed = true;
if (navigateTo) goto(navigateTo.url.href);
}}
><p>
You have unsaved changes. Do you want to leave this page? Unsaved changes will be deleted.
</p></Modal
>
</div>

<style lang="scss">
.assistant-form {
.container {
display: flex;
justify-content: center;
}
.inner-container {
display: flex;
flex-direction: column;
gap: 1.5rem;
width: 50%;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.top-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
@include type.type-style('heading-05');
}
.cancel-modal {
:global(.bx--modal-container) {
position: absolute;
top: 25%;
}
}
}
</style>
Loading

0 comments on commit e8408b7

Please sign in to comment.