feat: relocate context picker, refine chat input + flow card radii

- Move the @ context-picker Popover from above the textarea (in
  AIChatInput) to the bottom-left of the chat input controls row in
  AIChatDisplay, immediately to the left of the Flow mode selector.
  AIChatInput now exports addContextToSelection so the parent can drive
  the same validated add path. The badges-only row above the textarea
  only renders when there is at least one selected context (no empty
  bar). AIChatInput drops its now-unused Popover and AvailableContextList
  imports; AIChatDisplay picks them up plus AppAvailableContextList for
  the APP-mode list.
- Replace the raw send/stop button with a Button variant="subtle"
  iconOnly, keeping the rounded-full 28x28 hit area via btnClasses, the
  ArrowUp/Square icon swap, and the disabled-when-empty / cancel-when-
  loading behavior.
- Shrink the textarea: rows={3} -> rows={2} on all three textareas
  (ContextTextarea, the APP-mode textarea, the default-mode textarea)
  and bump min-height down from 3rem to 1.75rem so the textarea starts
  as a single visible line and grows with content via autosize.
- Refine chat input box radius to rounded-xl, match the textarea via
  border-radius: 0.75rem, and add pt-1 above the textarea for a touch
  of breathing room from the card's top edge.
- /design-sandbox: switch the right pane's flow editor card from
  rounded-xl to rounded-md so the flow card sits tighter while the chat
  input keeps the softer rounded-xl.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-04-29 13:09:12 +02:00
parent c750bdebe9
commit 05f3e2845e
4 changed files with 60 additions and 65 deletions
@@ -19,6 +19,9 @@
import ProviderModelSelector from './ProviderModelSelector.svelte'
import ChatMode from './ChatMode.svelte'
import DatatableCreationPolicy from './DatatableCreationPolicy.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import AvailableContextList from './AvailableContextList.svelte'
import AppAvailableContextList from './AppAvailableContextList.svelte'
import Markdown from 'svelte-exmarkdown'
import { aiChatManager, AIMode } from './AIChatManager.svelte'
import AIChatInput from './AIChatInput.svelte'
@@ -173,9 +176,9 @@
</div>
{/if}
<div
class="rounded-2xl border border-light bg-surface shadow-sm transition-all focus-within:shadow-md focus-within:border-gray-300 dark:focus-within:border-gray-600"
class="rounded-xl border border-light bg-surface shadow-sm transition-all focus-within:shadow-md focus-within:border-gray-300 dark:focus-within:border-gray-600"
>
<div class="px-1 pt-0.5">
<div class="px-1 pt-1">
<AIChatInput
bind:this={aiChatInput}
bind:selectedContext
@@ -194,6 +197,43 @@
<Markdown md={disabledMessage} />
</div>
{:else}
{#if aiChatManager.mode === AIMode.SCRIPT || aiChatManager.mode === AIMode.FLOW || aiChatManager.mode === AIMode.APP}
<Popover>
{#snippet trigger()}
<div
class="border rounded-md px-1 py-0.5 font-normal text-primary text-xs hover:bg-surface-hover bg-surface"
title="Add context"
>
@
</div>
{/snippet}
{#snippet content({ close })}
{#if aiChatManager.mode === AIMode.APP}
<AppAvailableContextList
{availableContext}
{selectedContext}
onSelect={(element) => {
aiChatInput?.addContextToSelection(element)
close()
}}
/>
{:else}
<AvailableContextList
{availableContext}
{selectedContext}
onSelect={(element) => {
aiChatInput?.addContextToSelection(element)
close()
}}
onSelectWorkspaceItem={(element) => {
aiChatInput?.addContextToSelection(element)
close()
}}
/>
{/if}
{/snippet}
</Popover>
{/if}
<ChatMode />
{#if aiChatManager.mode === AIMode.APP}
<DatatableCreationPolicy />
@@ -278,26 +318,22 @@
</div>
{#if !disabled}
<button
type="button"
aria-label={aiChatManager.loading ? 'Stop' : 'Send'}
title={aiChatManager.loading ? 'Stop' : 'Send'}
class="shrink-0 inline-flex items-center justify-center w-7 h-7 rounded-full bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 hover:bg-gray-700 dark:hover:bg-gray-300 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
<Button
variant="subtle"
size="xs"
iconOnly
startIcon={{ icon: aiChatManager.loading ? Square : ArrowUp }}
disabled={!aiChatManager.loading && !aiChatInput?.hasContent()}
onclick={() => {
title={aiChatManager.loading ? 'Stop' : 'Send'}
btnClasses="!rounded-full !w-7 !h-7"
on:click={() => {
if (aiChatManager.loading) {
cancel()
} else {
aiChatInput?.triggerSend()
}
}}
>
{#if aiChatManager.loading}
<Square class="w-3 h-3" />
{:else}
<ArrowUp class="w-3.5 h-3.5" strokeWidth={2.5} />
{/if}
</button>
/>
{/if}
</div>
</div>
@@ -1,6 +1,4 @@
<script lang="ts">
import Popover from '$lib/components/meltComponents/Popover.svelte'
import AvailableContextList from './AvailableContextList.svelte'
import AppAvailableContextList from './AppAvailableContextList.svelte'
import ContextElementBadge from './ContextElementBadge.svelte'
import ContextTextarea from './ContextTextarea.svelte'
@@ -109,7 +107,7 @@
}
}
function addContextToSelection(contextElement: ContextElement) {
export function addContextToSelection(contextElement: ContextElement) {
if (!selectedContext || !availableContext) return
const alreadySelected = selectedContext.find(
@@ -371,30 +369,8 @@
<div use:clickOutside class="relative">
{#if aiChatManager.mode === AIMode.SCRIPT || aiChatManager.mode === AIMode.FLOW}
{#if showContext}
{#if showContext && selectedContext && selectedContext.length > 0}
<div class="flex flex-row gap-1 mb-1 overflow-scroll pt-2 no-scrollbar">
<Popover>
{#snippet trigger()}
<div
class="border rounded-md px-1 py-0.5 font-normal text-primary text-xs hover:bg-surface-hover bg-surface"
>@</div
>
{/snippet}
{#snippet content({ close })}
<AvailableContextList
{availableContext}
{selectedContext}
onSelect={(element) => {
addContextToSelection(element)
close()
}}
onSelectWorkspaceItem={(element) => {
addContextToSelection(element)
close()
}}
/>
{/snippet}
</Popover>
{#each selectedContext as element}
<ContextElementBadge
contextElement={element}
@@ -426,26 +402,8 @@
{onKeyDown}
/>
{:else if aiChatManager.mode === AIMode.APP}
{#if showContext}
{#if showContext && selectedContext && selectedContext.length > 0}
<div class="flex flex-row gap-1 mb-1 overflow-scroll pt-2 no-scrollbar">
<Popover>
{#snippet trigger()}
<div
class="border rounded-md px-1 py-0.5 font-normal text-primary text-xs hover:bg-surface-hover bg-surface"
>@</div
>
{/snippet}
{#snippet content({ close })}
<AppAvailableContextList
{availableContext}
{selectedContext}
onSelect={(element) => {
addContextToSelection(element)
close()
}}
/>
{/snippet}
</Popover>
{#each selectedContext as element (element.type + '-' + element.title)}
<ContextElementBadge
contextElement={element}
@@ -486,7 +444,7 @@
sendRequest()
}
}}
rows={3}
rows={2}
placeholder={modePlaceholder}
class="resize-none"
{disabled}
@@ -532,7 +490,7 @@
sendRequest()
}
}}
rows={3}
rows={2}
placeholder={modePlaceholder}
class="resize-none"
{disabled}
@@ -315,7 +315,7 @@
onkeydown={handleKeyDown}
bind:value
use:autosize
rows={3}
rows={2}
oninput={handleInput}
onblur={() => {
setTimeout(() => {
@@ -373,12 +373,13 @@
.textarea-input {
padding: 0.25rem;
border: 1px solid transparent;
border-radius: 0.75rem; /* matches rounded-xl */
font-family: inherit;
font-size: 0.75rem;
line-height: 1.72;
white-space: pre-wrap;
word-break: break-words;
width: 100%;
min-height: 3rem;
min-height: 1.75rem;
}
</style>
@@ -235,7 +235,7 @@
than being its own root view. -->
<Pane size={50} minSize={30} class="flex flex-col min-h-0 p-2 pl-0">
<div
class="flex-1 min-h-0 rounded-xl border border-light bg-surface overflow-hidden shadow-sm"
class="flex-1 min-h-0 rounded-md border border-light bg-surface overflow-hidden shadow-sm"
>
<FlowWrapper
disableAi