mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
fix final bugs
This commit is contained in:
parent
41ce469901
commit
2a2b2660bd
6 changed files with 108 additions and 62 deletions
|
|
@ -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={
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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}{" "}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue