Skip to content

Instantly share code, notes, and snippets.

@armchair-traveller
Created June 19, 2024 22:06
Show Gist options
  • Save armchair-traveller/ac810e1b017e5c7060770286b2c9633f to your computer and use it in GitHub Desktop.
Save armchair-traveller/ac810e1b017e5c7060770286b2c9633f to your computer and use it in GitHub Desktop.
IMX Ray bottom hanging action bar backup
<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