diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css index a1585ad6a..6bcd69bde 100644 --- a/src/renderer/src/assets/main.css +++ b/src/renderer/src/assets/main.css @@ -3405,6 +3405,37 @@ body { background: var(--bg-hover, rgba(255, 255, 255, 0.08)); } +.chat-queue-edit { + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px; + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + border-radius: var(--radius-sm, 6px); +} + +.chat-queue-edit:hover { + color: var(--text-primary); + background: var(--bg-hover, rgba(255, 255, 255, 0.08)); +} + +.chat-queue-edit-input { + flex: 1; + min-width: 0; + padding: 2px 6px; + font-family: var(--font-sans); + font-size: 12px; + color: var(--text-primary); + background: var(--bg-primary); + border: 1px solid var(--border-focus, var(--accent)); + border-radius: var(--radius-sm, 4px); + outline: none; +} + /* Input area */ .chat-input-area { position: relative; diff --git a/src/renderer/src/screens/Chat/Chat.tsx b/src/renderer/src/screens/Chat/Chat.tsx index eed0a00b4..6086928f5 100644 --- a/src/renderer/src/screens/Chat/Chat.tsx +++ b/src/renderer/src/screens/Chat/Chat.tsx @@ -452,6 +452,11 @@ function Chat({ setQueuedMessages([...queueRef.current]); }, []); + const handleEditQueued = useCallback((index: number, newText: string) => { + queueRef.current[index] = { ...queueRef.current[index], text: newText }; + setQueuedMessages([...queueRef.current]); + }, []); + const handleSubmitOrQueue = useCallback( (text: string, attachments: Attachment[]) => { if (isLoading) { @@ -573,6 +578,7 @@ function Chat({ void; + onEdit: (index: number, newText: string) => void; } /** * Pending-send queue indicator shown above the input while the agent is busy. - * Each queued message can be individually cancelled via an X button. + * Each queued message can be edited via a pencil button or cancelled via an X button. */ export const QueuedMessages = memo(function QueuedMessages({ messages, onRemove, + onEdit, }: QueuedMessagesProps): React.JSX.Element | null { const { t } = useI18n(); const [expanded, setExpanded] = useState(false); + const [editingIndex, setEditingIndex] = useState(null); + const [editText, setEditText] = useState(""); + const editInputRef = useRef(null); useEffect(() => { if (messages.length === 0) setExpanded(false); }, [messages.length]); + // Focus the edit input when entering edit mode + useEffect(() => { + if (editingIndex !== null && editInputRef.current) { + editInputRef.current.focus(); + editInputRef.current.select(); + } + }, [editingIndex]); + + const startEdit = useCallback((index: number, text: string) => { + setEditingIndex(index); + setEditText(text); + }, []); + + const commitEdit = useCallback((index: number) => { + const trimmed = editText.trim(); + if (trimmed && trimmed !== messages[index].text) { + onEdit(index, trimmed); + } + setEditingIndex(null); + setEditText(""); + }, [editText, messages, onEdit]); + + const cancelEdit = useCallback(() => { + setEditingIndex(null); + setEditText(""); + }, []); + if (messages.length === 0) return null; const preview = (m: QueuedMessage): string => { @@ -36,13 +68,42 @@ export const QueuedMessages = memo(function QueuedMessages({ return t("chat.queuedAttachment", { count: m.attachments.length }); }; + const renderEditInput = (index: number) => ( + setEditText(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") commitEdit(index); + if (e.key === "Escape") cancelEdit(); + }} + onBlur={() => commitEdit(index)} + /> + ); + if (messages.length === 1) { + const isEditing = editingIndex === 0; return (
- - {preview(messages[0])} - + {isEditing ? ( + renderEditInput(0) + ) : ( + + {preview(messages[0])} + + )} + - - ))} + {isEditing ? ( + renderEditInput(i) + ) : ( + {preview(m)} + )} + + + + ); + })} )}
diff --git a/src/shared/i18n/locales/en/chat.ts b/src/shared/i18n/locales/en/chat.ts index 1a28eae43..9f6fdeef5 100644 --- a/src/shared/i18n/locales/en/chat.ts +++ b/src/shared/i18n/locales/en/chat.ts @@ -132,6 +132,7 @@ export default { queuedCount: "{{count}} queued", queuedAttachment: "{{count}} attachment(s)", queuedCancel: "Remove from queue", + queuedEdit: "Edit queued message", worktree: { loading: "Loading", empty: "Folder is empty", diff --git a/src/shared/i18n/locales/es/chat.ts b/src/shared/i18n/locales/es/chat.ts index 1cdf05413..a7cdbe3b4 100644 --- a/src/shared/i18n/locales/es/chat.ts +++ b/src/shared/i18n/locales/es/chat.ts @@ -108,6 +108,7 @@ export default { }, queued: "{{count}} mensaje(s) en cola — se enviará cuando el agente termine", queuedCancel: "Quitar de la cola", + queuedEdit: "Editar mensaje en cola", worktree: { loading: "Cargando", empty: "La carpeta está vacía", diff --git a/src/shared/i18n/locales/id/chat.ts b/src/shared/i18n/locales/id/chat.ts index bf4a24061..30478eff7 100644 --- a/src/shared/i18n/locales/id/chat.ts +++ b/src/shared/i18n/locales/id/chat.ts @@ -82,6 +82,7 @@ export default { version: "Tampilkan versi Hermes", }, queuedCancel: "Batalkan pesan antrian", + queuedEdit: "Edit pesan antrian", worktree: { loading: "Memuat", empty: "Folder kosong", diff --git a/src/shared/i18n/locales/ja/chat.ts b/src/shared/i18n/locales/ja/chat.ts index 83461d9f9..75f26e8b1 100644 --- a/src/shared/i18n/locales/ja/chat.ts +++ b/src/shared/i18n/locales/ja/chat.ts @@ -81,6 +81,7 @@ export default { version: "Hermes バージョンを表示", }, queuedCancel: "キューから削除", + queuedEdit: "キュー内のメッセージを編集", worktree: { loading: "読み込み中", empty: "フォルダは空です", diff --git a/src/shared/i18n/locales/pl/chat.ts b/src/shared/i18n/locales/pl/chat.ts index fa5b153c1..70920f517 100644 --- a/src/shared/i18n/locales/pl/chat.ts +++ b/src/shared/i18n/locales/pl/chat.ts @@ -88,6 +88,7 @@ export default { queued: "{{count}} wiadomość/wiadomości w kolejce — zostaną wysłane po zakończeniu pracy agenta", queuedCancel: "Usuń z kolejki", + queuedEdit: "Edytuj wiadomość w kolejce", worktree: { loading: "Ładowanie", empty: "Folder jest pusty", diff --git a/src/shared/i18n/locales/pt-BR/chat.ts b/src/shared/i18n/locales/pt-BR/chat.ts index 1050f2767..2bd6a01c7 100644 --- a/src/shared/i18n/locales/pt-BR/chat.ts +++ b/src/shared/i18n/locales/pt-BR/chat.ts @@ -82,6 +82,7 @@ export default { version: "Mostrar a versão do Hermes", }, queuedCancel: "Remover da fila", + queuedEdit: "Editar mensagem na fila", worktree: { loading: "Carregando", empty: "A pasta está vazia", diff --git a/src/shared/i18n/locales/pt-PT/chat.ts b/src/shared/i18n/locales/pt-PT/chat.ts index 819dbc9bf..edf096fbf 100644 --- a/src/shared/i18n/locales/pt-PT/chat.ts +++ b/src/shared/i18n/locales/pt-PT/chat.ts @@ -94,6 +94,7 @@ export default { queued: "{{count}} mensagem(ns) em fila — serão enviadas quando o agente terminar", queuedCancel: "Remover da fila", + queuedEdit: "Editar mensagem na fila", worktree: { loading: "A carregar", empty: "A pasta está vazia", diff --git a/src/shared/i18n/locales/tr/chat.ts b/src/shared/i18n/locales/tr/chat.ts index 40c29f531..0bf5a7c67 100644 --- a/src/shared/i18n/locales/tr/chat.ts +++ b/src/shared/i18n/locales/tr/chat.ts @@ -105,6 +105,7 @@ export default { }, queued: "{{count}} mesaj sırada — ajan işini bitirince gönderilecek", queuedCancel: "Sıradan kaldır", + queuedEdit: "Sıradaki mesajı düzenle", worktree: { loading: "Yükleniyor", empty: "Klasör boş", diff --git a/src/shared/i18n/locales/zh-CN/chat.ts b/src/shared/i18n/locales/zh-CN/chat.ts index 49d107459..a323acc8b 100644 --- a/src/shared/i18n/locales/zh-CN/chat.ts +++ b/src/shared/i18n/locales/zh-CN/chat.ts @@ -78,6 +78,7 @@ export default { version: "查看 Hermes 版本", }, queuedCancel: "从队列中移除", + queuedEdit: "编辑队列消息", worktree: { loading: "加载中", empty: "文件夹为空", diff --git a/src/shared/i18n/locales/zh-TW/chat.ts b/src/shared/i18n/locales/zh-TW/chat.ts index 337c15a67..01578405b 100644 --- a/src/shared/i18n/locales/zh-TW/chat.ts +++ b/src/shared/i18n/locales/zh-TW/chat.ts @@ -67,6 +67,7 @@ export default { }, queued: "{{count}} 則訊息已排隊 — 代理完成後將自動傳送", queuedCancel: "從佇列中移除", + queuedEdit: "編輯佇列訊息", worktree: { loading: "載入中", empty: "資料夾是空的",