8.0k New

Suggestion

A short prompt you can send to the agent in one click.

Epicenter

Local-first, open source apps

Special Sponsor
<script lang="ts">
  import ArrowUpIcon from "@lucide/svelte/icons/arrow-up";
  import PlusIcon from "@lucide/svelte/icons/plus";
  import * as Bubble from "$lib/components/ui/bubble/index.js";
  import * as InputGroup from "$lib/components/ui/input-group/index.js";
  import * as Message from "$lib/components/ui/message/index.js";
  import { Button } from "$lib/components/ui/button/index.js";
 
  const suggestions = [
    "Add a dark mode toggle",
    "Fix the failing build",
    "Write tests for the form",
    "Upgrade to Next.js 16",
    "Add a loading state",
    "Find unused dependencies"
  ];
 
  const replies: Record<string, string> = {
    "Add a dark mode toggle":
      "Added a ModeToggle to the site header. It calls setTheme from next-themes.",
    "Fix the failing build":
      "The build failed on a missing import in app/page.tsx. I added it and the build passes.",
    "Write tests for the form":
      "Added four tests in signup-form.test.tsx. They cover the empty, invalid and valid states.",
    "Upgrade to Next.js 16":
      "Ran the codemod and bumped next to 16. Two routes needed async params, so I updated them.",
    "Add a loading state":
      "Added a loading.tsx to the dashboard route. It shows skeletons while the data loads.",
    "Find unused dependencies":
      "Three packages are not imported anywhere: lodash, moment and classnames. Should I remove them?"
  };
 
  let text = $state("");
  let turn = $state<{ prompt: string; reply: string } | null>(null);
  let form = $state<HTMLFormElement | null>(null);
  const canSend = $derived(text.trim().length > 0);
 
  function send(prompt: string) {
    turn = {
      prompt,
      reply:
        replies[prompt] ?? "This demo only knows the suggestions. Pick one."
    };
    text = "";
  }
 
  function onSubmit(event: SubmitEvent) {
    event.preventDefault();
    if (!canSend) {
      return;
    }
 
    send(text.trim());
  }
</script>
 
<div class="mx-auto flex w-full max-w-lg flex-col gap-6 py-6 text-sm">
  {#if turn}
    <div class="flex flex-col gap-4">
      <Message.Root align="end">
        <Message.Content>
          <Bubble.Root align="end" variant="muted">
            <Bubble.Content>{turn.prompt}</Bubble.Content>
          </Bubble.Root>
        </Message.Content>
      </Message.Root>
      <Message.Root>
        <Message.Content>
          <p>{turn.reply}</p>
        </Message.Content>
      </Message.Root>
    </div>
  {/if}
  <div class="flex flex-col gap-2">
    <div
      class="flex scroll-fade-x scrollbar-none gap-2 overflow-x-auto overscroll-x-contain"
    >
      {#each suggestions as suggestion, index (index)}
        <Button
          variant="outline"
          size="sm"
          class="shrink-0"
          onclick={() => send(suggestion)}
        >
          {suggestion}
        </Button>
      {/each}
    </div>
    <form bind:this={form} onsubmit={onSubmit}>
      <InputGroup.Root>
        <InputGroup.Textarea
          bind:value={text}
          placeholder="Ask the agent to change your code..."
          onkeydown={(event) => {
            if (event.key === "Enter" && !event.shiftKey) {
              event.preventDefault();
              form?.requestSubmit();
            }
          }}
          class="p-3"
        />
        <InputGroup.Addon align="block-end">
          <InputGroup.Button
            size="icon-sm"
            variant="outline"
            class="rounded-full"
            aria-label="Attach files"
          >
            <PlusIcon />
          </InputGroup.Button>
          <InputGroup.Button
            type="submit"
            size="icon-sm"
            variant="default"
            class="ml-auto rounded-full"
            disabled={!canSend}
            aria-label="Send"
          >
            <ArrowUpIcon />
          </InputGroup.Button>
        </InputGroup.Addon>
      </InputGroup.Root>
    </form>
  </div>
</div>

Suggestion is a prompt you send without typing. Each one is a Button with the prompt as its label. Put a row of them above the Prompt Input.

Installation

Suggestion is built with the Button component.

pnpm dlx shadcn-svelte@latest add button

Usage

<script lang="ts">
  import { Button } from "$lib/components/ui/button/index.js";
</script>
<div
  class="scrollbar-none flex scroll-fade-x gap-2 overflow-x-auto overscroll-x-contain"
>
  {#each suggestions as suggestion, index (index)}
    <Button
      variant="outline"
      size="sm"
      class="shrink-0"
      onclick={() => send(suggestion)}
    >
      {suggestion}
    </Button>
  {/each}
</div>

Scrolling

The row scrolls sideways when the suggestions do not fit. scroll-fade-x fades the edge that has more to show, and shrink-0 keeps each label on one line.

AI SDK

The label is the message. Send it as is.

chat.sendMessage({ text: suggestion });