fix final bugs

This commit is contained in:
ved015 2026-06-02 21:26:39 +05:30
parent 41ce469901
commit 2a2b2660bd
6 changed files with 108 additions and 62 deletions

View file

@ -107,7 +107,7 @@ type QueuedChatMessage = {
reasoningEffort: ReasoningEffort
}
const CHAT_QUEUE_LIMIT = 5
const CHAT_QUEUE_LIMIT = 3
function normalizeModelId(value: unknown): ModelId | null {
if (typeof value !== "string") return null
@ -1274,6 +1274,7 @@ export function ChatSidebar({
? cn(
"flex flex-col space-y-3 min-h-full justify-end",
isPageDesktop || isMobile ? "pt-2" : "pt-14",
isStackedInput && "px-4",
)
: ""
}
@ -1423,8 +1424,7 @@ export function ChatSidebar({
? "Structuring response…"
: "Waiting for input…"
}
queuedMessagePreview={messageQueue[0]?.text ?? null}
queuedMessageCount={messageQueue.length}
queuedMessages={messageQueue}
showStatusStrip={showInputStatusStrip}
onExpandedChange={setIsInputExpanded}
chainOfThoughtComponent={

View file

@ -33,7 +33,7 @@ export function SendButton({
<title>Send Icon</title>
<path
d="M12 6L10.55 7.4L7 3.85L7 16L5 16L5 3.85L1.45 7.4L-4.37e-07 6L6 -2.62e-07L12 6Z"
fill="#FAFAFA"
fill="#9CA3AF"
/>
</svg>
</button>

View file

@ -1,12 +1,24 @@
"use client"
import { ChevronUpIcon } from "lucide-react"
import { BrainIcon, ChevronUpIcon, ZapIcon } from "lucide-react"
import NovaOrb from "@/components/nova/nova-orb"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import { type ReactNode, useEffect, useRef, useState } from "react"
import { motion } from "motion/react"
import { AnimatePresence, motion } from "motion/react"
import { SendButton, StopButton } from "./actions"
import {
type ModelId,
modelNames,
type ReasoningEffort,
} from "@/lib/models"
export interface QueuedChatMessagePreview {
id: string
text: string
model: ModelId
reasoningEffort: ReasoningEffort
}
interface ChatInputProps {
value: string
@ -18,8 +30,7 @@ interface ChatInputProps {
sendDisabled?: boolean
sendDisabledTooltip?: string
activeStatus?: string
queuedMessagePreview?: string | null
queuedMessageCount?: number
queuedMessages?: QueuedChatMessagePreview[]
chainOfThoughtComponent?: React.ReactNode
onExpandedChange?: (expanded: boolean) => void
/** Model + space controls on one row with send; textarea full-width above */
@ -38,8 +49,7 @@ export default function ChatInput({
sendDisabled = false,
sendDisabledTooltip,
activeStatus,
queuedMessagePreview,
queuedMessageCount = 0,
queuedMessages = [],
chainOfThoughtComponent,
onExpandedChange,
stackedToolbar,
@ -49,7 +59,7 @@ export default function ChatInput({
const [isExpanded, setIsExpanded] = useState(false)
const textareaRef = useRef<HTMLTextAreaElement>(null)
const isSendDisabled = !value.trim() || sendDisabled
const hasQueuedPreview = !!queuedMessagePreview && queuedMessageCount > 0
const hasQueuedPreview = queuedMessages.length > 0
const resolvedSendDisabledTooltip =
sendDisabled && value.trim()
? sendDisabledTooltip
@ -142,36 +152,61 @@ export default function ChatInput({
)}
</button>
{hasQueuedPreview && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
transition={{ duration: 0.18, ease: "easeOut" }}
className="overflow-hidden px-12 pr-4 pb-3"
>
<div className="flex min-w-0 items-center gap-2 rounded-lg border border-white/[0.06] bg-black/[0.16] px-2.5 py-1.5">
<span className="shrink-0 text-[10px] font-medium tracking-[0.12em] text-white/32">
QUEUED
</span>
<span
className={cn(
"min-w-0 flex-1 truncate text-xs text-[#6A7484]",
dmSansClassName(),
)}
>
{queuedMessagePreview}
</span>
{queuedMessageCount > 1 && (
<span className="shrink-0 text-[10px] text-white/28">
+{queuedMessageCount - 1}
</span>
)}
</div>
</motion.div>
<div className="flex flex-col gap-1 px-3 pr-4 pb-3">
<AnimatePresence initial={false}>
{queuedMessages.map((queued) => {
const model = modelNames[queued.model]
const ReasoningIcon =
queued.reasoningEffort === "thinking"
? BrainIcon
: ZapIcon
const reasoningLabel =
queued.reasoningEffort === "thinking"
? "Thinking"
: "Instant"
return (
<motion.div
key={queued.id}
layout
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
className="overflow-hidden"
>
<div className="flex min-w-0 items-center gap-2 px-2.5 py-1">
<span
className={cn(
"min-w-0 flex-1 truncate text-xs text-white/35",
dmSansClassName(),
)}
>
{queued.text}
</span>
<span
className={cn(
"flex shrink-0 items-center gap-1.5 text-[10px] text-white/28",
dmSansClassName(),
)}
>
<span className="truncate">
{model.name} {model.version}
</span>
<span className="text-white/18">·</span>
<ReasoningIcon className="size-3 shrink-0 text-white/30" />
<span className="truncate">{reasoningLabel}</span>
</span>
</div>
</motion.div>
)
})}
</AnimatePresence>
</div>
)}
</>
) : null}
{stackedToolbar ? (
<div className="flex flex-col gap-2 rounded-xl bg-surface-card/60 backdrop-blur-md p-2 shadow-[0_16px_48px_rgba(0,0,0,0.34)] transition-all duration-200 focus-within:ring-1 focus-within:ring-fg-primary/10">
<div className="relative z-30 flex flex-col gap-2 rounded-xl bg-surface-card/60 backdrop-blur-md p-2 shadow-[0_16px_48px_rgba(0,0,0,0.34)] transition-all duration-200 focus-within:ring-1 focus-within:ring-fg-primary/10">
<textarea
ref={textareaRef}
value={value}

View file

@ -1,7 +1,7 @@
"use client"
import { memo, useEffect, useRef, useState } from "react"
import { motion } from "motion/react"
import { AnimatePresence, motion } from "motion/react"
import { Copy, Check, PencilIcon, PencilOffIcon } from "lucide-react"
import type { UIMessage } from "@ai-sdk/react"
import { cn } from "@lib/utils"
@ -73,18 +73,16 @@ export const UserMessage = memo(function UserMessage({
return (
<div className="flex flex-col items-end w-full">
<motion.div
layout
transition={{ layout: { duration: 0.2, ease: "easeOut" } }}
className={cn(
"origin-top-right overflow-hidden",
isEditing
? "w-full max-w-[88%] rounded-[14px] border border-[#293952]/70 bg-[#0D121A] p-2 shadow-[0_16px_36px_rgba(0,0,0,0.28)]"
: "max-w-[80%] rounded-[12px] bg-[#1B1F24] p-3 px-[14px]",
)}
>
<AnimatePresence mode="popLayout" initial={false}>
{isEditing ? (
<>
<motion.div
key="edit"
initial={{ opacity: 0, scaleX: 0.4, scaleY: 0.6 }}
animate={{ opacity: 1, scaleX: 1, scaleY: 1 }}
exit={{ opacity: 0, scaleX: 0.4, scaleY: 0.6 }}
transition={{ duration: 0.55, ease: [0.22, 0.68, 0.18, 1] }}
className="relative z-20 w-full max-w-[88%] origin-right rounded-xl bg-surface-card/60 p-2 shadow-[0_16px_48px_rgba(0,0,0,0.34)] backdrop-blur-md"
>
<textarea
ref={textareaRef}
value={draft}
@ -103,12 +101,12 @@ export const UserMessage = memo(function UserMessage({
selectedModel={editModel}
onModelChange={handleEditModelChange}
minimal
dropdownDirection="down"
dropdownDirection="up"
/>
<ReasoningSelector
value={editReasoningEffort}
onChange={setEditReasoningEffort}
dropdownDirection="down"
dropdownDirection="up"
/>
</div>
<div className="flex shrink-0 items-center gap-1">
@ -119,12 +117,25 @@ export const UserMessage = memo(function UserMessage({
/>
</div>
</div>
</>
</motion.div>
) : (
<p className="text-sm text-white">{text}</p>
<motion.div
key="view"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.18, ease: "easeOut" }}
className="max-w-[80%] origin-top-right rounded-[12px] bg-[#1B1F24] p-3 px-[14px]"
>
<p className="text-sm text-white">{text}</p>
</motion.div>
)}
</motion.div>
<div className="mt-1 flex max-w-full items-center justify-end gap-1">
</AnimatePresence>
<motion.div
layout
transition={{ layout: { duration: 0.28, ease: [0.22, 1, 0.36, 1] } }}
className="mt-1 flex max-w-full items-center justify-end gap-1"
>
<button
type="button"
onClick={() => onCopy(message.id, text)}
@ -149,7 +160,7 @@ export const UserMessage = memo(function UserMessage({
<PencilIcon className="size-3.5 text-white/50" />
)}
</button>
</div>
</motion.div>
</div>
)
})

View file

@ -61,7 +61,7 @@ export default function ChatModelSelector({
<button
type="button"
className={cn(
"flex max-w-[min(100%,220px)] min-w-0 shrink cursor-pointer items-center gap-1.5 rounded-full border border-white/15 bg-black px-3 py-1.5 text-sm text-white transition-colors hover:border-white/30 hover:bg-white/5",
"flex max-w-[min(100%,220px)] min-w-0 shrink cursor-pointer items-center gap-1.5 rounded-full border border-white/15 bg-black px-3 py-1.5 text-[13px] text-white transition-colors hover:border-white/30 hover:bg-white/5",
dmSansClassName(),
)}
onClick={() => setIsOpen(!isOpen)}
@ -132,8 +132,8 @@ export default function ChatModelSelector({
<div className="min-w-0 flex-1">
<div
className={cn(
"truncate text-[15px] font-medium",
isSelected ? "text-white" : "text-white",
"truncate text-sm font-medium",
isSelected ? "text-white" : "text-white",
)}
>
{modelData.name}{" "}

View file

@ -70,7 +70,7 @@ export function ReasoningSelector({
"cursor-pointer transition-colors disabled:cursor-not-allowed disabled:opacity-50",
variant === "icon"
? "rounded p-1.5 hover:bg-white/10"
: "flex size-9 items-center justify-center gap-1.5 rounded-full border border-white/15 bg-black px-0 py-1.5 text-[13px] text-white hover:border-white/30 hover:bg-white/5 sm:size-auto sm:justify-start sm:px-2.5",
: "flex size-9 items-center justify-center gap-1.5 rounded-full border border-white/15 bg-black px-0 py-1.5 text-xs text-white hover:border-white/30 hover:bg-white/5 sm:size-auto sm:justify-start sm:px-2.5",
dmSansClassName(),
)}
title={`Reasoning: ${selectedLabel}`}
@ -122,8 +122,8 @@ export function ReasoningSelector({
<div className="min-w-0 flex-1">
<div
className={cn(
"text-[15px] font-medium",
isSelected ? "text-white" : "text-white",
"text-sm font-medium",
isSelected ? "text-white" : "text-white",
)}
>
{option.label}