Files
CodeNomad/src/components/message-item.tsx
Shantur Rathore 7be4248e20 Enable command queueing and fix message duplication issues
- Remove sending() state blocking to allow queueing multiple messages
- Add optimistic message creation with temp IDs for immediate UI feedback
- Implement QUEUED badge display matching TUI behavior (accent color, bold)
- Compute queued state: user message ID > last assistant message ID
- Clear prompt input immediately before async send for better UX
- Fix duplicate messages by properly finding and replacing temp messages
- Fix duplicate parts by clearing optimistic parts when real message arrives
- Fix state mutation bugs in message.part.updated handler (immutable updates)
- Fix state mutation bugs in message.updated handler (immutable updates)

Matches TUI implementation for consistent user experience across clients.
2025-10-24 16:38:42 +01:00

82 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { For, Show } from "solid-js"
import type { Message } from "../types/message"
import MessagePart from "./message-part"
interface MessageItemProps {
message: Message
messageInfo?: any
isQueued?: boolean
}
export default function MessageItem(props: MessageItemProps) {
const isUser = () => props.message.type === "user"
const timestamp = () => {
const date = new Date(props.message.timestamp)
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
}
const errorMessage = () => {
if (!props.messageInfo?.error) return null
const error = props.messageInfo.error
if (error.name === "ProviderAuthError") {
return error.data?.message || "Authentication error"
}
if (error.name === "MessageOutputLengthError") {
return "Message output length exceeded"
}
if (error.name === "MessageAbortedError") {
return "Request was aborted"
}
if (error.name === "UnknownError") {
return error.data?.message || "Unknown error occurred"
}
return null
}
const hasContent = () => {
return props.message.parts.length > 0 || errorMessage() !== null
}
const isGenerating = () => {
return !hasContent() && props.messageInfo?.time?.completed === 0
}
return (
<div class={`message-item ${isUser() ? "user" : "assistant"}`}>
<div class="message-header">
<span class="message-sender">{isUser() ? "You" : "Assistant"}</span>
<span class="message-timestamp">{timestamp()}</span>
</div>
<div class="message-content">
<Show when={props.isQueued && isUser()}>
<div class="message-queued-badge">QUEUED</div>
</Show>
<Show when={errorMessage()}>
<div class="message-error-block"> {errorMessage()}</div>
</Show>
<Show when={isGenerating()}>
<div class="message-generating">
<span class="generating-spinner"></span> Generating...
</div>
</Show>
<For each={props.message.parts}>{(part) => <MessagePart part={part} />}</For>
</div>
<Show when={props.message.status === "sending"}>
<div class="message-sending">
<span class="generating-spinner"></span> Sending...
</div>
</Show>
<Show when={props.message.status === "error"}>
<div class="message-error"> Message failed to send</div>
</Show>
</div>
)
}