mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-09 03:18:04 +00:00
few more bro
This commit is contained in:
parent
5e4afbd317
commit
84fad23e71
6 changed files with 215 additions and 175 deletions
|
|
@ -82,7 +82,6 @@ export function Header({ onAddMemory }: { onAddMemory?: () => void }) {
|
|||
}, [conversations])
|
||||
|
||||
useEffect(() => {
|
||||
console.log("searchParams", searchParams.get("mcp"))
|
||||
const mcpParam = searchParams.get("mcp")
|
||||
if (mcpParam === "manual") {
|
||||
setMcpInitialClient("mcp-url")
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ function ChatEmptyStatePlaceholder() {
|
|||
<Button
|
||||
key={suggestion}
|
||||
variant="default"
|
||||
className="rounded-full text-base gap-1 h-10! border-[#2261CA33] bg-[#041127] border w-fit py-[4px] pl-[8px] pr-[12px]"
|
||||
className="rounded-full text-base gap-1 h-10! border-[#2261CA33] bg-[#041127] border w-fit py-[4px] pl-[8px] pr-[12px] hover:bg-[#0A1A3A] hover:[&_span]:text-white hover:[&_svg]:text-white transition-colors"
|
||||
>
|
||||
<SearchIcon className="size-4 text-[#267BF1]" />
|
||||
<span className="text-[#267BF1] text-[12px]">{suggestion}</span>
|
||||
|
|
@ -78,6 +78,7 @@ export function ChatSidebar() {
|
|||
const [loadingFollowUps, setLoadingFollowUps] = useState<
|
||||
Record<string, boolean>
|
||||
>({})
|
||||
const [isInputExpanded, setIsInputExpanded] = useState(false)
|
||||
const pendingFollowUpGenerations = useRef<Set<string>>(new Set())
|
||||
const { selectedProject } = useProject()
|
||||
const { setCurrentChatId } = usePersistentChat()
|
||||
|
|
@ -199,8 +200,6 @@ export function ChatSidebar() {
|
|||
}
|
||||
}, [messages, followUpQuestions, loadingFollowUps, status])
|
||||
|
||||
console.log(messages)
|
||||
|
||||
const handleSend = () => {
|
||||
if (!input.trim() || status === "submitted" || status === "streaming")
|
||||
return
|
||||
|
|
@ -284,7 +283,7 @@ export function ChatSidebar() {
|
|||
>
|
||||
<motion.button
|
||||
onClick={toggleChat}
|
||||
className="flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-medium border-[1px] border-[#17181A] text-white cursor-pointer"
|
||||
className="flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-medium border border-[#17181A] text-white cursor-pointer"
|
||||
style={{
|
||||
background: "linear-gradient(180deg, #0A0E14 0%, #05070A 100%)",
|
||||
}}
|
||||
|
|
@ -297,7 +296,7 @@ export function ChatSidebar() {
|
|||
<motion.div
|
||||
key="open"
|
||||
className={cn(
|
||||
"w-[450px] h-[calc(100vh-95px)] bg-[#05070A] backdrop-blur-md flex flex-col rounded-2xl m-4 border border-[#17181AB2] relative",
|
||||
"w-[450px] h-[calc(100vh-95px)] bg-[#05070A] backdrop-blur-md flex flex-col rounded-2xl m-4 mt-2 border border-[#17181AB2] relative",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
initial={{ x: "100px", opacity: 0 }}
|
||||
|
|
@ -354,6 +353,12 @@ export function ChatSidebar() {
|
|||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
{isInputExpanded && (
|
||||
<div
|
||||
className="absolute inset-0 z-10! rounded-2xl pointer-events-none"
|
||||
style={{ backgroundColor: "#000000E5" }}
|
||||
/>
|
||||
)}
|
||||
{messages.length === 0 && <ChatEmptyStatePlaceholder />}
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -429,24 +434,12 @@ export function ChatSidebar() {
|
|||
? "Structuring response..."
|
||||
: "Waiting for input..."
|
||||
}
|
||||
chainOfThoughtComponent={(() => {
|
||||
const lastUserMessage = [...messages]
|
||||
.reverse()
|
||||
.find((msg) => msg.role === "user")
|
||||
const lastAgentMessage = [...messages]
|
||||
.reverse()
|
||||
.find((msg) => msg.role === "assistant")
|
||||
const userMessageText =
|
||||
lastUserMessage?.parts.find((part) => part.type === "text")
|
||||
?.text ?? ""
|
||||
|
||||
return userMessageText ? (
|
||||
<ChainOfThought
|
||||
userMessage={userMessageText}
|
||||
lastAgentMessage={lastAgentMessage}
|
||||
/>
|
||||
onExpandedChange={setIsInputExpanded}
|
||||
chainOfThoughtComponent={
|
||||
messages.length > 0 ? (
|
||||
<ChainOfThought messages={messages} />
|
||||
) : null
|
||||
})()}
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -18,169 +18,198 @@ interface ReasoningStep {
|
|||
message: string
|
||||
}
|
||||
|
||||
export function ChainOfThought({
|
||||
userMessage,
|
||||
lastAgentMessage,
|
||||
}: {
|
||||
userMessage: string
|
||||
lastAgentMessage: UIMessage | undefined
|
||||
}) {
|
||||
export function ChainOfThought({ messages }: { messages: UIMessage[] }) {
|
||||
const { user } = useAuth()
|
||||
|
||||
const reasoningSteps: ReasoningStep[] = []
|
||||
if (lastAgentMessage) {
|
||||
lastAgentMessage.parts.forEach((part) => {
|
||||
if (part.type === "tool-searchMemories") {
|
||||
if (
|
||||
part.state === "input-available" ||
|
||||
part.state === "input-streaming"
|
||||
) {
|
||||
reasoningSteps.push({
|
||||
type: part.type,
|
||||
state: part.state,
|
||||
message: "Searching memories...",
|
||||
})
|
||||
} else if (part.state === "output-available") {
|
||||
reasoningSteps.push({
|
||||
type: part.type,
|
||||
state: part.state,
|
||||
message: "Found relevant memories",
|
||||
})
|
||||
} else if (part.state === "output-error") {
|
||||
reasoningSteps.push({
|
||||
type: part.type,
|
||||
state: part.state,
|
||||
message: "Error searching memories",
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// Group messages into user-assistant pairs
|
||||
const messagePairs: Array<{
|
||||
userMessage: UIMessage
|
||||
agentMessage?: UIMessage
|
||||
}> = []
|
||||
|
||||
const memoryResults: MemoryResult[] = []
|
||||
if (lastAgentMessage) {
|
||||
lastAgentMessage.parts.forEach((part) => {
|
||||
if (
|
||||
part.type === "tool-searchMemories" &&
|
||||
part.state === "output-available"
|
||||
) {
|
||||
const output = part.output as { results?: MemoryResult[] } | undefined
|
||||
const results = Array.isArray(output?.results) ? output.results : []
|
||||
memoryResults.push(...results)
|
||||
}
|
||||
})
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const message = messages[i]
|
||||
if (message.role === "user") {
|
||||
// Find the next assistant message after this user message
|
||||
const agentMessage = messages
|
||||
.slice(i + 1)
|
||||
.find((msg) => msg.role === "assistant")
|
||||
messagePairs.push({
|
||||
userMessage: message,
|
||||
agentMessage,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="m-3 mb-0 space-y-3 relative z-10">
|
||||
<div className="absolute left-[11px] top-0 bottom-0 w-[1px] bg-[#151F31] self-stretch mb-0 z-[-10]" />
|
||||
<div className="absolute left-[11px] top-0 bottom-0 w-px bg-[#151F31] self-stretch mb-0 -z-10" />
|
||||
|
||||
<div className="flex items-start gap-3 text-[#525D6E] py-1 bg-[#01173C]">
|
||||
{user && (
|
||||
<Avatar className="size-[21px]">
|
||||
<AvatarImage src={user?.image ?? ""} />
|
||||
<AvatarFallback className="text-[10px]">
|
||||
{user?.name?.charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
)}
|
||||
<p>{userMessage}</p>
|
||||
</div>
|
||||
{messagePairs.map((pair, pairIdx) => {
|
||||
const userMessageText =
|
||||
pair.userMessage.parts.find((part) => part.type === "text")?.text ??
|
||||
""
|
||||
|
||||
{(reasoningSteps.length > 0 || memoryResults.length > 0) && (
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col items-center mx-2 bg-[#01173C] py-1 h-fit">
|
||||
<div className="size-[7px] rounded-full bg-[#151F31] shrink-0" />
|
||||
</div>
|
||||
const reasoningSteps: ReasoningStep[] = []
|
||||
if (pair.agentMessage) {
|
||||
pair.agentMessage.parts.forEach((part) => {
|
||||
if (part.type === "tool-searchMemories") {
|
||||
if (
|
||||
part.state === "input-available" ||
|
||||
part.state === "input-streaming"
|
||||
) {
|
||||
reasoningSteps.push({
|
||||
type: part.type,
|
||||
state: part.state,
|
||||
message: "Searching memories...",
|
||||
})
|
||||
} else if (part.state === "output-available") {
|
||||
reasoningSteps.push({
|
||||
type: part.type,
|
||||
state: part.state,
|
||||
message: "Found relevant memories",
|
||||
})
|
||||
} else if (part.state === "output-error") {
|
||||
reasoningSteps.push({
|
||||
type: part.type,
|
||||
state: part.state,
|
||||
message: "Error searching memories",
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
<div className="flex-1 space-y-1">
|
||||
{reasoningSteps.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
{reasoningSteps.map((step, idx) => (
|
||||
<div
|
||||
key={`${step.type}-${step.state}-${idx}`}
|
||||
className="text-[#525D6E]"
|
||||
>
|
||||
{step.message}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
const memoryResults: MemoryResult[] = []
|
||||
if (pair.agentMessage) {
|
||||
pair.agentMessage.parts.forEach((part) => {
|
||||
if (
|
||||
part.type === "tool-searchMemories" &&
|
||||
part.state === "output-available"
|
||||
) {
|
||||
const output = part.output as
|
||||
| { results?: MemoryResult[] }
|
||||
| undefined
|
||||
const results = Array.isArray(output?.results)
|
||||
? output.results
|
||||
: []
|
||||
memoryResults.push(...results)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
{memoryResults.length > 0 && (
|
||||
<div className="grid grid-cols-2 gap-2 max-h-64 overflow-y-auto">
|
||||
{memoryResults.map((result, idx) => {
|
||||
const isClickable =
|
||||
result.url &&
|
||||
(result.url.startsWith("http://") ||
|
||||
result.url.startsWith("https://"))
|
||||
return (
|
||||
<div key={pair.userMessage.id || pairIdx} className="space-y-3">
|
||||
<div className="flex items-start gap-3 text-[#525D6E] py-1">
|
||||
{user && (
|
||||
<Avatar className="size-[21px]">
|
||||
<AvatarImage src={user?.image ?? ""} />
|
||||
<AvatarFallback className="text-[10px]">
|
||||
{user?.name?.charAt(0)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
)}
|
||||
<p>{userMessageText}</p>
|
||||
</div>
|
||||
|
||||
const content = (
|
||||
<div className="">
|
||||
<div className="bg-[#060D17] p-2 px-[10px] rounded-xl m-[2px]">
|
||||
{result.title && (
|
||||
<div className="text-xs text-[#525D6E] line-clamp-2">
|
||||
{result.title}
|
||||
</div>
|
||||
)}
|
||||
{result.content && (
|
||||
<div className="text-xs text-[#525D6E]/80 line-clamp-2 mt-1">
|
||||
{result.content}
|
||||
</div>
|
||||
)}
|
||||
{result.url && (
|
||||
<div className="text-xs text-[#525D6E] mt-1 truncate">
|
||||
{result.url}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{result.score && (
|
||||
<div className="flex justify-center p-1">
|
||||
<div
|
||||
className={cn(
|
||||
"text-[10px] inline-block bg-clip-text text-transparent font-medium",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
style={{
|
||||
backgroundImage:
|
||||
"var(--grad-1, linear-gradient(94deg, #369BFD 4.8%, #36FDFD 77.04%, #36FDB5 143.99%))",
|
||||
}}
|
||||
>
|
||||
Relevancy score: {(result.score * 100).toFixed(1)}%
|
||||
</div>
|
||||
{(reasoningSteps.length > 0 || memoryResults.length > 0) && (
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col items-center mx-2 py-1 h-fit">
|
||||
<div className="size-[7px] rounded-full bg-[#151F31] shrink-0" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 space-y-1">
|
||||
{reasoningSteps.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
{reasoningSteps.map((step, idx) => (
|
||||
<div
|
||||
key={`${step.type}-${step.state}-${idx}`}
|
||||
className="text-[#525D6E]"
|
||||
>
|
||||
{step.message}
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
if (isClickable) {
|
||||
return (
|
||||
<a
|
||||
className="block p-2 bg-[#0C1829]/50 rounded-md border border-[#525D6E]/20 hover:bg-[#0C1829]/70 transition-colors cursor-pointer"
|
||||
href={result.url}
|
||||
key={result.documentId || idx}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{memoryResults.length > 0 && (
|
||||
<div className="grid grid-cols-2 gap-2 max-h-64 overflow-y-auto">
|
||||
{memoryResults.map((result, idx) => {
|
||||
const isClickable =
|
||||
result.url &&
|
||||
(result.url.startsWith("http://") ||
|
||||
result.url.startsWith("https://"))
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("bg-[#0C1829] rounded-xl")}
|
||||
key={result.documentId || idx}
|
||||
>
|
||||
{content}
|
||||
const content = (
|
||||
<div className="">
|
||||
<div className="bg-[#060D17] p-2 px-[10px] rounded-xl m-[2px]">
|
||||
{result.title && (
|
||||
<div className="text-xs text-[#525D6E] line-clamp-2">
|
||||
{result.title}
|
||||
</div>
|
||||
)}
|
||||
{result.content && (
|
||||
<div className="text-xs text-[#525D6E]/80 line-clamp-2 mt-1">
|
||||
{result.content}
|
||||
</div>
|
||||
)}
|
||||
{result.url && (
|
||||
<div className="text-xs text-[#525D6E] mt-1 truncate">
|
||||
{result.url}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{result.score && (
|
||||
<div className="flex justify-center p-1">
|
||||
<div
|
||||
className={cn(
|
||||
"text-[10px] inline-block bg-clip-text text-transparent font-medium",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
style={{
|
||||
backgroundImage:
|
||||
"var(--grad-1, linear-gradient(94deg, #369BFD 4.8%, #36FDFD 77.04%, #36FDB5 143.99%))",
|
||||
}}
|
||||
>
|
||||
Relevancy score:{" "}
|
||||
{(result.score * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (isClickable) {
|
||||
return (
|
||||
<a
|
||||
className="block p-2 bg-[#0C1829]/50 rounded-md border border-[#525D6E]/20 hover:bg-[#0C1829]/70 transition-colors cursor-pointer"
|
||||
href={result.url}
|
||||
key={result.documentId || idx}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("bg-[#0C1829] rounded-xl")}
|
||||
key={result.documentId || idx}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ interface ChatInputProps {
|
|||
isResponding?: boolean
|
||||
activeStatus?: string
|
||||
chainOfThoughtComponent?: React.ReactNode
|
||||
onExpandedChange?: (expanded: boolean) => void
|
||||
}
|
||||
|
||||
export default function ChatInput({
|
||||
|
|
@ -28,6 +29,7 @@ export default function ChatInput({
|
|||
isResponding = false,
|
||||
activeStatus,
|
||||
chainOfThoughtComponent,
|
||||
onExpandedChange,
|
||||
}: ChatInputProps) {
|
||||
const [isMultiline, setIsMultiline] = useState(false)
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
|
@ -48,11 +50,12 @@ export default function ChatInput({
|
|||
|
||||
return (
|
||||
<motion.div
|
||||
className={cn("bg-[#01173C] relative")}
|
||||
className={cn("relative z-20!")}
|
||||
animate={{
|
||||
padding: isExpanded ? "0.5rem" : "0",
|
||||
padding: isExpanded ? "16px" : "0",
|
||||
margin: isExpanded ? "0" : "16px",
|
||||
borderRadius: isExpanded ? "0 0 12px 12px" : "12px",
|
||||
backgroundColor: isExpanded ? "#000B1B" : "#01173C",
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
|
|
@ -61,18 +64,15 @@ export default function ChatInput({
|
|||
>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute bottom-full left-0 right-0 overflow-hidden transition-all duration-300 ease-out bg-[#01173C]",
|
||||
"absolute bottom-full left-0 right-0 overflow-hidden transition-all duration-300 ease-out bg-[#000B1B]",
|
||||
isExpanded
|
||||
? "max-h-[80vh] opacity-100 overflow-y-auto pt-1.5 pb-2 rounded-t-xl px-2"
|
||||
? "max-h-[80vh] opacity-100 overflow-y-auto pt-1.5 pb-2 rounded-t-xl px-4"
|
||||
: "max-h-0 opacity-0",
|
||||
)}
|
||||
style={{
|
||||
zIndex: isExpanded ? 50 : 0,
|
||||
}}
|
||||
>
|
||||
{isExpanded && (
|
||||
<div className="absolute top-0 left-0 right-0 h-10 bg-linear-to-b from-[#01173C] via-[#01173C]/50 to-transparent pointer-events-none z-10" />
|
||||
)}
|
||||
{chainOfThoughtComponent}
|
||||
</div>
|
||||
<button
|
||||
|
|
@ -82,8 +82,11 @@ export default function ChatInput({
|
|||
!chainOfThoughtComponent && "disabled:cursor-not-allowed",
|
||||
)}
|
||||
onClick={() => {
|
||||
setIsExpanded(!isExpanded)
|
||||
const newExpanded = !isExpanded
|
||||
setIsExpanded(newExpanded)
|
||||
onExpandedChange?.(newExpanded)
|
||||
}}
|
||||
disabled={!chainOfThoughtComponent}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<NovaOrb size={24} className="blur-[1px]! z-10" />
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export function AgentMessage({
|
|||
return (
|
||||
<div
|
||||
key={`${message.id}-${partIndex}`}
|
||||
className="text-sm text-white/90"
|
||||
className="text-sm text-white/90 chat-markdown-content"
|
||||
>
|
||||
<Streamdown>{part.text}</Streamdown>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -66,3 +66,19 @@
|
|||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
/* css to fix for ordered list numbers being cut off in chat agent messages */
|
||||
.chat-markdown-content ol {
|
||||
padding-left: 1.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.chat-markdown-content ul {
|
||||
padding-left: 1.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.chat-markdown-content li {
|
||||
margin-left: 0;
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue