fix(ui): unify thinking controls with icon buttons

This commit is contained in:
Shantur Rathore
2026-02-09 16:20:33 +00:00
parent d143faf8eb
commit 01300a81de
3 changed files with 73 additions and 57 deletions

View File

@@ -1,5 +1,5 @@
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, untrack } from "solid-js" import { For, Match, Show, Switch, createEffect, createMemo, createSignal, untrack } from "solid-js"
import { ExternalLink, FoldVertical, Trash2 } from "lucide-solid" import { ChevronsDownUp, ChevronsUpDown, ExternalLink, FoldVertical, Trash2 } from "lucide-solid"
import MessageItem from "./message-item" import MessageItem from "./message-item"
import ToolCall from "./tool-call" import ToolCall from "./tool-call"
import type { InstanceMessageStore } from "../stores/message-v2/instance-store" import type { InstanceMessageStore } from "../stores/message-v2/instance-store"
@@ -1010,10 +1010,13 @@ function ReasoningCard(props: ReasoningCardProps) {
const toggle = () => setExpanded((prev) => !prev) const toggle = () => setExpanded((prev) => !prev)
const viewHideLabel = () =>
expanded() ? t("messageBlock.reasoning.indicator.hide") : t("messageBlock.reasoning.indicator.view")
const hasDeleteTarget = () => Boolean(props.partId) const hasDeleteTarget = () => Boolean(props.partId)
const canDelete = () => hasDeleteTarget() && !deleting() const canDelete = () => hasDeleteTarget() && !deleting()
const handleDelete = async (event: Event) => { const handleDelete = async (event: MouseEvent) => {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
if (!canDelete()) return if (!canDelete()) return
@@ -1033,56 +1036,66 @@ function ReasoningCard(props: ReasoningCardProps) {
return ( return (
<div class="message-reasoning-card"> <div class="message-reasoning-card">
<button <div class="message-reasoning-header">
type="button" <button
class="message-reasoning-toggle" type="button"
onClick={toggle} class="message-reasoning-toggle"
aria-expanded={expanded()} onClick={toggle}
aria-label={expanded() ? t("messageBlock.reasoning.collapseAriaLabel") : t("messageBlock.reasoning.expandAriaLabel")} aria-expanded={expanded()}
> aria-label={expanded() ? t("messageBlock.reasoning.collapseAriaLabel") : t("messageBlock.reasoning.expandAriaLabel")}
<span class="message-reasoning-label flex flex-wrap items-center gap-2"> >
<span>{t("messageBlock.reasoning.thinkingLabel")}</span> <span class="message-reasoning-label flex flex-wrap items-center gap-2">
<Show when={props.showAgentMeta && (agentIdentifier() || modelIdentifier())}> <span>{t("messageBlock.reasoning.thinkingLabel")}</span>
<span class="message-step-meta-inline"> <Show when={props.showAgentMeta && (agentIdentifier() || modelIdentifier())}>
<Show when={agentIdentifier()}> <span class="message-step-meta-inline">
{(value) => ( <Show when={agentIdentifier()}>
<span class="font-medium text-[var(--message-assistant-border)]">{t("messageBlock.step.agentLabel", { agent: value() })}</span> {(value) => (
)} <span class="font-medium text-[var(--message-assistant-border)]">{t("messageBlock.step.agentLabel", { agent: value() })}</span>
</Show> )}
<Show when={modelIdentifier()}> </Show>
{(value) => ( <Show when={modelIdentifier()}>
<span class="font-medium text-[var(--message-assistant-border)]">{t("messageBlock.step.modelLabel", { model: value() })}</span> {(value) => (
)} <span class="font-medium text-[var(--message-assistant-border)]">{t("messageBlock.step.modelLabel", { model: value() })}</span>
</Show> )}
</span> </Show>
</Show> </span>
</span> </Show>
<span class="message-reasoning-meta">
<span class="message-reasoning-indicator">
{expanded() ? t("messageBlock.reasoning.indicator.hide") : t("messageBlock.reasoning.indicator.view")}
</span> </span>
</button>
<div class="message-reasoning-actions">
<button
type="button"
class="message-action-button"
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
toggle()
}}
aria-label={viewHideLabel()}
title={viewHideLabel()}
>
<Show when={expanded()} fallback={<ChevronsUpDown class="w-3.5 h-3.5" aria-hidden="true" />}>
<ChevronsDownUp class="w-3.5 h-3.5" aria-hidden="true" />
</Show>
</button>
<Show when={hasDeleteTarget()}> <Show when={hasDeleteTarget()}>
<span <button
class={`message-reasoning-indicator${canDelete() ? "" : " opacity-50 pointer-events-none"}`} type="button"
role="button" class="message-action-button"
tabIndex={0}
onClick={handleDelete} onClick={handleDelete}
onKeyDown={(event) => { disabled={!canDelete()}
if (event.key === "Enter" || event.key === " ") {
handleDelete(event)
}
}}
aria-label={t("messagePart.actions.deleteTitle")} aria-label={t("messagePart.actions.deleteTitle")}
title={t("messagePart.actions.deleteTitle")} title={t("messagePart.actions.deleteTitle")}
> >
{deleting() ? t("messagePart.actions.deleting") : t("messagePart.actions.delete")} <Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</span> </button>
</Show> </Show>
<span class="message-reasoning-time">{timestamp()}</span> <span class="message-reasoning-time">{timestamp()}</span>
</span> </div>
</button> </div>
<Show when={expanded()}> <Show when={expanded()}>
<div class="message-reasoning-expanded"> <div class="message-reasoning-expanded">

View File

@@ -305,19 +305,6 @@ export default function MessageItem(props: MessageItemProps) {
> >
<Copy class="w-3.5 h-3.5" aria-hidden="true" /> <Copy class="w-3.5 h-3.5" aria-hidden="true" />
</button> </button>
<Show when={deletableTextPartId()}>
{(partId) => (
<button
class="message-action-button"
onClick={() => void handleDeletePart(partId())}
disabled={isDeletingPart(partId())}
title={isDeletingPart(partId()) ? t("messagePart.actions.deleting") : t("messagePart.actions.delete")}
aria-label={isDeletingPart(partId()) ? t("messagePart.actions.deleting") : t("messagePart.actions.delete")}
>
<Trash2 class="w-3.5 h-3.5" aria-hidden="true" />
</button>
)}
</Show>
</div> </div>
</Show> </Show>
<Show when={!isUser()}> <Show when={!isUser()}>

View File

@@ -298,11 +298,20 @@
gap: 0; gap: 0;
} }
.message-reasoning-header {
display: flex;
align-items: stretch;
justify-content: space-between;
gap: 0.5rem;
}
.message-reasoning-toggle { .message-reasoning-toggle {
width: 100%; flex: 1 1 auto;
min-width: 0;
width: auto;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: flex-start;
gap: 0.65rem; gap: 0.65rem;
background: none; background: none;
border: none; border: none;
@@ -314,6 +323,13 @@
transition: background-color 0.2s ease, box-shadow 0.2s ease; transition: background-color 0.2s ease, box-shadow 0.2s ease;
} }
.message-reasoning-actions {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.25rem 0.6rem 0.25rem 0;
}
.message-reasoning-toggle:hover { .message-reasoning-toggle:hover {
background-color: var(--surface-hover); background-color: var(--surface-hover);
} }