|
1 | 1 | "use client" |
2 | 2 |
|
3 | 3 | import { useState, useEffect, useCallback, useRef, useMemo } from "react" |
| 4 | +import { useQuery } from "@tanstack/react-query" |
| 5 | +import { $fetch } from "@lib/api" |
4 | 6 | import { useQueryState } from "nuqs" |
5 | 7 | import type { UIMessage } from "@ai-sdk/react" |
6 | 8 | import { motion } from "motion/react" |
@@ -49,83 +51,7 @@ import { generateId } from "@lib/generate-id" |
49 | 51 | import { useViewMode } from "@/lib/view-mode-context" |
50 | 52 | import { threadParam } from "@/lib/search-params" |
51 | 53 | import { AUTO_CHAT_SPACE_ID } from "@/lib/chat-auto-space" |
52 | | - |
53 | | -const DEFAULT_CHAT_PROMPTS = [ |
54 | | - "What do you know about me?", |
55 | | - "What have I been working on lately?", |
56 | | - "What themes keep showing up in my memories?", |
57 | | -] |
58 | | - |
59 | | -const chatEmptyCardClass = cn( |
60 | | - "flex min-h-[76px] flex-col justify-between rounded-lg border border-[#2B3038] bg-[#14161A]/95 p-3 text-left md:min-h-[88px]", |
61 | | - "shadow-[0_18px_50px_rgba(0,0,0,0.32),inset_0_1px_0_rgba(255,255,255,0.04)]", |
62 | | - "transition-colors hover:border-[#3374FF]/55 hover:bg-[#1A1F26] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#3374FF]/70", |
63 | | -) |
64 | | - |
65 | | -function ChatEmptyStatePlaceholder({ |
66 | | - onSuggestionClick, |
67 | | - suggestions = DEFAULT_CHAT_PROMPTS, |
68 | | -}: { |
69 | | - onSuggestionClick: (suggestion: string) => void |
70 | | - suggestions?: string[] |
71 | | -}) { |
72 | | - const promptCards = suggestions.slice(0, 3) |
73 | | - |
74 | | - return ( |
75 | | - <div |
76 | | - id="chat-empty-state" |
77 | | - className="relative flex min-h-full items-center justify-center overflow-hidden px-0 py-6 md:px-3" |
78 | | - > |
79 | | - <div |
80 | | - className="pointer-events-none absolute inset-x-[-1rem] inset-y-0 bg-[radial-gradient(circle_at_center,rgba(105,167,240,0.28)_1px,transparent_1px)] bg-size-[32px_32px] opacity-80 mask-[radial-gradient(ellipse_at_center,black_52%,transparent_100%)]" |
81 | | - aria-hidden |
82 | | - /> |
83 | | - <div |
84 | | - className="pointer-events-none absolute inset-x-[-1rem] bottom-0 h-2/3 bg-[radial-gradient(ellipse_at_bottom,rgba(20,65,255,0.42),transparent_68%)]" |
85 | | - aria-hidden |
86 | | - /> |
87 | | - <div className="relative z-10 flex w-full max-w-xl flex-col items-center text-center"> |
88 | | - <NovaOrb size={52} className="mb-3 blur-[1.5px]!" /> |
89 | | - <h2 |
90 | | - className={cn( |
91 | | - "mb-1 max-w-[420px] text-[24px] font-medium leading-[1.12] tracking-normal text-white md:text-[30px]", |
92 | | - dmSansClassName(), |
93 | | - )} |
94 | | - > |
95 | | - Nova knows you. |
96 | | - </h2> |
97 | | - <p |
98 | | - className={cn( |
99 | | - "mb-4 max-w-[420px] text-[14px] leading-5 text-[#8B8B8B] md:text-[15px]", |
100 | | - dmSansClassName(), |
101 | | - )} |
102 | | - > |
103 | | - <span className="text-[#FAFAFA]"> |
104 | | - Your personal memories are all here. |
105 | | - </span>{" "} |
106 | | - Chat with supermemory and ask about... |
107 | | - </p> |
108 | | - <div className="mb-3 grid w-full grid-cols-1 gap-2.5 sm:grid-cols-3"> |
109 | | - {promptCards.map((suggestion, index) => ( |
110 | | - <button |
111 | | - key={suggestion} |
112 | | - type="button" |
113 | | - onClick={() => onSuggestionClick(suggestion)} |
114 | | - className={chatEmptyCardClass} |
115 | | - > |
116 | | - <span className="flex size-5 items-center justify-center rounded-full border border-[#3374FF]/35 bg-[#071B3A] text-[11px] font-medium text-[#4BA0FA]"> |
117 | | - {index + 1} |
118 | | - </span> |
119 | | - <span className="mt-2 line-clamp-3 text-[13px] font-medium leading-[18px] text-white md:text-[14px] md:leading-5"> |
120 | | - {suggestion} |
121 | | - </span> |
122 | | - </button> |
123 | | - ))} |
124 | | - </div> |
125 | | - </div> |
126 | | - </div> |
127 | | - ) |
128 | | -} |
| 54 | +import { ChatEmptyStatePlaceholder } from "./chat-empty-state" |
129 | 55 |
|
130 | 56 | export function ChatLaunchFab({ |
131 | 57 | onOpen, |
@@ -243,6 +169,43 @@ export function ChatSidebar({ |
243 | 169 | }), |
244 | 170 | [chatProject, allProjects], |
245 | 171 | ) |
| 172 | + const isAutoChatSpace = chatProject === AUTO_CHAT_SPACE_ID |
| 173 | + const { data: chatSpaceMemoryCount } = useQuery({ |
| 174 | + queryKey: ["chat-empty-space-count", chatProject], |
| 175 | + queryFn: async (): Promise<number> => { |
| 176 | + const response = await $fetch("@post/documents/documents", { |
| 177 | + body: { |
| 178 | + page: 1, |
| 179 | + limit: 1, |
| 180 | + sort: "createdAt", |
| 181 | + order: "desc", |
| 182 | + containerTags: [chatProject], |
| 183 | + }, |
| 184 | + disableValidation: true, |
| 185 | + }) |
| 186 | + if (response.error) return 0 |
| 187 | + const data = response.data as { |
| 188 | + pagination?: { totalItems?: number } |
| 189 | + } | null |
| 190 | + return data?.pagination?.totalItems ?? 0 |
| 191 | + }, |
| 192 | + staleTime: 30 * 1000, |
| 193 | + enabled: !!chatProject && !isAutoChatSpace, |
| 194 | + }) |
| 195 | + const emptyStateSubtitle = useMemo(() => { |
| 196 | + if (isAutoChatSpace) { |
| 197 | + return "Picks the best space for each question" |
| 198 | + } |
| 199 | + if (chatSpaceMemoryCount === undefined) { |
| 200 | + return `Grounded in ${chatSpaceLabel}` |
| 201 | + } |
| 202 | + if (chatSpaceMemoryCount === 0) { |
| 203 | + return `Nothing in ${chatSpaceLabel} yet` |
| 204 | + } |
| 205 | + const countLabel = chatSpaceMemoryCount.toLocaleString() |
| 206 | + const memoryWord = chatSpaceMemoryCount === 1 ? "memory" : "memories" |
| 207 | + return `${countLabel} ${memoryWord} in ${chatSpaceLabel}` |
| 208 | + }, [isAutoChatSpace, chatSpaceLabel, chatSpaceMemoryCount]) |
246 | 209 | const { viewMode } = useViewMode() |
247 | 210 | const { user: _user } = useAuth() |
248 | 211 | const [threadId, setThreadId] = useQueryState("thread", threadParam) |
@@ -1011,6 +974,7 @@ export function ChatSidebar({ |
1011 | 974 | <ChatEmptyStatePlaceholder |
1012 | 975 | onSuggestionClick={handleSuggestedQuestion} |
1013 | 976 | suggestions={emptyStateSuggestions} |
| 977 | + subtitle={emptyStateSubtitle} |
1014 | 978 | /> |
1015 | 979 | )} |
1016 | 980 | <div |
@@ -1246,3 +1210,4 @@ export function ChatSidebar({ |
1246 | 1210 | } |
1247 | 1211 |
|
1248 | 1212 | export { HomeChatComposer } from "./home-chat-composer" |
| 1213 | +export { ChatEmptyStatePlaceholder } from "./chat-empty-state" |
0 commit comments