mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-08 03:08:21 +00:00
feat: highlight chat shows inline reply and uses highlight as assistant's first message (#944)
## Summary Changes the highlight "Chat" button behavior: - **Before**: Clicking "Chat" immediately opened the chat view with "Tell me more about XYZ" sent as the user's first message - **After**: Clicking "Chat" reveals an inline reply input right under the highlight card. The user types their response, and when they submit: - The highlight content appears as the **assistant's first message** (prefixed with "Here is a highlight from your memories:") - The user's reply appears as the second message - A fresh chat thread is created (no overwriting existing conversations) ### Files Changed - **`apps/web/components/highlights-card.tsx`** - Added inline reply input UI with send/cancel buttons, keyboard handling (Enter/Escape), auto-focus, and state reset on item navigation or items refresh - **`apps/web/components/chat/index.tsx`** - Added `queuedHighlightContent` prop, fresh thread creation for highlight chats, deferred `setMessages` to ensure correct Chat instance targeting, and `awaitingHighlightInjectionRef` for safe reply dispatch - **`apps/web/app/(app)/page.tsx`** - Added `queuedHighlightContent` state, updated `handleHighlightsChat` to accept highlight content + user reply, cleared stale highlight content in `handleHomeChatStart` - **`apps/web/components/dashboard-view.tsx`** - Updated `onHighlightsChat` type signature - **`apps/web/components/memories-grid.tsx`** - Updated `HighlightsProps.onChat` type signature ## Testing ### TypeScript - `npx tsc --noEmit` — no new type errors (all errors are pre-existing in unrelated files) ### Biome - `bunx biome check --write` — clean on all changed files (pre-existing warnings only in dashboard-view.tsx) ### Automated Tests (via testing subagent) - **25 passed / 0 failed / 1 skipped** (26 total) - Tests covered: inline reply input visibility, keyboard handling (Enter/Escape), reply state reset on navigation, highlight content injection as assistant message, user reply dispatch, fresh thread creation, stale highlight content prevention, pagination dot handler - The testing agent discovered and fixed a race condition where `setMessages` was called before `useChat` had recreated its internal Chat object after `setFallbackChatId` — the fix defers `setMessages` to a separate effect gated on `currentChatId` matching the target - 1 test skipped (T12): Nova FAB button not separately visible from highlights card button at 1440px viewport — expected structural behavior --- **Session Details** - Session: [View Session](https://supermemory.us1.vorflux.com/agent-sessions/9cbf2106-b7b9-4d7c-b275-2a7e72417793) - Requested by: Soham Daga (soham@supermemory.com) - Address comments on this PR. Add `(aside)` to your comment to have me ignore it.
This commit is contained in:
parent
ee7951f6ba
commit
b6913a0375
6 changed files with 206 additions and 44 deletions
|
|
@ -159,6 +159,9 @@ export default function NewPage() {
|
|||
const [fullscreenInitialContent, setFullscreenInitialContent] = useState("")
|
||||
const [queuedChatSeed, setQueuedChatSeed] = useState<string | null>(null)
|
||||
const [queuedChatModel, setQueuedChatModel] = useState<ModelId | null>(null)
|
||||
const [queuedHighlightContent, setQueuedHighlightContent] = useState<
|
||||
string | null
|
||||
>(null)
|
||||
const [queuedMessageSource, setQueuedMessageSource] = useState<
|
||||
"highlight" | "home"
|
||||
>("highlight")
|
||||
|
|
@ -466,8 +469,9 @@ export default function NewPage() {
|
|||
)
|
||||
|
||||
const handleHighlightsChat = useCallback(
|
||||
(seed: string) => {
|
||||
setQueuedChatSeed(seed)
|
||||
(highlightContent: string, userReply: string) => {
|
||||
setQueuedHighlightContent(highlightContent)
|
||||
setQueuedChatSeed(userReply)
|
||||
setQueuedChatModel(null)
|
||||
setQueuedMessageSource("highlight")
|
||||
void setViewMode("chat")
|
||||
|
|
@ -477,6 +481,7 @@ export default function NewPage() {
|
|||
|
||||
const handleHomeChatStart = useCallback(
|
||||
(message: string, model: ModelId) => {
|
||||
setQueuedHighlightContent(null)
|
||||
setQueuedChatSeed(message)
|
||||
setQueuedChatModel(model)
|
||||
setQueuedMessageSource("home")
|
||||
|
|
@ -488,6 +493,7 @@ export default function NewPage() {
|
|||
const consumeQueuedChat = useCallback(() => {
|
||||
setQueuedChatSeed(null)
|
||||
setQueuedChatModel(null)
|
||||
setQueuedHighlightContent(null)
|
||||
setQueuedMessageSource("highlight")
|
||||
}, [])
|
||||
|
||||
|
|
@ -598,6 +604,7 @@ export default function NewPage() {
|
|||
if (!open) void setViewMode("dashboard")
|
||||
}}
|
||||
queuedMessage={queuedChatSeed}
|
||||
queuedHighlightContent={queuedHighlightContent}
|
||||
onConsumeQueuedMessage={consumeQueuedChat}
|
||||
queuedMessageSource={queuedMessageSource}
|
||||
initialSelectedModel={queuedChatModel}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ export function ChatSidebar({
|
|||
isChatOpen,
|
||||
setIsChatOpen,
|
||||
queuedMessage,
|
||||
queuedHighlightContent,
|
||||
onConsumeQueuedMessage,
|
||||
queuedMessageSource = "highlight",
|
||||
initialSelectedModel = null,
|
||||
|
|
@ -155,6 +156,7 @@ export function ChatSidebar({
|
|||
isChatOpen: boolean
|
||||
setIsChatOpen: (open: boolean) => void
|
||||
queuedMessage?: string | null
|
||||
queuedHighlightContent?: string | null
|
||||
onConsumeQueuedMessage?: () => void
|
||||
queuedMessageSource?: "highlight" | "home"
|
||||
initialSelectedModel?: ModelId | null
|
||||
|
|
@ -189,6 +191,10 @@ export function ChatSidebar({
|
|||
const messagesContainerRef = useRef<HTMLDivElement>(null)
|
||||
const isScrolledToBottomRef = useRef(true)
|
||||
const sentQueuedMessageRef = useRef<string | null>(null)
|
||||
const pendingHighlightReplyRef = useRef<string | null>(null)
|
||||
const awaitingHighlightInjectionRef = useRef(false)
|
||||
const pendingHighlightMessageRef = useRef<UIMessage[] | null>(null)
|
||||
const targetHighlightChatIdRef = useRef<string | null>(null)
|
||||
const { selectedProject } = useProject()
|
||||
const { allProjects } = useContainerTags()
|
||||
const selectedProjectRef = useRef(selectedProject)
|
||||
|
|
@ -526,14 +532,48 @@ export function ChatSidebar({
|
|||
return
|
||||
}
|
||||
sentQueuedMessageRef.current = queuedMessage
|
||||
if (!threadId) setThreadId(fallbackChatId)
|
||||
analytics.chatMessageSent({ source: queuedMessageSource })
|
||||
sendMessage({ text: queuedMessage })
|
||||
|
||||
if (queuedHighlightContent) {
|
||||
// Start a fresh thread for highlight-based chats to avoid overwriting existing conversations
|
||||
const newChatId = generateId()
|
||||
chatIdRef.current = newChatId
|
||||
setThreadId(null)
|
||||
setFallbackChatId(newChatId)
|
||||
|
||||
// Store the highlight message and user reply in refs.
|
||||
// We cannot call setMessages here because setFallbackChatId above triggers
|
||||
// useChat to recreate its internal Chat object (new id → new Chat), which
|
||||
// resets messages to []. Instead, pendingHighlightMessageRef is read by a
|
||||
// separate useEffect that fires after currentChatId has settled, ensuring
|
||||
// setMessages is called on the correct, freshly-created Chat instance.
|
||||
// targetHighlightChatIdRef ensures we only call setMessages once the new
|
||||
// Chat instance (with id=newChatId) is active, not the old one.
|
||||
pendingHighlightReplyRef.current = queuedMessage
|
||||
awaitingHighlightInjectionRef.current = true
|
||||
targetHighlightChatIdRef.current = newChatId
|
||||
pendingHighlightMessageRef.current = [
|
||||
{
|
||||
id: generateId(),
|
||||
role: "assistant" as const,
|
||||
parts: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `Here is a highlight from your memories:\n\n${queuedHighlightContent}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
} else {
|
||||
if (!threadId) setThreadId(fallbackChatId)
|
||||
sendMessage({ text: queuedMessage })
|
||||
}
|
||||
onConsumeQueuedMessage?.()
|
||||
}
|
||||
}, [
|
||||
isChatOpen,
|
||||
queuedMessage,
|
||||
queuedHighlightContent,
|
||||
queuedMessageSource,
|
||||
initialSelectedModel,
|
||||
selectedModel,
|
||||
|
|
@ -545,6 +585,41 @@ export function ChatSidebar({
|
|||
threadId,
|
||||
])
|
||||
|
||||
// Inject the pending highlight assistant message once the new Chat instance is ready.
|
||||
// This effect must run AFTER the currentChatId change has been committed and useChat
|
||||
// has recreated its internal Chat object, so that setMessages targets the correct instance.
|
||||
// We gate on currentChatId === targetHighlightChatIdRef to ensure we call setMessages
|
||||
// only when useChat's internal Chat has the new id (not the old one from before setFallbackChatId).
|
||||
useEffect(() => {
|
||||
if (
|
||||
awaitingHighlightInjectionRef.current &&
|
||||
pendingHighlightMessageRef.current &&
|
||||
targetHighlightChatIdRef.current &&
|
||||
currentChatId === targetHighlightChatIdRef.current
|
||||
) {
|
||||
const msgs = pendingHighlightMessageRef.current
|
||||
pendingHighlightMessageRef.current = null
|
||||
targetHighlightChatIdRef.current = null
|
||||
setMessages(msgs)
|
||||
}
|
||||
}, [currentChatId, setMessages])
|
||||
|
||||
// Send pending highlight reply once the injected assistant message is committed
|
||||
useEffect(() => {
|
||||
if (
|
||||
awaitingHighlightInjectionRef.current &&
|
||||
pendingHighlightReplyRef.current &&
|
||||
messages.length >= 1 &&
|
||||
messages[0]?.role === "assistant" &&
|
||||
status === "ready"
|
||||
) {
|
||||
awaitingHighlightInjectionRef.current = false
|
||||
const reply = pendingHighlightReplyRef.current
|
||||
pendingHighlightReplyRef.current = null
|
||||
sendMessage({ text: reply })
|
||||
}
|
||||
}, [messages, sendMessage, status])
|
||||
|
||||
// Reset the sent message ref when queued message is consumed
|
||||
useEffect(() => {
|
||||
if (!queuedMessage) {
|
||||
|
|
|
|||
|
|
@ -627,7 +627,7 @@ export function DashboardView({
|
|||
onNavigateToMemories: () => void
|
||||
onNavigateToGraph: () => void
|
||||
onOpenDocument: (document: DocumentWithMemories) => void
|
||||
onHighlightsChat: (seed: string) => void
|
||||
onHighlightsChat: (highlightContent: string, userReply: string) => void
|
||||
onHighlightsShowRelated: (query: string) => void
|
||||
onResetHighlights: () => void
|
||||
memoryOfDay: MemoryOfDay | null
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { useState, useCallback } from "react"
|
||||
import { useState, useCallback, useRef, useEffect } from "react"
|
||||
import { cn } from "@lib/utils"
|
||||
import { dmSansClassName } from "@/lib/fonts"
|
||||
import {
|
||||
|
|
@ -9,6 +9,8 @@ import {
|
|||
Info,
|
||||
MessageSquare,
|
||||
Link2,
|
||||
ArrowUp,
|
||||
X,
|
||||
} from "lucide-react"
|
||||
import { Logo } from "@ui/assets/Logo"
|
||||
import { analytics } from "@/lib/analytics"
|
||||
|
|
@ -26,7 +28,7 @@ export interface HighlightItem {
|
|||
|
||||
interface HighlightsCardProps {
|
||||
items: HighlightItem[]
|
||||
onChat: (seed: string) => void
|
||||
onChat: (highlightContent: string, userReply: string) => void
|
||||
onShowRelated: (query: string) => void
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
|
@ -50,7 +52,7 @@ function renderContent(content: string, format: HighlightFormat) {
|
|||
}
|
||||
case "quote":
|
||||
return (
|
||||
<p className="text-[12px] leading-normal italic border-l-2 border-[#4BA0FA] pl-2">
|
||||
<p className="text-[12px] leading-normal italic border-l-2 border-brand-accent pl-2">
|
||||
"{content}"
|
||||
</p>
|
||||
)
|
||||
|
|
@ -68,26 +70,64 @@ export function HighlightsCard({
|
|||
isLoading = false,
|
||||
}: HighlightsCardProps) {
|
||||
const [activeIndex, setActiveIndex] = useState(0)
|
||||
const [isReplyOpen, setIsReplyOpen] = useState(false)
|
||||
const [replyText, setReplyText] = useState("")
|
||||
const replyInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const currentItem = items[activeIndex]
|
||||
|
||||
useEffect(() => {
|
||||
if (isReplyOpen) replyInputRef.current?.focus()
|
||||
}, [isReplyOpen])
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: intentionally re-run when items changes
|
||||
useEffect(() => {
|
||||
setIsReplyOpen(false)
|
||||
setReplyText("")
|
||||
}, [items])
|
||||
|
||||
const handlePrev = useCallback(() => {
|
||||
setActiveIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1))
|
||||
setIsReplyOpen(false)
|
||||
setReplyText("")
|
||||
}, [items.length])
|
||||
|
||||
const handleNext = useCallback(() => {
|
||||
setActiveIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0))
|
||||
setIsReplyOpen(false)
|
||||
setReplyText("")
|
||||
}, [items.length])
|
||||
|
||||
const handleChat = useCallback(() => {
|
||||
const handleChatClick = useCallback(() => {
|
||||
if (!currentItem) return
|
||||
analytics.highlightClicked({
|
||||
highlight_id: currentItem.id,
|
||||
action: "chat",
|
||||
})
|
||||
const seed = `Tell me more about "${currentItem.title}"`
|
||||
onChat(seed)
|
||||
}, [currentItem, onChat])
|
||||
setIsReplyOpen(true)
|
||||
}, [currentItem])
|
||||
|
||||
const handleReplySubmit = useCallback(() => {
|
||||
if (!currentItem || !replyText.trim()) return
|
||||
const highlightContent = `${currentItem.title}\n\n${currentItem.content}`
|
||||
onChat(highlightContent, replyText.trim())
|
||||
setIsReplyOpen(false)
|
||||
setReplyText("")
|
||||
}, [currentItem, replyText, onChat])
|
||||
|
||||
const handleReplyKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
handleReplySubmit()
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
setIsReplyOpen(false)
|
||||
setReplyText("")
|
||||
}
|
||||
},
|
||||
[handleReplySubmit],
|
||||
)
|
||||
|
||||
const handleShowRelated = useCallback(() => {
|
||||
if (!currentItem) return
|
||||
|
|
@ -107,18 +147,18 @@ export function HighlightsCard({
|
|||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="size-[14px] rounded-full bg-[#1A2030] animate-pulse" />
|
||||
<div className="h-2 w-20 rounded bg-[#1A2030] animate-pulse" />
|
||||
<div className="size-[14px] rounded-full bg-surface-skeleton animate-pulse" />
|
||||
<div className="h-2 w-20 rounded bg-surface-skeleton animate-pulse" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="h-2.5 w-2/5 rounded bg-[#1A2030] animate-pulse" />
|
||||
<div className="h-2 w-full rounded bg-[#1A2030] animate-pulse" />
|
||||
<div className="h-2 w-[85%] rounded bg-[#1A2030] animate-pulse" />
|
||||
<div className="h-2 w-[65%] rounded bg-[#1A2030] animate-pulse" />
|
||||
<div className="h-2.5 w-2/5 rounded bg-surface-skeleton animate-pulse" />
|
||||
<div className="h-2 w-full rounded bg-surface-skeleton animate-pulse" />
|
||||
<div className="h-2 w-[85%] rounded bg-surface-skeleton animate-pulse" />
|
||||
<div className="h-2 w-[65%] rounded bg-surface-skeleton animate-pulse" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-[26px] w-14 rounded-lg bg-[#1A2030] animate-pulse" />
|
||||
<div className="h-[26px] w-16 rounded-lg bg-[#1A2030] animate-pulse" />
|
||||
<div className="h-[26px] w-14 rounded-lg bg-surface-skeleton animate-pulse" />
|
||||
<div className="h-[26px] w-16 rounded-lg bg-surface-skeleton animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -136,10 +176,10 @@ export function HighlightsCard({
|
|||
<div className="flex items-center gap-1">
|
||||
<Logo className="size-[14px]" />
|
||||
<div className="flex items-center gap-0.5">
|
||||
<span className="text-[10px] text-[#4BA0FA] tracking-[-0.3px]">
|
||||
<span className="text-[10px] text-brand-accent tracking-[-0.3px]">
|
||||
powered by
|
||||
</span>
|
||||
<span className="text-[10px] text-[#4BA0FA] font-medium tracking-[-0.3px]">
|
||||
<span className="text-[10px] text-brand-accent font-medium tracking-[-0.3px]">
|
||||
supermemory
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -165,10 +205,10 @@ export function HighlightsCard({
|
|||
<div className="flex items-center gap-1">
|
||||
<Logo className="size-[14px]" />
|
||||
<div className="flex items-center gap-0.5">
|
||||
<span className="text-[10px] text-[#4BA0FA] tracking-[-0.3px]">
|
||||
<span className="text-[10px] text-brand-accent tracking-[-0.3px]">
|
||||
powered by
|
||||
</span>
|
||||
<span className="text-[10px] text-[#4BA0FA] font-medium tracking-[-0.3px]">
|
||||
<span className="text-[10px] text-brand-accent font-medium tracking-[-0.3px]">
|
||||
supermemory
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -185,33 +225,61 @@ export function HighlightsCard({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{isReplyOpen && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="flex-1 flex items-center bg-pill-bg border border-pill-border rounded-full px-3 py-1 gap-1.5 focus-within:border-pill-border-active transition-colors">
|
||||
<input
|
||||
ref={replyInputRef}
|
||||
type="text"
|
||||
value={replyText}
|
||||
onChange={(e) => setReplyText(e.target.value)}
|
||||
onKeyDown={handleReplyKeyDown}
|
||||
placeholder={`Ask Nova about "${currentItem.title.length > 36 ? `${currentItem.title.slice(0, 36)}…` : currentItem.title}"`}
|
||||
className="flex-1 bg-transparent text-[11px] text-fg-primary placeholder:text-fg-subtle outline-none min-w-0"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleReplySubmit}
|
||||
disabled={!replyText.trim()}
|
||||
className="shrink-0 flex items-center justify-center text-fg-subtle hover:text-fg-primary disabled:opacity-30 disabled:hover:text-fg-subtle cursor-pointer disabled:cursor-default transition-colors"
|
||||
aria-label="Send reply"
|
||||
>
|
||||
<ArrowUp className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsReplyOpen(false)
|
||||
setReplyText("")
|
||||
}}
|
||||
className="shrink-0 text-fg-subtle hover:text-fg-primary transition-colors cursor-pointer"
|
||||
aria-label="Cancel reply"
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between w-full gap-2">
|
||||
<div id="highlights-actions" className="flex gap-2 items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleChat}
|
||||
className="bg-[#182333] border border-surface-border rounded-[8px] px-2 py-1.5 flex items-center gap-1.5 cursor-pointer relative"
|
||||
style={{
|
||||
boxShadow: "0 4px 20px 0 rgba(0, 0, 0, 0.25)",
|
||||
}}
|
||||
onClick={handleChatClick}
|
||||
className="rounded-full border border-pill-border bg-pill-bg px-2.5 py-1 text-[11px] text-fg-secondary hover:bg-pill-bg-active hover:border-pill-border-active hover:text-fg-primary transition-colors flex items-center gap-1.5 cursor-pointer"
|
||||
aria-label="Chat with Nova"
|
||||
>
|
||||
<MessageSquare className="size-3.5 text-fg-primary" />
|
||||
<span className="text-[11px] text-fg-primary">Chat</span>
|
||||
<div className="absolute inset-0 pointer-events-none rounded-[inherit] shadow-[inset_1px_1px_1px_0_rgba(255,255,255,0.1)]" />
|
||||
<MessageSquare className="size-3.5" />
|
||||
Chat
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleShowRelated}
|
||||
className="bg-[#182333] border border-surface-border rounded-[8px] px-2 py-1.5 flex items-center gap-1.5 cursor-pointer relative"
|
||||
style={{
|
||||
boxShadow: "0 4px 20px 0 rgba(0, 0, 0, 0.25)",
|
||||
}}
|
||||
className="rounded-full border border-pill-border bg-pill-bg px-2.5 py-1 text-[11px] text-fg-secondary hover:bg-pill-bg-active hover:border-pill-border-active hover:text-fg-primary transition-colors flex items-center gap-1.5 cursor-pointer"
|
||||
aria-label="Show related"
|
||||
>
|
||||
<Link2 className="size-3.5 text-fg-primary" />
|
||||
<span className="text-[11px] text-fg-primary">Related</span>
|
||||
<div className="absolute inset-0 pointer-events-none rounded-[inherit] shadow-[inset_1px_1px_1px_0_rgba(255,255,255,0.1)]" />
|
||||
<Link2 className="size-3.5" />
|
||||
Related
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -220,7 +288,7 @@ export function HighlightsCard({
|
|||
<button
|
||||
type="button"
|
||||
onClick={handlePrev}
|
||||
className="text-fg-subtle hover:text-white transition-colors cursor-pointer"
|
||||
className="text-fg-subtle hover:text-fg-primary transition-colors cursor-pointer"
|
||||
aria-label="Previous item"
|
||||
>
|
||||
<ChevronLeft className="size-4" />
|
||||
|
|
@ -230,11 +298,15 @@ export function HighlightsCard({
|
|||
<button
|
||||
key={idx}
|
||||
type="button"
|
||||
onClick={() => setActiveIndex(idx)}
|
||||
onClick={() => {
|
||||
setActiveIndex(idx)
|
||||
setIsReplyOpen(false)
|
||||
setReplyText("")
|
||||
}}
|
||||
className={cn(
|
||||
"rounded-full transition-all cursor-pointer",
|
||||
idx === activeIndex
|
||||
? "w-4 h-1.5 bg-[#4BA0FA]"
|
||||
? "w-4 h-1.5 bg-brand-accent"
|
||||
: "size-1.5 bg-fg-subtle hover:bg-fg-secondary",
|
||||
)}
|
||||
aria-label={`Go to item ${idx + 1}`}
|
||||
|
|
@ -244,7 +316,7 @@ export function HighlightsCard({
|
|||
<button
|
||||
type="button"
|
||||
onClick={handleNext}
|
||||
className="text-fg-subtle hover:text-white transition-colors cursor-pointer"
|
||||
className="text-fg-subtle hover:text-fg-primary transition-colors cursor-pointer"
|
||||
aria-label="Next item"
|
||||
>
|
||||
<ChevronRight className="size-4" />
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ interface QuickNoteProps {
|
|||
|
||||
interface HighlightsProps {
|
||||
items: HighlightItem[]
|
||||
onChat: (seed: string) => void
|
||||
onChat: (highlightContent: string, userReply: string) => void
|
||||
onShowRelated: (query: string) => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,16 @@
|
|||
--color-surface-base: #0b1119;
|
||||
--color-surface-card: #101822;
|
||||
--color-surface-hover: #131b28;
|
||||
--color-surface-skeleton: #1a2030;
|
||||
--color-surface-border: #263348;
|
||||
|
||||
--color-pill-bg: #0d121a;
|
||||
--color-pill-bg-active: #00173c;
|
||||
--color-pill-border: #161f2c;
|
||||
--color-pill-border-active: #2261ca33;
|
||||
|
||||
--color-brand-accent: #4ba0fa;
|
||||
|
||||
--animate-file-upload-grow: file-upload-grow 6s cubic-bezier(0.22, 1, 0.36, 1)
|
||||
forwards;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue