Created
June 19, 2024 22:06
-
-
Save armchair-traveller/ac810e1b017e5c7060770286b2c9633f to your computer and use it in GitHub Desktop.
IMX Ray bottom hanging action bar backup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
import { binX, lineY, rectY } from '@observablehq/plot' | |
import CarbonThumbsDown from '~icons/carbon/thumbs-down' | |
import CarbonThumbsDownFilled from '~icons/carbon/thumbs-down-filled' | |
import CarbonThumbsUp from '~icons/carbon/thumbs-up' | |
import CarbonThumbsUpFilled from '~icons/carbon/thumbs-up-filled' | |
import CornerDownLeft from '~icons/lucide/corner-down-left' | |
import Mic from '~icons/lucide/mic' | |
import Paperclip from '~icons/lucide/paperclip' | |
import { browser } from '$app/environment' | |
import { typingEffect } from '$lib/actions/typingEffect' | |
import { Button, buttonVariants } from '$lib/components/ui/button' | |
import * as Card from '$lib/components/ui/card' | |
import { Label } from '$lib/components/ui/label' | |
import { Textarea } from '$lib/components/ui/textarea' | |
import * as Tooltip from '$lib/components/ui/tooltip' | |
import { sleep } from '$lib/utils/sleep' | |
import { cumsum, randomNormal } from 'd3' | |
import { onMount } from 'svelte' | |
import { slide } from 'svelte/transition' | |
import Sidebar from './Sidebar.svelte' | |
let charts = browser | |
? { | |
bar: rectY({ length: 10_000 }, binX({ y: 'count' }, { x: randomNormal() })).plot(), | |
line: lineY(cumsum({ length: 600 }, randomNormal())).plot(), | |
} | |
: {} | |
function appendElement(node, newElement) { | |
// Initially append the passed element | |
node.append(newElement) | |
return { | |
update(updatedElement) { | |
// Replace the existing child with the updated element | |
node.replaceWith(updatedElement, newElement) | |
newElement = updatedElement | |
}, | |
destroy() { | |
// Cleanup if the element using the action is destroyed | |
if (newElement && newElement.parentNode === node) { | |
node.remove(newElement) | |
} | |
}, | |
} | |
} | |
const messagesDummy = [ | |
{ | |
role: 'user', | |
content: | |
'Give me an index on ICD code X and the top 10 patient types with the highest total cost.', | |
}, | |
{ | |
role: 'assistant', | |
content: "Sure, here's a bar chart of the top 10 patient types with the highest total cost.", | |
chart: { | |
type: 'bar', | |
data: [], | |
}, | |
}, | |
{ role: 'user', content: "I'd like to know that data over time" }, | |
{ | |
role: 'assistant', | |
content: 'Sure, this represents the data over time.', | |
chart: { | |
type: 'line', | |
data: [], | |
}, | |
}, | |
{ role: 'user', content: 'Thanks' }, | |
{ role: 'assistant', content: 'You are welcome!' }, | |
] | |
let messages = [] | |
async function addMessagesAtIntervals() { | |
for (const element of messagesDummy) { | |
messages = [...messages, element] | |
await sleep(2000) // 1 second delay | |
} | |
} | |
// eslint-disable-next-line unicorn/prefer-top-level-await | |
browser && addMessagesAtIntervals() | |
</script> | |
<div class="grid h-screen w-full pl-[53px]"> | |
<Sidebar /> | |
<div class="flex flex-col"> | |
<main class="grid flex-1 gap-4 overflow-auto p-4"> | |
<div | |
class="relative flex h-full min-h-[50vh] flex-col rounded-xl bg-muted/50 p-4 pt-8 lg:col-span-2 lg:pt-4" | |
> | |
<Button variant="secondary" class="absolute right-3 top-3">New Chat</Button> | |
<div class="flex-1"> | |
<div class="prose mx-auto my-4 w-full max-w-[48rem] space-y-4"> | |
{#each messages as message} | |
<div class="relative" transition:slide> | |
{#if message.role === 'user'} | |
<p class="px-6"> | |
{message.content} | |
</p> | |
{:else} | |
<Card.Root class="rounded-bl-none"> | |
<Card.Content class="pb-0 pt-4"> | |
{#if message.chart} | |
<div | |
class="w-full" | |
use:appendElement={charts[message.chart.type]} | |
role="img" | |
/> | |
{/if} | |
<p use:typingEffect={{ speed: 25 }}> | |
{message.content} | |
</p> | |
</Card.Content> | |
<!-- Actions --> | |
<div class="mb-1 ml-1 flex"></div> | |
</Card.Root> | |
<div | |
class="-mt-px w-fit rounded-b-lg border border-t-0 bg-card text-card-foreground shadow-sm" | |
> | |
<Tooltip.Root> | |
<Tooltip.Trigger asChild let:builder> | |
<Button | |
class="text-muted-foreground" | |
variant="ghost" | |
size="icon" | |
builders={[builder]} | |
on:click={() => { | |
// TODO: either notify backend or be lazy like chatgpt and don't save into history | |
message.thumbsUp = true | |
}} | |
>{#if message.thumbsUp}<CarbonThumbsUpFilled /> | |
{:else}<CarbonThumbsUp />{/if} | |
</Button> | |
</Tooltip.Trigger> | |
<Tooltip.Content> | |
<p>Good response</p> | |
</Tooltip.Content> | |
</Tooltip.Root> | |
<Tooltip.Root> | |
<Tooltip.Trigger asChild let:builder> | |
<Button | |
class="text-muted-foreground" | |
variant="ghost" | |
size="icon" | |
builders={[builder]} | |
on:click={() => { | |
message.thumbsDown = true | |
}} | |
>{#if message.thumbsDown}<CarbonThumbsDownFilled /> | |
{:else}<CarbonThumbsDown />{/if} | |
</Button> | |
</Tooltip.Trigger> | |
<Tooltip.Content> | |
<p>Bad response</p> | |
</Tooltip.Content> | |
</Tooltip.Root> | |
</div> | |
{/if} | |
</div> | |
{/each} | |
</div> | |
</div> | |
<form | |
class="relative mx-auto w-full max-w-[48rem] overflow-hidden rounded-lg border bg-background focus-within:ring-1 focus-within:ring-ring" | |
data-x-chunk-name="dashboard-03-chunk-1" | |
data-x-chunk-description="A form for sending a message to an AI chatbot. The form has a textarea and buttons to upload files and record audio." | |
> | |
<Label for="message" class="sr-only">Message</Label> | |
<Textarea | |
autofocus | |
id="message" | |
placeholder="Type your message here..." | |
class="min-h-12 resize-none border-0 p-3 shadow-none focus-visible:ring-0" | |
/> | |
<div class="flex items-center p-3 pt-0"> | |
<Tooltip.Root> | |
<Tooltip.Trigger asChild let:builder> | |
<Button variant="ghost" size="icon" builders={[builder]}> | |
<Paperclip class="size-4" /> | |
<span class="sr-only">Attach file</span> | |
</Button> | |
</Tooltip.Trigger> | |
<Tooltip.Content side="top">Attach File</Tooltip.Content> | |
</Tooltip.Root> | |
<Tooltip.Root> | |
<Tooltip.Trigger asChild let:builder> | |
<Button variant="ghost" size="icon" builders={[builder]}> | |
<Mic class="size-4" /> | |
<span class="sr-only">Use Microphone</span> | |
</Button> | |
</Tooltip.Trigger> | |
<Tooltip.Content side="top">Use Microphone</Tooltip.Content> | |
</Tooltip.Root> | |
<Button type="submit" size="sm" class="ml-auto gap-1.5 transition hover:scale-105"> | |
Send Message | |
<CornerDownLeft class="size-3.5" /> | |
</Button> | |
</div> | |
</form> | |
</div> | |
</main> | |
</div> | |
</div> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment