mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
Polish chat edit and queue UI
This commit is contained in:
parent
80a237a14e
commit
4ca26ef14e
2 changed files with 67 additions and 118 deletions
|
|
@ -1255,8 +1255,13 @@ export function ChatSidebar({
|
|||
</div>
|
||||
))}
|
||||
{(status === "submitted" || status === "streaming") && (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<SuperLoader label="Thinking…" />
|
||||
{messageQueue.length > 0 && (
|
||||
<span className="rounded-full border border-white/8 bg-white/[0.03] px-2 py-0.5 text-[10px] font-medium tracking-[0.12em] text-white/32">
|
||||
QUEUED
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -1329,66 +1334,6 @@ export function ChatSidebar({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{messageQueue.length > 0 && (
|
||||
<div
|
||||
className={cn(
|
||||
"mb-2 rounded-xl border border-[#263348]/80 bg-[#0B1119]/95 p-2 shadow-[0_14px_36px_rgba(0,0,0,0.34)]",
|
||||
isPageDesktop ? "mx-0" : "mx-4",
|
||||
)}
|
||||
>
|
||||
<div className="mb-1.5 flex items-center justify-between gap-3 px-1">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<div className="flex h-5 min-w-5 items-center justify-center rounded-full bg-[#267BF1]/15 px-1.5 text-[11px] font-medium text-[#8DBDFF]">
|
||||
{messageQueue.length}
|
||||
</div>
|
||||
<p className="truncate text-xs font-medium text-white/75">
|
||||
Queued messages
|
||||
</p>
|
||||
</div>
|
||||
<p className="shrink-0 text-[11px] text-white/38">
|
||||
{messageQueue.length}/{CHAT_QUEUE_LIMIT}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex max-h-36 flex-col gap-1 overflow-y-auto pr-1">
|
||||
{messageQueue.map((queued, index) => {
|
||||
const model = modelNames[queued.model]
|
||||
const reasoningLabel =
|
||||
queued.reasoningEffort === "thinking" ? "Thinking" : "Instant"
|
||||
return (
|
||||
<div
|
||||
key={queued.id}
|
||||
className="group flex min-h-10 items-center gap-2 rounded-lg border border-transparent bg-white/[0.035] px-2 py-1.5 transition-colors hover:border-[#263348]/90 hover:bg-white/[0.055]"
|
||||
>
|
||||
<div className="flex size-5 shrink-0 items-center justify-center rounded-full bg-[#101822] text-[11px] text-white/45">
|
||||
{index + 1}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm leading-5 text-white/88">
|
||||
{queued.text}
|
||||
</p>
|
||||
<p className="truncate text-[11px] leading-4 text-white/38">
|
||||
{model.name} {model.version} - {reasoningLabel}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setMessageQueue((prev) =>
|
||||
prev.filter((item) => item.id !== queued.id),
|
||||
)
|
||||
}
|
||||
className="flex size-7 shrink-0 items-center justify-center rounded-md text-white/35 transition-colors hover:bg-white/10 hover:text-white/80"
|
||||
aria-label="Remove queued message"
|
||||
>
|
||||
<XIcon className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
"use client"
|
||||
|
||||
import { memo, useEffect, useRef, useState } from "react"
|
||||
import { AnimatePresence, motion } from "motion/react"
|
||||
import { Copy, Check, PencilIcon, PencilOffIcon } from "lucide-react"
|
||||
import type { UIMessage } from "@ai-sdk/react"
|
||||
import ChatModelSelector from "../model-selector"
|
||||
import { ReasoningSelector } from "../reasoning-selector"
|
||||
import { SendButton } from "../input/actions"
|
||||
import {
|
||||
getDefaultReasoningEffort,
|
||||
type ModelId,
|
||||
|
|
@ -70,64 +72,66 @@ export const UserMessage = memo(function UserMessage({
|
|||
|
||||
return (
|
||||
<div className="flex flex-col items-end w-full">
|
||||
{isEditing ? (
|
||||
<div className="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)]">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={draft}
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
submitEdit()
|
||||
}
|
||||
}}
|
||||
className="min-h-20 w-full resize-none bg-transparent p-2 text-sm text-white outline-none placeholder:text-white/30"
|
||||
/>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<ChatModelSelector
|
||||
selectedModel={editModel}
|
||||
onModelChange={handleEditModelChange}
|
||||
minimal
|
||||
dropdownDirection="down"
|
||||
/>
|
||||
<ReasoningSelector
|
||||
value={editReasoningEffort}
|
||||
onChange={setEditReasoningEffort}
|
||||
dropdownDirection="down"
|
||||
/>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{isEditing ? (
|
||||
<motion.div
|
||||
key="edit"
|
||||
layout
|
||||
initial={{ height: 44, opacity: 0, scale: 0.98 }}
|
||||
animate={{ height: "auto", opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.98 }}
|
||||
transition={{ duration: 0.22, ease: "easeOut" }}
|
||||
className="w-full max-w-[88%] origin-top-right overflow-hidden rounded-[14px] border border-[#293952]/70 bg-[#0D121A] p-2 shadow-[0_16px_36px_rgba(0,0,0,0.28)]"
|
||||
>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={draft}
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
submitEdit()
|
||||
}
|
||||
}}
|
||||
className="min-h-20 w-full resize-none bg-transparent p-2 text-sm text-white outline-none placeholder:text-white/30"
|
||||
/>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<ChatModelSelector
|
||||
selectedModel={editModel}
|
||||
onModelChange={handleEditModelChange}
|
||||
minimal
|
||||
dropdownDirection="down"
|
||||
/>
|
||||
<ReasoningSelector
|
||||
value={editReasoningEffort}
|
||||
onChange={setEditReasoningEffort}
|
||||
dropdownDirection="down"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<SendButton
|
||||
onClick={submitEdit}
|
||||
disabled={!draft.trim()}
|
||||
disabledTooltip="Type a message to send"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={submitEdit}
|
||||
disabled={!draft.trim()}
|
||||
className="rounded-md bg-[#E052A0] p-2 transition-colors hover:bg-[#EF6FB4] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
title="Send edited message"
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 12 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Send</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"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-[#1B1F24] rounded-[12px] p-3 px-[14px] max-w-[80%]">
|
||||
<p className="text-sm text-white">{text}</p>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="message"
|
||||
layout
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.98 }}
|
||||
transition={{ duration: 0.18, ease: "easeOut" }}
|
||||
className="bg-[#1B1F24] rounded-[12px] p-3 px-[14px] max-w-[80%]"
|
||||
>
|
||||
<p className="text-sm text-white">{text}</p>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<div className="mt-1 flex max-w-full items-center justify-end gap-1">
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue