feat(message-input): turn plus button into menu with attach files and quick messages

This commit is contained in:
xintaofei
2026-04-24 11:07:56 +08:00
parent 61778f152b
commit 7caf730369
11 changed files with 171 additions and 11 deletions

View File

@@ -19,6 +19,8 @@ import {
FileSearch,
GitFork,
ListPlus,
MessageSquareText,
Paperclip,
Plus,
Search,
Send,
@@ -33,13 +35,16 @@ import {
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { ImagePreviewDialog } from "@/components/ui/image-preview-dialog"
import { cn, randomUUID } from "@/lib/utils"
import { matchShortcutEvent } from "@/lib/keyboard-shortcuts"
import { useShortcutSettings } from "@/hooks/use-shortcut-settings"
import { readFileBase64 } from "@/lib/api"
import { readFileBase64, quickMessagesList } from "@/lib/api"
import { openFileDialog } from "@/lib/platform"
import { disposeTauriListener } from "@/lib/tauri-listener"
import type {
@@ -50,6 +55,7 @@ import type {
PromptCapabilitiesInfo,
PromptDraft,
PromptInputBlock,
QuickMessage,
SessionConfigOptionInfo,
SessionModeInfo,
} from "@/lib/types"
@@ -409,6 +415,8 @@ export function MessageInput({
})
const [attachments, setAttachments] = useState<InputAttachment[]>([])
const [isDragActive, setIsDragActive] = useState(false)
const [quickMessages, setQuickMessages] = useState<QuickMessage[]>([])
const [quickMessagesLoading, setQuickMessagesLoading] = useState(false)
const [previewAttachmentId, setPreviewAttachmentId] = useState<string | null>(
null
)
@@ -1299,6 +1307,47 @@ export function MessageInput({
}
}, [appendResourceAttachments, defaultPath, disabled])
const loadQuickMessages = useCallback(async () => {
setQuickMessagesLoading(true)
try {
const list = await quickMessagesList()
setQuickMessages(list)
} catch (error) {
console.error("[MessageInput] load quick messages failed:", error)
} finally {
setQuickMessagesLoading(false)
}
}, [])
const handleAddMenuOpenChange = useCallback(
(open: boolean) => {
if (!open) return
cursorPosRef.current = textareaRef.current?.selectionStart ?? null
loadQuickMessages().catch((error) => {
console.error("[MessageInput] quick messages refresh failed:", error)
})
},
[loadQuickMessages]
)
const handleQuickMessageSelect = useCallback((message: QuickMessage) => {
const insertion = message.content
if (!insertion) return
const current = textRef.current
const rawPos = cursorPosRef.current ?? current.length
const pos = Math.max(0, Math.min(rawPos, current.length))
const before = current.slice(0, pos)
const after = current.slice(pos)
setText(before + insertion + after)
requestAnimationFrame(() => {
const ta = textareaRef.current
if (!ta) return
ta.focus()
const newPos = pos + insertion.length
ta.setSelectionRange(newPos, newPos)
})
}, [])
useEffect(() => {
if (!attachmentTabId) return
@@ -1994,16 +2043,77 @@ export function MessageInput({
/>
<div className="@container flex shrink-0 items-end justify-between gap-2 px-2 pb-2">
<div className="flex min-w-0 items-end gap-2">
<Button
onClick={handlePickFiles}
disabled={disabled}
variant="outline"
size="icon"
className="h-6 w-6 shrink-0 bg-transparent"
title={t("attachFiles")}
>
<Plus className="size-4" />
</Button>
<DropdownMenu onOpenChange={handleAddMenuOpenChange}>
<DropdownMenuTrigger asChild>
<Button
disabled={disabled}
variant="outline"
size="icon"
className="h-6 w-6 shrink-0 bg-transparent"
title={t("addActions")}
aria-label={t("addActions")}
>
<Plus className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
className="min-w-48"
>
<DropdownMenuItem
onClick={() => {
handlePickFiles().catch((error) => {
console.error(
"[MessageInput] pick files from menu failed:",
error
)
})
}}
>
<Paperclip className="size-4" />
{t("attachFiles")}
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<MessageSquareText className="size-4" />
{t("quickMessages")}
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
className="min-w-40 overflow-y-auto"
style={{
maxHeight:
"min(32rem, var(--radix-dropdown-menu-content-available-height))",
}}
>
{quickMessagesLoading && quickMessages.length === 0 ? (
<div className="px-3 py-4 text-center text-xs text-muted-foreground">
{t("quickMessagesLoading")}
</div>
) : quickMessages.length === 0 ? (
<div className="px-3 py-4 text-center text-xs text-muted-foreground">
{t("quickMessagesEmpty")}
</div>
) : (
quickMessages.map((message) => (
<DropdownMenuItem
key={message.id}
onClick={() => handleQuickMessageSelect(message)}
>
<span className="truncate">
{message.title || (
<span className="italic text-muted-foreground">
{t("quickMessageUntitled")}
</span>
)}
</span>
</DropdownMenuItem>
))
)}
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button

View File

@@ -1569,6 +1569,11 @@
"askAnything": "اسأل أي شيء...",
"removeAttachmentAria": "إزالة {name}",
"attachFiles": "إرفاق ملفات",
"addActions": "إضافة",
"quickMessages": "الرسائل السريعة",
"quickMessagesEmpty": "لا توجد رسائل سريعة بعد",
"quickMessagesLoading": "جارٍ التحميل...",
"quickMessageUntitled": "بدون عنوان",
"dropFilesToAttach": "أسقط الملفات لإرفاقها",
"loadingSettings": "جارٍ تحميل الإعدادات...",
"loadingMode": "جارٍ تحميل الوضع...",

View File

@@ -1569,6 +1569,11 @@
"askAnything": "Fragen Sie alles...",
"removeAttachmentAria": "{name} entfernen",
"attachFiles": "Dateien anhängen",
"addActions": "Hinzufügen",
"quickMessages": "Schnellnachrichten",
"quickMessagesEmpty": "Noch keine Schnellnachrichten",
"quickMessagesLoading": "Wird geladen...",
"quickMessageUntitled": "Ohne Titel",
"dropFilesToAttach": "Dateien zum Anhängen ablegen",
"loadingSettings": "Einstellungen werden geladen...",
"loadingMode": "Modus wird geladen...",

View File

@@ -1569,6 +1569,11 @@
"askAnything": "Ask anything...",
"removeAttachmentAria": "Remove {name}",
"attachFiles": "Attach files",
"addActions": "Add",
"quickMessages": "Quick messages",
"quickMessagesEmpty": "No quick messages yet",
"quickMessagesLoading": "Loading...",
"quickMessageUntitled": "Untitled",
"dropFilesToAttach": "Drop files to attach",
"loadingSettings": "Loading settings...",
"loadingMode": "Loading mode...",

View File

@@ -1569,6 +1569,11 @@
"askAnything": "Pregunta lo que sea...",
"removeAttachmentAria": "Quitar {name}",
"attachFiles": "Adjuntar archivos",
"addActions": "Añadir",
"quickMessages": "Mensajes rápidos",
"quickMessagesEmpty": "Aún no hay mensajes rápidos",
"quickMessagesLoading": "Cargando...",
"quickMessageUntitled": "Sin título",
"dropFilesToAttach": "Suelta archivos para adjuntar",
"loadingSettings": "Cargando ajustes...",
"loadingMode": "Cargando modo...",

View File

@@ -1569,6 +1569,11 @@
"askAnything": "Posez n'importe quelle question...",
"removeAttachmentAria": "Retirer {name}",
"attachFiles": "Joindre des fichiers",
"addActions": "Ajouter",
"quickMessages": "Messages rapides",
"quickMessagesEmpty": "Aucun message rapide pour l'instant",
"quickMessagesLoading": "Chargement...",
"quickMessageUntitled": "Sans titre",
"dropFilesToAttach": "Déposez des fichiers à joindre",
"loadingSettings": "Chargement des paramètres...",
"loadingMode": "Chargement du mode...",

View File

@@ -1569,6 +1569,11 @@
"askAnything": "何でも質問してください...",
"removeAttachmentAria": "{name} を削除",
"attachFiles": "ファイルを添付",
"addActions": "追加",
"quickMessages": "クイックメッセージ",
"quickMessagesEmpty": "クイックメッセージはありません",
"quickMessagesLoading": "読み込み中...",
"quickMessageUntitled": "無題",
"dropFilesToAttach": "ファイルをドロップして添付",
"loadingSettings": "設定を読み込み中...",
"loadingMode": "モードを読み込み中...",

View File

@@ -1569,6 +1569,11 @@
"askAnything": "무엇이든 물어보세요...",
"removeAttachmentAria": "{name} 제거",
"attachFiles": "파일 첨부",
"addActions": "추가",
"quickMessages": "빠른 메시지",
"quickMessagesEmpty": "빠른 메시지가 없습니다",
"quickMessagesLoading": "불러오는 중...",
"quickMessageUntitled": "제목 없음",
"dropFilesToAttach": "파일을 놓아 첨부",
"loadingSettings": "설정 불러오는 중...",
"loadingMode": "모드 불러오는 중...",

View File

@@ -1569,6 +1569,11 @@
"askAnything": "Pergunte qualquer coisa...",
"removeAttachmentAria": "Remover {name}",
"attachFiles": "Anexar arquivos",
"addActions": "Adicionar",
"quickMessages": "Mensagens rápidas",
"quickMessagesEmpty": "Ainda não há mensagens rápidas",
"quickMessagesLoading": "Carregando...",
"quickMessageUntitled": "Sem título",
"dropFilesToAttach": "Solte arquivos para anexar",
"loadingSettings": "Carregando configurações...",
"loadingMode": "Carregando modo...",

View File

@@ -1569,6 +1569,11 @@
"askAnything": "请开始输入...",
"removeAttachmentAria": "移除 {name}",
"attachFiles": "附加文件",
"addActions": "添加",
"quickMessages": "快捷消息",
"quickMessagesEmpty": "暂无快捷消息",
"quickMessagesLoading": "加载中...",
"quickMessageUntitled": "未命名",
"dropFilesToAttach": "拖拽文件到此处附加",
"loadingSettings": "正在加载设置...",
"loadingMode": "正在加载模式...",

View File

@@ -1569,6 +1569,11 @@
"askAnything": "請開始輸入...",
"removeAttachmentAria": "移除 {name}",
"attachFiles": "附加檔案",
"addActions": "新增",
"quickMessages": "快捷訊息",
"quickMessagesEmpty": "尚無快捷訊息",
"quickMessagesLoading": "載入中...",
"quickMessageUntitled": "未命名",
"dropFilesToAttach": "拖曳檔案到此處附加",
"loadingSettings": "正在載入設定...",
"loadingMode": "正在載入模式...",