Skip to content

Commit

Permalink
Polish dashboard, separate out components
Browse files Browse the repository at this point in the history
  • Loading branch information
franknoirot committed Jan 9, 2024
1 parent 22f8c50 commit fc4f2e5
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 149 deletions.
44 changes: 44 additions & 0 deletions src/components/ExamplePrompts.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts">
import { EXAMPLES_TO_SHOW, EXAMPLE_PROMPTS } from '$lib/consts'
export let input: HTMLTextAreaElement | null
const getExammplePrompts = () =>
[...EXAMPLE_PROMPTS].sort(() => Math.random() - 0.5).slice(0, EXAMPLES_TO_SHOW)
let examplePrompts = getExammplePrompts()
</script>

<div {...$$restProps}>
<p class="font-mono pt-1 text-xs uppercase text-chalkboard-100 dark:text-chalkboard-20">
Example prompts:
</p>
<div class="prompt-buttons">
{#each examplePrompts as prompt (prompt)}
<button
on:click={() => {
if (input) {
input.value = prompt
input.focus()
examplePrompts = getExammplePrompts()
}
}}
>
<span class="mt-0.5">{prompt}</span>
</button>
{/each}
</div>
</div>

<style lang="postcss">
.prompt-buttons {
@apply grid md:grid-cols-2 gap-4 mt-4;
}
.prompt-buttons button {
@apply text-sm text-left font-mono tracking-wider rounded border p-3 md:p-4;
@apply bg-transparent hover:bg-green/50;
@apply text-chalkboard-70 dark:text-chalkboard-50 hover:text-chalkboard-120 dark:hover:text-chalkboard-10;
@apply border-chalkboard-30 dark:border-chalkboard-70;
@apply hover:border-chalkboard-120 dark:hover:border-chalkboard-10;
@apply hover:bg-green/20 text-chalkboard-120 dark:text-chalkboard-20 hover:hue-rotate-15;
}
</style>
117 changes: 117 additions & 0 deletions src/components/PromptForm.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script lang="ts">
import autosize from 'svelte-autosize'
import { page } from '$app/stores'
import type { Models } from '@kittycad/lib'
import ArrowRight from './Icons/ArrowRight.svelte'
import { endpoints } from '$lib/endpoints'
import { localGenerations } from '$lib/stores'
import { paths } from '$lib/paths'
import { goto } from '$app/navigation'
export let input: HTMLTextAreaElement | null
export let token: string
let inputValue = $page.url.searchParams.get('prompt') ?? ''
let form = null as HTMLFormElement | null
let button = null as HTMLButtonElement | null
let error: string | null = null
let isSubmitting = false
let showSuccessMessage: boolean = false
let isCoolingDown = false
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') {
button?.click()
}
}
const submitPrompt = async (prompt: string) => {
const OUTPUT_FORMAT: Models['FileExportFormat_type'] = 'gltf'
const response = await fetch(endpoints.prompt(OUTPUT_FORMAT), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token
},
body: JSON.stringify({ prompt })
})
if (!response.ok) {
error = 'Failed to submit prompt'
return
}
const responseData = (await response.json()) as Models['TextToCad_type']
$localGenerations = responseData ? [responseData, ...$localGenerations] : $localGenerations
goto(paths.VIEW(responseData.id))
}
const submitForm = async (e: Event) => {
e.preventDefault()
isSubmitting = true
const prompt = (e.target as HTMLFormElement).prompt.value
await submitPrompt(prompt)
const form = e.target as HTMLFormElement
form.reset()
showSuccessMessage = true
isCoolingDown = true
setTimeout(() => {
isCoolingDown = false
}, 3000)
isSubmitting = false
}
</script>

<form on:submit={submitForm} class="flex border items-stretch text-lg" bind:this={form}>
<label class="flex-1 grid place-items-center">
<span class="sr-only">Enter a text-to-CAD prompt:</span>
<textarea
autocapitalize="false"
name="prompt"
placeholder="e.g. Create a plate with 4 holes and rounded corners"
required
spellcheck="false"
disabled={isSubmitting}
class="w-full tracking-wide px-4 py-1 focus:outline-none focus:bg-green/20 focus:placeholder-shown:bg-green/10"
bind:this={input}
bind:value={inputValue}
on:keydown={handleKeydown}
on:input={() => {
showSuccessMessage = false
error = null
}}
use:autosize
/>
</label>
<button type="submit" class="submit" disabled={isCoolingDown || isSubmitting} bind:this={button}>
<span class="sr-only md:not-sr-only md:pt-0.5">
{#if isSubmitting}
Submitting
{:else}
Submit
{/if}
</span>
<ArrowRight class="w-8 h-8 md:w-5 md:h-5" />
</button>
</form>
{#if error}
<p class="text-red mt-2">{error}</p>
{:else if showSuccessMessage}
<p class="border border-t-0 border-chalkboard-70 dark:border-chalkboard-40 p-2 bg-green/40">
Prompt submitted!
</p>
{/if}

<style lang="postcss">
.submit {
@apply m-1 md:px-4 lg:px-6 md:pt-1 border;
@apply self-end flex items-center justify-center gap-2;
@apply font-mono uppercase tracking-[1px] text-sm;
@apply border-chalkboard-100 dark:border-chalkboard-20;
@apply bg-green;
@apply disabled:bg-chalkboard-40 dark:disabled:bg-chalkboard-70;
}
</style>
16 changes: 16 additions & 0 deletions src/components/PromptGuide.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="my-12 text-chalkboard-100 dark:text-chalkboard-20">
<h2 class="font-mono pt-1 text-xs uppercase">Prompt writing tips:</h2>
<ul class="list-disc pl-6 text-lg">
<li class="my-4">
Describe an object that can be represented in geometric shapes, and less nebulous concepts
such as "a tiger" or "the universe", unless you just want to see what it does with that 😏
</li>
<li class="my-4">
Be as explicit as possible. For example, if you want a plate with 4 holes, say <em>where</em>
you want the holes placed and <em>how big</em> of a diameter each should have
</li>
<li class="my-4">
Our ML models are better at describing single objects than assemblies <em>for now</em>
</li>
</ul>
</div>
2 changes: 2 additions & 0 deletions src/lib/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const EXAMPLE_PROMPTS = [
'a coaster for a drink'
] as const

export const EXAMPLES_TO_SHOW = 4

export const TIME_BUCKETS = [
{
name: 'Today',
Expand Down
3 changes: 2 additions & 1 deletion src/lib/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export const paths = {
VIEW: (viewId: string) => `/view/${viewId}`,
SIGN_OUT: `/?${SIGN_OUT_PARAM}=true`,
ZOO_SITE: SITE,
ZOO_BILLING: SITE + '/account/billing-information'
ZOO_BILLING: SITE + '/account/billing-information',
ZOO_ML: SITE + '/machine-learning-api'
} as const
177 changes: 29 additions & 148 deletions src/routes/(sidebarLayout)/dashboard/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,158 +1,39 @@
<script lang="ts">
import { endpoints } from '$lib/endpoints'
import type { Models } from '@kittycad/lib'
import PromptForm from 'components/PromptForm.svelte'
import type { PageData } from '../view/$types'
import { localGenerations } from '$lib/stores'
import { goto } from '$app/navigation'
import { EXAMPLE_PROMPTS } from '$lib/consts'
import { page } from '$app/stores'
import ExamplePrompts from 'components/ExamplePrompts.svelte'
import { paths } from '$lib/paths'
import autosize from 'svelte-autosize'
import ArrowRight from 'components/Icons/ArrowRight.svelte'
import PromptGuide from 'components/PromptGuide.svelte'
export let data: PageData
let form = null as HTMLFormElement | null
let input = null as HTMLTextAreaElement | null
let button = null as HTMLButtonElement | null
const getExammplePrompts = () => [...EXAMPLE_PROMPTS].sort(() => Math.random() - 0.5).slice(0, 3)
let examplePrompts = getExammplePrompts()
let error: string | null = null
let isSubmitting = false
let showSuccessMessage: boolean = false
let isCoolingDown = false
let listSection = null as HTMLDivElement | null
let inputValue = $page.url.searchParams.get('prompt') ?? ''
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') {
button?.click()
}
}
const submitPrompt = async (prompt: string) => {
const OUTPUT_FORMAT: Models['FileExportFormat_type'] = 'gltf'
const response = await fetch(endpoints.prompt(OUTPUT_FORMAT), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + data.token
},
body: JSON.stringify({ prompt })
})
if (!response.ok) {
error = 'Failed to submit prompt'
return
}
const responseData = (await response.json()) as Models['TextToCad_type']
$localGenerations = responseData ? [responseData, ...$localGenerations] : $localGenerations
goto(paths.VIEW(responseData.id))
}
const submitForm = async (e: Event) => {
e.preventDefault()
isSubmitting = true
const prompt = (e.target as HTMLFormElement).prompt.value
await submitPrompt(prompt)
const form = e.target as HTMLFormElement
form.reset()
showSuccessMessage = true
isCoolingDown = true
setTimeout(() => {
isCoolingDown = false
}, 3000)
listSection?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
isSubmitting = false
}
</script>

<section class="mx-auto max-w-2xl my-16 md:my-24 lg:my-48">
<h1 class="text-4xl md:text-5xl mb-2">
Text-to-<span class="text-green">CAD</span>
</h1>
<form on:submit={submitForm} class="flex border items-stretch text-lg" bind:this={form}>
<label class="flex-1 grid place-items-center">
<span class="sr-only">Enter a text-to-CAD prompt:</span>
<textarea
autocapitalize="false"
name="prompt"
placeholder="e.g. Create a plate with 4 holes and rounded corners"
required
spellcheck="false"
disabled={isSubmitting}
class="w-full tracking-wide px-4 py-1 focus:outline-none focus:bg-green/20 focus:placeholder-shown:bg-green/10"
bind:this={input}
bind:value={inputValue}
on:keydown={handleKeydown}
on:input={() => {
showSuccessMessage = false
error = null
}}
use:autosize
/>
</label>
<button
type="submit"
class="submit"
disabled={isCoolingDown || isSubmitting}
bind:this={button}
>
<span class="sr-only md:not-sr-only md:pt-0.5">
{#if isSubmitting}
Submitting
{:else}
Submit
{/if}
</span>
<ArrowRight class="w-8 h-8 md:w-5 md:h-5" />
</button>
</form>
{#if error}
<p class="text-red mt-2">{error}</p>
{:else if showSuccessMessage}
<p class="border border-t-0 border-chalkboard-70 dark:border-chalkboard-40 p-2 bg-green/40">
Prompt submitted!
</p>
{/if}
<div class="prompt-buttons">
<span class="font-mono pt-1 text-xs uppercase text-chalkboard-70 dark:text-chalkboard-40"
>Example prompts:</span
>
{#each examplePrompts as prompt (prompt)}
<button
on:click={() => {
if (input) {
input.value = prompt
input.focus()
examplePrompts = getExammplePrompts()
}
}}>{prompt}</button
>
{/each}
<section class="mx-auto min-h-screen flex flex-col justify-end">
<div class="max-w-2xl mx-auto mt-16">
<h1 class="text-4xl md:text-5xl mb-2">
Text-to-<span class="text-green">CAD</span>
</h1>
<div class="tracking-wide">
<PromptForm bind:input token={data.token} />
<ExamplePrompts {input} class="my-12" />
<PromptGuide />
</div>
</div>
<footer
class="max-w-4xl w-full mx-auto flex flex-col md:flex-row gap-4 md:items-center justify-between px-2 lg:px-4 py-1 border border-b-0 text-xs font-mono text-chalkboard-70 dark:text-chalkboard-40"
>
<p>
Built with the{' '}
<a href={paths.ZOO_ML} target="_blank" rel="noopener noreferrer" class="underline">
ML-ephant API by Zoo
</a>
</p>
<p>
View and contribute on{' '}
<a href={paths.GITHUB_REPO} target="_blank" rel="noopener noreferrer" class="underline">
GitHub
</a>
</p>
</footer>
</section>

<style lang="postcss">
.submit {
@apply m-1 md:px-4 lg:px-6 md:pt-1 border;
@apply self-end flex items-center justify-center gap-2;
@apply font-mono uppercase tracking-[1px] text-sm;
@apply border-chalkboard-100 dark:border-chalkboard-20;
@apply bg-green;
@apply disabled:bg-chalkboard-40 dark:disabled:bg-chalkboard-70;
}
.prompt-buttons {
@apply flex flex-wrap gap-2 mt-4;
}
.prompt-buttons button {
@apply text-sm tracking-wider rounded-full border pt-0.5 pb-0 px-3;
@apply bg-transparent hover:bg-green/50;
@apply text-chalkboard-70 dark:text-chalkboard-50 hover:text-chalkboard-120 dark:hover:text-chalkboard-10;
@apply border-chalkboard-30 dark:border-chalkboard-70;
@apply hover:border-chalkboard-120 dark:hover:border-chalkboard-10;
@apply hover:bg-green/20 text-chalkboard-120 dark:text-chalkboard-20 hover:hue-rotate-15;
}
</style>

0 comments on commit fc4f2e5

Please sign in to comment.