From 411e61ac75ab4c23933d5f48331234f1631a6bd9 Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Sun, 14 Jun 2026 11:03:56 +0200 Subject: [PATCH] feat: use icon actions in chat composer bar Replace Send/Queue/Steer/Stop text buttons with compact icons, move Attach into the message box, and show the thinking level as a fill gauge. Keeps mobile layouts uncrowded while preserving accessible labels and the readable model selector. --- .changeset/prompt-composer-icon-actions.md | 5 ++ src/client/src/components/PromptEditor.ts | 13 ++-- .../src/components/promptEditorIcons.ts | 76 +++++++++++++++++++ src/client/src/components/shared.ts | 16 +++- 4 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 .changeset/prompt-composer-icon-actions.md create mode 100644 src/client/src/components/promptEditorIcons.ts diff --git a/.changeset/prompt-composer-icon-actions.md b/.changeset/prompt-composer-icon-actions.md new file mode 100644 index 0000000..b857bc3 --- /dev/null +++ b/.changeset/prompt-composer-icon-actions.md @@ -0,0 +1,5 @@ +--- +"@jmfederico/pi-web": patch +--- + +Declutter the chat composer bar with icon-based actions. The Send, Queue, Steer, and Stop buttons are now compact icons, the Attach button moved into the message box, and the thinking level is shown as a small gauge that fills with the selected level. This leaves more room on narrow/mobile layouts while keeping the model selector readable. All controls retain accessible labels and tooltips. diff --git a/src/client/src/components/PromptEditor.ts b/src/client/src/components/PromptEditor.ts index 46b420c..8f6a734 100644 --- a/src/client/src/components/PromptEditor.ts +++ b/src/client/src/components/PromptEditor.ts @@ -14,6 +14,7 @@ import { detectPromptCompletionTrigger, fileCompletionInsertText, type PromptCom import { clearDraft, loadDraft, saveDraft } from "../promptDraftStorage"; import { loadAttachmentDelivery, saveAttachmentDelivery } from "../attachmentPreferences"; import { promptEditorStyles, type CompletionItem } from "./shared"; +import { renderAttachIcon, renderSendIcon, renderQueueIcon, renderSteerIcon, renderStopIcon, renderThinkingGauge, thinkingLevelLabel } from "./promptEditorIcons"; import "./AutocompleteMenu"; interface PendingAttachment { @@ -90,6 +91,8 @@ export class PromptEditor extends LitElement { `; @@ -119,7 +120,7 @@ export class PromptEditor extends LitElement { return html`
- +
`; } diff --git a/src/client/src/components/promptEditorIcons.ts b/src/client/src/components/promptEditorIcons.ts new file mode 100644 index 0000000..9b9c246 --- /dev/null +++ b/src/client/src/components/promptEditorIcons.ts @@ -0,0 +1,76 @@ +import { svg, type TemplateResult } from "lit"; + +// Hand-rolled inline icons matching the project's stroke style +// (viewBox 0 0 24 24, fill none, stroke currentColor, round caps/joins). +// See tabIcons.ts for the established convention. + +export function renderAttachIcon(): TemplateResult { + return svg` + + `; +} + +export function renderSendIcon(): TemplateResult { + return svg` + + `; +} + +export function renderQueueIcon(): TemplateResult { + return svg` + + `; +} + +export function renderSteerIcon(): TemplateResult { + // Steer and send are both "do this now"; the queue icon carries the "later" distinction. + return renderSendIcon(); +} + +export function renderStopIcon(): TemplateResult { + return svg` + + `; +} + +const THINKING_LEVEL_STEPS: Record = { + off: 0, + minimal: 1, + low: 2, + medium: 3, + high: 4, + xhigh: 5, +}; + +export function thinkingLevelLabel(level: string | undefined): string { + return level === undefined || level === "" ? "off" : level; +} + +/** A 5-bar gauge that fills up to the active thinking level. */ +export function renderThinkingGauge(level: string | undefined): TemplateResult { + const steps = THINKING_LEVEL_STEPS[level ?? "off"] ?? 0; + const bars = [0, 1, 2, 3, 4].map((i) => { + const x = 3 + i * 4; + const height = 4 + i * 3; + const y = 20 - height; + const active = i < steps; + return svg``; + }); + return svg` + + `; +} diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index 8f51736..c112063 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -454,16 +454,24 @@ export const promptEditorStyles = css` .compact-status { display: flex; min-width: 0; align-items: center; gap: 6px; color: var(--pi-muted); font-size: 12px; flex: 1 1 0; } .compact-status > button { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; } .select-model { max-width: min(42vw, 320px); } - .select-thinking { max-width: 110px; } + .icon-button { flex: 0 0 auto; display: inline-grid; place-items: center; width: 36px; height: 36px; padding: 0; } + .icon-button .prompt-action-icon, .icon-button .prompt-thinking-gauge { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; } + .icon-button .prompt-action-icon-filled { fill: currentColor; stroke: none; } + .send-button:not(:disabled) { color: var(--pi-accent, var(--pi-text)); } + .stop-button:not(:disabled) { color: var(--pi-danger); } + .select-thinking .prompt-thinking-gauge .gauge-bar { fill: currentColor; stroke: none; opacity: .28; } + .select-thinking .prompt-thinking-gauge .gauge-bar-active { opacity: 1; } + .editor-attach { position: absolute; right: 8px; bottom: 8px; z-index: 2; width: 30px; height: 30px; } + .editor-attach .prompt-action-icon { width: 16px; height: 16px; } textarea, .markdown-editor .cm-editor { box-sizing: border-box; width: 100%; min-height: 54px; max-height: 220px; resize: none; overflow: hidden; border-radius: 8px; border: 1px solid var(--pi-border); background: var(--pi-bg); color: var(--pi-text); font: 16px/1.4 system-ui, sans-serif; } textarea { overflow-y: auto; padding: 8px; } .markdown-editor .cm-scroller { max-height: 220px; overflow-y: auto; font-family: system-ui, sans-serif; line-height: 1.4; } - .markdown-editor .cm-content { min-height: 38px; padding: 8px; caret-color: var(--pi-text); } + .markdown-editor .cm-content { min-height: 38px; padding: 8px 44px 8px 8px; caret-color: var(--pi-text); } .markdown-editor .cm-line { padding: 0; } .markdown-editor .cm-placeholder { color: var(--pi-dim); } .markdown-editor .cm-focused { outline: none; } .shell-mode textarea, .shell-mode .markdown-editor .cm-editor { border-color: var(--pi-success); box-shadow: 0 0 0 1px var(--pi-success-ring); } - .mode-hint { position: absolute; right: 8px; bottom: 8px; max-width: calc(100% - 16px); border: 1px solid var(--pi-success-border); border-radius: 999px; background: var(--pi-success-surface); color: var(--pi-success); padding: 2px 8px; font-size: 12px; pointer-events: none; } + .mode-hint { position: absolute; right: 46px; bottom: 8px; max-width: calc(100% - 54px); border: 1px solid var(--pi-success-border); border-radius: 999px; background: var(--pi-success-surface); color: var(--pi-success); padding: 2px 8px; font-size: 12px; pointer-events: none; } .attachments { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; } .attachment-chip { position: relative; width: 56px; height: 56px; border: 1px solid var(--pi-border); border-radius: 8px; overflow: hidden; background: var(--pi-bg); } .attachment-chip img { width: 100%; height: 100%; object-fit: cover; display: block; } @@ -482,7 +490,7 @@ export const promptEditorStyles = css` @media (max-width: 430px) { .compact-status { flex-basis: 170px; font-size: 11px; } .select-model { max-width: 48vw; } - .select-thinking { max-width: 70px; } button { padding: 5px 7px; } + .icon-button { width: 34px; height: 34px; } } `;