loading indicator, growing message box

This commit is contained in:
2026-02-14 21:07:31 -08:00
parent cd449a30fa
commit 0c892d0ffa
2 changed files with 35 additions and 8 deletions

View File

@@ -272,6 +272,14 @@ export default function App() {
const searchRunCounterRef = useRef(0);
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
useEffect(() => {
if (typeof document === "undefined") return;
const textarea = document.getElementById("composer-input") as HTMLTextAreaElement | null;
if (!textarea) return;
textarea.style.height = "0px";
textarea.style.height = `${textarea.scrollHeight}px`;
}, [composer]);
const sidebarItems = useMemo(() => buildSidebarItems(chats, searches), [chats, searches]);
const resetWorkspaceState = () => {
@@ -916,9 +924,15 @@ export default function App() {
<footer className="border-t p-3 md:p-4">
<div className="mx-auto max-w-3xl rounded-xl border bg-background p-2 shadow-sm">
<Textarea
rows={3}
id="composer-input"
rows={1}
value={composer}
onInput={(event) => setComposer(event.currentTarget.value)}
onInput={(event) => {
const textarea = event.currentTarget;
textarea.style.height = "0px";
textarea.style.height = `${textarea.scrollHeight}px`;
setComposer(textarea.value);
}}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
@@ -926,11 +940,11 @@ export default function App() {
}
}}
placeholder={isSearchMode ? "Search the web" : "Message Sybil"}
className="resize-none border-0 shadow-none focus-visible:ring-0"
className="max-h-40 min-h-0 resize-none overflow-y-auto border-0 shadow-none focus-visible:ring-0"
disabled={isSending}
/>
<div className="flex items-center justify-between px-2 pb-1">
{error ? <p className="text-xs text-red-600">{error}</p> : <span className="text-xs text-muted-foreground">{isSearchMode ? "Enter to search" : "Enter to send"}</span>}
<div className={cn("flex items-center px-2 pb-1", error ? "justify-between" : "justify-end")}>
{error ? <p className="text-xs text-red-600">{error}</p> : null}
<Button onClick={() => void handleSend()} size="icon" disabled={isSending || !composer.trim()}>
{isSearchMode ? <Search className="h-4 w-4" /> : <SendHorizontal className="h-4 w-4" />}
</Button>

View File

@@ -9,6 +9,8 @@ type Props = {
};
export function ChatMessagesPanel({ messages, isLoading, isSending }: Props) {
const hasPendingAssistant = messages.some((message) => message.id.startsWith("temp-assistant-"));
return (
<>
{isLoading && messages.length === 0 ? <p className="text-sm text-muted-foreground">Loading messages...</p> : null}
@@ -26,9 +28,9 @@ export function ChatMessagesPanel({ messages, isLoading, isSending }: Props) {
>
{isPendingAssistant ? (
<span className="inline-flex items-center gap-1" aria-label="Assistant is typing" role="status">
<span className="inline-block h-1.5 w-1.5 animate-bounce rounded-full bg-white [animation-delay:0ms]" />
<span className="inline-block h-1.5 w-1.5 animate-bounce rounded-full bg-white [animation-delay:140ms]" />
<span className="inline-block h-1.5 w-1.5 animate-bounce rounded-full bg-white [animation-delay:280ms]" />
<span className="inline-block h-1.5 w-1.5 animate-bounce rounded-full bg-muted-foreground [animation-delay:0ms]" />
<span className="inline-block h-1.5 w-1.5 animate-bounce rounded-full bg-muted-foreground [animation-delay:140ms]" />
<span className="inline-block h-1.5 w-1.5 animate-bounce rounded-full bg-muted-foreground [animation-delay:280ms]" />
</span>
) : (
<MarkdownContent
@@ -40,6 +42,17 @@ export function ChatMessagesPanel({ messages, isLoading, isSending }: Props) {
</div>
);
})}
{isSending && !hasPendingAssistant ? (
<div className="flex justify-start">
<div className="max-w-[85%] text-base leading-7 text-fuchsia-100">
<span className="inline-flex items-center gap-1" aria-label="Assistant is typing" role="status">
<span className="inline-block h-1.5 w-1.5 animate-bounce rounded-full bg-muted-foreground [animation-delay:0ms]" />
<span className="inline-block h-1.5 w-1.5 animate-bounce rounded-full bg-muted-foreground [animation-delay:140ms]" />
<span className="inline-block h-1.5 w-1.5 animate-bounce rounded-full bg-muted-foreground [animation-delay:280ms]" />
</span>
</div>
</div>
) : null}
</div>
</>
);