feat(ui): add clear action to prompt input
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { Suspense, createEffect, createSignal, lazy, on, onCleanup, Show } from "solid-js"
|
import { Suspense, createEffect, createSignal, lazy, on, onCleanup, Show } from "solid-js"
|
||||||
import { ArrowBigUp, ArrowBigDown, Loader2, Mic } from "lucide-solid"
|
import { ArrowBigUp, ArrowBigDown, Loader2, Mic, X } from "lucide-solid"
|
||||||
import ExpandButton from "./expand-button"
|
import ExpandButton from "./expand-button"
|
||||||
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
import { clearAttachments, removeAttachment } from "../stores/attachments"
|
||||||
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
|
||||||
@@ -351,6 +351,19 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
textareaRef?.focus()
|
textareaRef?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleClearPrompt() {
|
||||||
|
clearPrompt()
|
||||||
|
clearHistoryDraft()
|
||||||
|
resetHistoryNavigation()
|
||||||
|
setShowPicker(false)
|
||||||
|
setPickerMode("mention")
|
||||||
|
setAtPosition(null)
|
||||||
|
setSearchQuery("")
|
||||||
|
setIgnoredAtPositions(new Set<number>())
|
||||||
|
syncAttachmentCounters("")
|
||||||
|
textareaRef?.focus()
|
||||||
|
}
|
||||||
|
|
||||||
function insertBlockContent(block: string) {
|
function insertBlockContent(block: string) {
|
||||||
const textarea = textareaRef
|
const textarea = textareaRef
|
||||||
const current = prompt()
|
const current = prompt()
|
||||||
@@ -422,6 +435,8 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
return hasText || attachments().length > 0
|
return hasText || attachments().length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canClearPrompt = () => prompt().length > 0
|
||||||
|
|
||||||
const shellHint = () =>
|
const shellHint = () =>
|
||||||
mode() === "shell"
|
mode() === "shell"
|
||||||
? { key: "Esc", text: t("promptInput.hints.shell.exit") }
|
? { key: "Esc", text: t("promptInput.hints.shell.exit") }
|
||||||
@@ -543,7 +558,7 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<div class="prompt-nav-buttons">
|
<div class="prompt-nav-buttons">
|
||||||
<div class="prompt-nav-top-row">
|
<div class="prompt-nav-column prompt-nav-column-left">
|
||||||
<Show when={showVoiceInput()}>
|
<Show when={showVoiceInput()}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -586,43 +601,55 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="prompt-clear-button"
|
||||||
|
onClick={handleClearPrompt}
|
||||||
|
disabled={!canClearPrompt()}
|
||||||
|
aria-label={t("promptInput.clear.ariaLabel")}
|
||||||
|
title={t("promptInput.clear.title")}
|
||||||
|
>
|
||||||
|
<X class="h-4 w-4" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="prompt-nav-column prompt-nav-column-right">
|
||||||
<ExpandButton
|
<ExpandButton
|
||||||
expandState={expandState}
|
expandState={expandState}
|
||||||
onToggleExpand={handleExpandToggle}
|
onToggleExpand={handleExpandToggle}
|
||||||
/>
|
/>
|
||||||
|
<Show when={hasHistory()}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="prompt-history-button"
|
||||||
|
onClick={() =>
|
||||||
|
selectPreviousHistory({
|
||||||
|
force: true,
|
||||||
|
isPickerOpen: showPicker(),
|
||||||
|
getTextarea: () => textareaRef,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!canHistoryGoPrevious()}
|
||||||
|
aria-label={t("promptInput.history.previousAriaLabel")}
|
||||||
|
>
|
||||||
|
<ArrowBigUp class="h-5 w-5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="prompt-history-button"
|
||||||
|
onClick={() =>
|
||||||
|
selectNextHistory({
|
||||||
|
force: true,
|
||||||
|
isPickerOpen: showPicker(),
|
||||||
|
getTextarea: () => textareaRef,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!canHistoryGoNext()}
|
||||||
|
aria-label={t("promptInput.history.nextAriaLabel")}
|
||||||
|
>
|
||||||
|
<ArrowBigDown class="h-5 w-5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={hasHistory()}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="prompt-history-button"
|
|
||||||
onClick={() =>
|
|
||||||
selectPreviousHistory({
|
|
||||||
force: true,
|
|
||||||
isPickerOpen: showPicker(),
|
|
||||||
getTextarea: () => textareaRef,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
disabled={!canHistoryGoPrevious()}
|
|
||||||
aria-label={t("promptInput.history.previousAriaLabel")}
|
|
||||||
>
|
|
||||||
<ArrowBigUp class="h-5 w-5" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="prompt-history-button"
|
|
||||||
onClick={() =>
|
|
||||||
selectNextHistory({
|
|
||||||
force: true,
|
|
||||||
isPickerOpen: showPicker(),
|
|
||||||
getTextarea: () => textareaRef,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
disabled={!canHistoryGoNext()}
|
|
||||||
aria-label={t("promptInput.history.nextAriaLabel")}
|
|
||||||
>
|
|
||||||
<ArrowBigDown class="h-5 w-5" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
<Show when={shouldShowOverlay()}>
|
<Show when={shouldShowOverlay()}>
|
||||||
<div class={`prompt-input-overlay keyboard-hints ${mode() === "shell" ? "shell-mode" : ""}`}>
|
<div class={`prompt-input-overlay keyboard-hints ${mode() === "shell" ? "shell-mode" : ""}`}>
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ export const messagingMessages = {
|
|||||||
"promptInput.overlay.againToAbort": "again to abort session",
|
"promptInput.overlay.againToAbort": "again to abort session",
|
||||||
"promptInput.stopSession.ariaLabel": "Stop session",
|
"promptInput.stopSession.ariaLabel": "Stop session",
|
||||||
"promptInput.stopSession.title": "Stop session",
|
"promptInput.stopSession.title": "Stop session",
|
||||||
|
"promptInput.clear.ariaLabel": "Clear prompt text",
|
||||||
|
"promptInput.clear.title": "Clear prompt text",
|
||||||
"promptInput.send.ariaLabel": "Send message",
|
"promptInput.send.ariaLabel": "Send message",
|
||||||
"promptInput.send.errorFallback": "Failed to send message",
|
"promptInput.send.errorFallback": "Failed to send message",
|
||||||
"promptInput.send.errorTitle": "Send failed",
|
"promptInput.send.errorTitle": "Send failed",
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ export const messagingMessages = {
|
|||||||
"promptInput.overlay.againToAbort": "otra vez para abortar la sesión",
|
"promptInput.overlay.againToAbort": "otra vez para abortar la sesión",
|
||||||
"promptInput.stopSession.ariaLabel": "Detener sesión",
|
"promptInput.stopSession.ariaLabel": "Detener sesión",
|
||||||
"promptInput.stopSession.title": "Detener sesión",
|
"promptInput.stopSession.title": "Detener sesión",
|
||||||
|
"promptInput.clear.ariaLabel": "Borrar el texto del prompt",
|
||||||
|
"promptInput.clear.title": "Borrar el texto del prompt",
|
||||||
"promptInput.send.ariaLabel": "Enviar mensaje",
|
"promptInput.send.ariaLabel": "Enviar mensaje",
|
||||||
"promptInput.send.errorFallback": "No se pudo enviar el mensaje",
|
"promptInput.send.errorFallback": "No se pudo enviar el mensaje",
|
||||||
"promptInput.send.errorTitle": "Error al enviar",
|
"promptInput.send.errorTitle": "Error al enviar",
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ export const messagingMessages = {
|
|||||||
"promptInput.overlay.againToAbort": "à nouveau pour interrompre la session",
|
"promptInput.overlay.againToAbort": "à nouveau pour interrompre la session",
|
||||||
"promptInput.stopSession.ariaLabel": "Arrêter la session",
|
"promptInput.stopSession.ariaLabel": "Arrêter la session",
|
||||||
"promptInput.stopSession.title": "Arrêter la session",
|
"promptInput.stopSession.title": "Arrêter la session",
|
||||||
|
"promptInput.clear.ariaLabel": "Effacer le texte du prompt",
|
||||||
|
"promptInput.clear.title": "Effacer le texte du prompt",
|
||||||
"promptInput.send.ariaLabel": "Envoyer le message",
|
"promptInput.send.ariaLabel": "Envoyer le message",
|
||||||
"promptInput.send.errorFallback": "Impossible d'envoyer le message",
|
"promptInput.send.errorFallback": "Impossible d'envoyer le message",
|
||||||
"promptInput.send.errorTitle": "Échec de l'envoi",
|
"promptInput.send.errorTitle": "Échec de l'envoi",
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ export const messagingMessages = {
|
|||||||
"promptInput.overlay.againToAbort": "שוב כדי לבטל את הסשן",
|
"promptInput.overlay.againToAbort": "שוב כדי לבטל את הסשן",
|
||||||
"promptInput.stopSession.ariaLabel": "עצור סשן",
|
"promptInput.stopSession.ariaLabel": "עצור סשן",
|
||||||
"promptInput.stopSession.title": "עצור סשן",
|
"promptInput.stopSession.title": "עצור סשן",
|
||||||
|
"promptInput.clear.ariaLabel": "נקה את טקסט הפרומפט",
|
||||||
|
"promptInput.clear.title": "נקה את טקסט הפרומפט",
|
||||||
"promptInput.send.ariaLabel": "שלח הודעה",
|
"promptInput.send.ariaLabel": "שלח הודעה",
|
||||||
"promptInput.send.errorFallback": "שליחת ההודעה נכשלה",
|
"promptInput.send.errorFallback": "שליחת ההודעה נכשלה",
|
||||||
"promptInput.send.errorTitle": "השליחה נכשלה",
|
"promptInput.send.errorTitle": "השליחה נכשלה",
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ export const messagingMessages = {
|
|||||||
"promptInput.overlay.againToAbort": "もう一度押すとセッションを中断",
|
"promptInput.overlay.againToAbort": "もう一度押すとセッションを中断",
|
||||||
"promptInput.stopSession.ariaLabel": "セッションを停止",
|
"promptInput.stopSession.ariaLabel": "セッションを停止",
|
||||||
"promptInput.stopSession.title": "セッションを停止",
|
"promptInput.stopSession.title": "セッションを停止",
|
||||||
|
"promptInput.clear.ariaLabel": "プロンプトのテキストをクリア",
|
||||||
|
"promptInput.clear.title": "プロンプトのテキストをクリア",
|
||||||
"promptInput.send.ariaLabel": "メッセージを送信",
|
"promptInput.send.ariaLabel": "メッセージを送信",
|
||||||
"promptInput.send.errorFallback": "メッセージの送信に失敗しました",
|
"promptInput.send.errorFallback": "メッセージの送信に失敗しました",
|
||||||
"promptInput.send.errorTitle": "送信に失敗",
|
"promptInput.send.errorTitle": "送信に失敗",
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ export const messagingMessages = {
|
|||||||
"promptInput.overlay.againToAbort": "еще раз, чтобы прервать сессию",
|
"promptInput.overlay.againToAbort": "еще раз, чтобы прервать сессию",
|
||||||
"promptInput.stopSession.ariaLabel": "Остановить сессию",
|
"promptInput.stopSession.ariaLabel": "Остановить сессию",
|
||||||
"promptInput.stopSession.title": "Остановить сессию",
|
"promptInput.stopSession.title": "Остановить сессию",
|
||||||
|
"promptInput.clear.ariaLabel": "Очистить текст prompt",
|
||||||
|
"promptInput.clear.title": "Очистить текст prompt",
|
||||||
"promptInput.send.ariaLabel": "Отправить сообщение",
|
"promptInput.send.ariaLabel": "Отправить сообщение",
|
||||||
"promptInput.send.errorFallback": "Не удалось отправить сообщение",
|
"promptInput.send.errorFallback": "Не удалось отправить сообщение",
|
||||||
"promptInput.send.errorTitle": "Не удалось отправить",
|
"promptInput.send.errorTitle": "Не удалось отправить",
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ export const messagingMessages = {
|
|||||||
"promptInput.overlay.againToAbort": "再次按下以中止会话",
|
"promptInput.overlay.againToAbort": "再次按下以中止会话",
|
||||||
"promptInput.stopSession.ariaLabel": "停止会话",
|
"promptInput.stopSession.ariaLabel": "停止会话",
|
||||||
"promptInput.stopSession.title": "停止会话",
|
"promptInput.stopSession.title": "停止会话",
|
||||||
|
"promptInput.clear.ariaLabel": "清除输入框文本",
|
||||||
|
"promptInput.clear.title": "清除输入框文本",
|
||||||
"promptInput.send.ariaLabel": "发送消息",
|
"promptInput.send.ariaLabel": "发送消息",
|
||||||
"promptInput.send.errorFallback": "发送消息失败",
|
"promptInput.send.errorFallback": "发送消息失败",
|
||||||
"promptInput.send.errorTitle": "发送失败",
|
"promptInput.send.errorTitle": "发送失败",
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
.prompt-input {
|
.prompt-input {
|
||||||
@apply w-full pt-2.5 border text-sm resize-none outline-none transition-colors;
|
@apply w-full pt-2.5 border text-sm resize-none outline-none transition-colors;
|
||||||
padding-inline-start: 0.75rem;
|
padding-inline-start: 0.75rem;
|
||||||
padding-inline-end: 5.5rem;
|
padding-inline-end: 7.5rem;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
background-color: var(--surface-base);
|
background-color: var(--surface-base);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
@@ -90,22 +90,32 @@
|
|||||||
inset-inline-end: 0.25rem;
|
inset-inline-end: 0.25rem;
|
||||||
bottom: 0.25rem;
|
bottom: 0.25rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
align-items: flex-end;
|
align-items: flex-start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-end;
|
||||||
gap: 0.125rem;
|
gap: 0.125rem;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prompt-nav-top-row {
|
.prompt-nav-column {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
align-items: flex-end;
|
||||||
|
justify-content: flex-start;
|
||||||
gap: 0.125rem;
|
gap: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prompt-nav-column-left {
|
||||||
|
min-width: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-nav-column-right {
|
||||||
|
min-width: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.prompt-expand-button,
|
.prompt-expand-button,
|
||||||
.prompt-history-button {
|
.prompt-history-button,
|
||||||
|
.prompt-clear-button {
|
||||||
@apply w-7 h-7 flex items-center justify-center rounded-md;
|
@apply w-7 h-7 flex items-center justify-center rounded-md;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
background-color: var(--control-ghost-bg);
|
background-color: var(--control-ghost-bg);
|
||||||
@@ -115,7 +125,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prompt-expand-button:hover:not(:disabled),
|
.prompt-expand-button:hover:not(:disabled),
|
||||||
.prompt-history-button:hover:not(:disabled) {
|
.prompt-history-button:hover:not(:disabled),
|
||||||
|
.prompt-clear-button:hover:not(:disabled) {
|
||||||
background-color: var(--surface-secondary);
|
background-color: var(--surface-secondary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
@@ -127,7 +138,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prompt-expand-button:disabled,
|
.prompt-expand-button:disabled,
|
||||||
.prompt-history-button:disabled {
|
.prompt-history-button:disabled,
|
||||||
|
.prompt-clear-button:disabled {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
@@ -397,7 +409,7 @@
|
|||||||
.prompt-input {
|
.prompt-input {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
padding-inline-end: 5.5rem;
|
padding-inline-end: 7.5rem;
|
||||||
padding-bottom: 0.75rem;
|
padding-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user