aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/web/app/api/agent/chat/route.ts186
-rw-r--r--apps/web/app/api/agent/follow-ups/route.ts51
-rw-r--r--apps/web/app/api/agent/title/route.ts42
-rw-r--r--apps/web/components/new/chat/index.tsx91
-rw-r--r--apps/web/components/new/chat/input/chain-of-thought.tsx23
-rw-r--r--apps/web/components/new/chat/message/agent-message.tsx6
-rw-r--r--apps/web/components/new/chat/message/related-memories.tsx12
-rw-r--r--apps/web/components/new/chat/message/user-message.tsx6
-rw-r--r--apps/web/components/new/onboarding/setup/chat-sidebar.tsx18
-rw-r--r--apps/web/components/views/chat/chat-messages.tsx126
-rw-r--r--apps/web/hooks/use-claude-agent.ts238
-rw-r--r--apps/web/lib/agent/tools.ts164
-rw-r--r--apps/web/lib/agent/types.ts75
-rw-r--r--apps/web/package.json1
-rw-r--r--packages/ui/components/badge.tsx2
-rw-r--r--packages/ui/components/breadcrumb.tsx2
-rw-r--r--packages/ui/components/button.tsx2
-rw-r--r--packages/ui/components/sidebar.tsx10
-rw-r--r--packages/ui/text/heading/heading-h1-bold.tsx13
-rw-r--r--packages/ui/text/heading/heading-h1-medium.tsx13
-rw-r--r--packages/ui/text/heading/heading-h2-bold.tsx13
-rw-r--r--packages/ui/text/heading/heading-h2-medium.tsx13
-rw-r--r--packages/ui/text/heading/heading-h3-bold.tsx13
-rw-r--r--packages/ui/text/heading/heading-h3-medium.tsx13
-rw-r--r--packages/ui/text/heading/heading-h4-bold.tsx13
-rw-r--r--packages/ui/text/heading/heading-h4-medium.tsx13
-rw-r--r--packages/ui/text/label/label-1-medium.tsx20
-rw-r--r--packages/ui/text/label/label-1-regular.tsx20
-rw-r--r--packages/ui/text/label/label-2-medium.tsx20
-rw-r--r--packages/ui/text/label/label-2-regular.tsx20
-rw-r--r--packages/ui/text/label/label-3-medium.tsx20
-rw-r--r--packages/ui/text/label/label-3-regular.tsx20
-rw-r--r--packages/ui/text/title/title-1-bold.tsx20
-rw-r--r--packages/ui/text/title/title-1-medium.tsx20
-rw-r--r--packages/ui/text/title/title-2-bold.tsx20
-rw-r--r--packages/ui/text/title/title-2-medium.tsx20
-rw-r--r--packages/ui/text/title/title-3-bold.tsx20
-rw-r--r--packages/ui/text/title/title-3-medium.tsx20
38 files changed, 1108 insertions, 291 deletions
diff --git a/apps/web/app/api/agent/chat/route.ts b/apps/web/app/api/agent/chat/route.ts
new file mode 100644
index 00000000..110c4ef3
--- /dev/null
+++ b/apps/web/app/api/agent/chat/route.ts
@@ -0,0 +1,186 @@
+import { query } from "@anthropic-ai/claude-agent-sdk"
+import { cookies } from "next/headers"
+import { createSupermemoryMcpServer, SUPERMEMORY_SYSTEM_PROMPT } from "@/lib/agent/tools"
+import type { ChatRequest, AgentMessagePart } from "@/lib/agent/types"
+
+export const runtime = "nodejs"
+
+export async function POST(req: Request) {
+ try {
+ const body: ChatRequest = await req.json()
+ const { messages, metadata } = body
+
+ const cookieStore = await cookies()
+ const cookieHeader = cookieStore
+ .getAll()
+ .map((c) => `${c.name}=${c.value}`)
+ .join("; ")
+
+ const supermemoryServer = createSupermemoryMcpServer({
+ cookies: cookieHeader,
+ projectId: metadata.projectId,
+ })
+
+ const encoder = new TextEncoder()
+ const stream = new ReadableStream({
+ async start(controller) {
+ try {
+ const conversationText = messages
+ .map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`)
+ .join("\n\n")
+
+ const lastUserMessage = messages.filter((m) => m.role === "user").pop()
+ const promptText = lastUserMessage?.content ?? conversationText
+
+ const result = query({
+ prompt: promptText,
+ options: {
+ systemPrompt: `${SUPERMEMORY_SYSTEM_PROMPT}\n\nConversation history:\n${conversationText}`,
+ model: "claude-sonnet-4-20250514",
+ mcpServers: {
+ supermemory: supermemoryServer,
+ },
+ allowedTools: [
+ "mcp__supermemory__searchMemories",
+ "mcp__supermemory__addMemory",
+ ],
+ permissionMode: "bypassPermissions",
+ allowDangerouslySkipPermissions: true,
+ includePartialMessages: true,
+ },
+ })
+
+ for await (const message of result) {
+ const parts: AgentMessagePart[] = []
+ const msgType = message.type
+
+ if (msgType === "assistant") {
+ const assistantMsg = message as {
+ type: "assistant"
+ message: {
+ id?: string
+ content: string | Array<{ type: string; text?: string }>
+ }
+ }
+ const textContent =
+ typeof assistantMsg.message.content === "string"
+ ? assistantMsg.message.content
+ : Array.isArray(assistantMsg.message.content)
+ ? assistantMsg.message.content
+ .filter((c) => c.type === "text" && typeof c.text === "string")
+ .map((c) => c.text as string)
+ .join("")
+ : ""
+
+ if (textContent) {
+ parts.push({ type: "text", text: textContent })
+ }
+
+ const event = {
+ type: "assistant",
+ id: assistantMsg.message.id ?? crypto.randomUUID(),
+ role: "assistant",
+ parts,
+ }
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`))
+ }
+
+ if (msgType === "result") {
+ const resultMsg = message as {
+ type: "result"
+ subtype?: string
+ tool_name?: string
+ tool_input?: Record<string, unknown>
+ tool_result?: {
+ content?: Array<{ type: string; text?: string }>
+ }
+ }
+
+ if (resultMsg.subtype === "tool_use" && resultMsg.tool_name) {
+ const toolName = resultMsg.tool_name
+
+ if (toolName === "mcp__supermemory__searchMemories") {
+ parts.push({
+ type: "tool-searchMemories",
+ state: "input-available",
+ input: resultMsg.tool_input as { query: string; limit?: number },
+ })
+ } else if (toolName === "mcp__supermemory__addMemory") {
+ parts.push({
+ type: "tool-addMemory",
+ state: "input-available",
+ input: resultMsg.tool_input as { content: string; title?: string },
+ })
+ }
+
+ if (parts.length > 0) {
+ const event = { type: "tool_use", parts }
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`))
+ }
+ }
+
+ if (resultMsg.subtype === "tool_result" && resultMsg.tool_result?.content) {
+ try {
+ const resultText = resultMsg.tool_result.content
+ .filter((c) => c.type === "text")
+ .map((c) => c.text)
+ .join("")
+
+ if (resultText) {
+ const parsed = JSON.parse(resultText)
+
+ if ("count" in parsed && "results" in parsed) {
+ parts.push({
+ type: "tool-searchMemories",
+ state: "output-available",
+ output: parsed,
+ })
+ } else if ("status" in parsed) {
+ parts.push({
+ type: "tool-addMemory",
+ state: "output-available",
+ output: parsed,
+ })
+ }
+ }
+ } catch {
+ // Ignore parse errors
+ }
+
+ if (parts.length > 0) {
+ const event = { type: "tool_result", parts }
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`))
+ }
+ }
+ }
+ }
+
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"))
+ controller.close()
+ } catch (error) {
+ console.error("Agent stream error:", error)
+ const errorEvent = {
+ type: "error",
+ error: error instanceof Error ? error.message : "Unknown error",
+ }
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(errorEvent)}\n\n`))
+ controller.close()
+ }
+ },
+ })
+
+ return new Response(stream, {
+ headers: {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ },
+ })
+ } catch (error) {
+ console.error("Agent route error:", error)
+ return Response.json(
+ { error: error instanceof Error ? error.message : "Internal server error" },
+ { status: 500 }
+ )
+ }
+}
diff --git a/apps/web/app/api/agent/follow-ups/route.ts b/apps/web/app/api/agent/follow-ups/route.ts
new file mode 100644
index 00000000..6653226a
--- /dev/null
+++ b/apps/web/app/api/agent/follow-ups/route.ts
@@ -0,0 +1,51 @@
+import { unstable_v2_prompt } from "@anthropic-ai/claude-agent-sdk"
+import type { FollowUpRequest } from "@/lib/agent/types"
+
+export const runtime = "nodejs"
+
+export async function POST(req: Request) {
+ try {
+ const body: FollowUpRequest = await req.json()
+ const { messages } = body
+
+ const conversationContext = messages
+ .slice(-5)
+ .map((msg) => `${msg.role}: ${msg.content}`)
+ .join("\n\n")
+
+ const prompt = `Based on this conversation, generate exactly 3 brief follow-up questions that the user might want to ask next. Return ONLY a JSON array of strings, no other text.
+
+Conversation:
+${conversationContext}
+
+Return format: ["question 1", "question 2", "question 3"]`
+
+ const response = await unstable_v2_prompt(prompt, {
+ model: "claude-sonnet-4-20250514",
+ })
+
+ let questions: string[] = []
+
+ let resultText = ""
+ if (response.type === "result" && "result" in response) {
+ resultText = (response as { result?: string }).result ?? ""
+ }
+
+ try {
+ const jsonMatch = resultText.match(/\[[\s\S]*\]/)
+ if (jsonMatch) {
+ questions = JSON.parse(jsonMatch[0])
+ }
+ } catch {
+ const lines = resultText.split("\n").filter((line: string) => line.trim())
+ questions = lines.slice(0, 3).map((line: string) =>
+ line.replace(/^[\d\.\-\*\s]+/, "").replace(/["\[\]]/g, "").trim()
+ )
+ }
+
+ return Response.json({ questions: questions.slice(0, 3) })
+ } catch (error) {
+ console.error("Follow-ups route error:", error)
+ return Response.json({ questions: [] }, { status: 200 })
+ }
+}
diff --git a/apps/web/app/api/agent/title/route.ts b/apps/web/app/api/agent/title/route.ts
new file mode 100644
index 00000000..5626387c
--- /dev/null
+++ b/apps/web/app/api/agent/title/route.ts
@@ -0,0 +1,42 @@
+import { unstable_v2_prompt } from "@anthropic-ai/claude-agent-sdk"
+import type { TitleRequest } from "@/lib/agent/types"
+
+export const runtime = "nodejs"
+
+export async function POST(req: Request) {
+ try {
+ const body: TitleRequest = await req.json()
+ const { messages } = body
+
+ if (messages.length === 0) {
+ return Response.json({ title: "New Chat" })
+ }
+
+ const conversationContext = messages
+ .slice(0, 4)
+ .map((msg) => `${msg.role}: ${msg.content.slice(0, 500)}`)
+ .join("\n\n")
+
+ const prompt = `Generate a short, descriptive title (3-6 words) for this conversation. Return ONLY the title text, no quotes or punctuation at the end.
+
+Conversation:
+${conversationContext}
+
+Title:`
+
+ const response = await unstable_v2_prompt(prompt, {
+ model: "claude-sonnet-4-20250514",
+ })
+
+ let resultText = "New Chat"
+ if (response.type === "result" && "result" in response) {
+ resultText = (response as { result?: string }).result ?? "New Chat"
+ }
+ const title = resultText.trim().replace(/^["']|["']$/g, "").slice(0, 100)
+
+ return Response.json({ title })
+ } catch (error) {
+ console.error("Title route error:", error)
+ return Response.json({ title: "New Chat" }, { status: 200 })
+ }
+}
diff --git a/apps/web/components/new/chat/index.tsx b/apps/web/components/new/chat/index.tsx
index 51bbe455..dc0c49f6 100644
--- a/apps/web/components/new/chat/index.tsx
+++ b/apps/web/components/new/chat/index.tsx
@@ -1,11 +1,13 @@
"use client"
import { useState, useEffect, useCallback, useRef } from "react"
-import type { UIMessage } from "@ai-sdk/react"
import { motion, AnimatePresence } from "motion/react"
-import { useChat } from "@ai-sdk/react"
-import { DefaultChatTransport } from "ai"
import NovaOrb from "@/components/nova/nova-orb"
+import {
+ useClaudeAgent,
+ generateFollowUpQuestions,
+} from "@/hooks/use-claude-agent"
+import type { AgentMessage as AgentMessageType } from "@/lib/agent/types"
import { Button } from "@ui/components/button"
import {
Dialog,
@@ -32,9 +34,9 @@ import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
import ChatInput from "./input"
import ChatModelSelector from "./model-selector"
+import type { ModelId } from "@/lib/models"
import { GradientLogo, LogoBgGradient } from "@ui/assets/Logo"
import { useProject } from "@/stores"
-import type { ModelId } from "@/lib/models"
import { SuperLoader } from "../../superloader"
import { UserMessage } from "./message/user-message"
import { AgentMessage } from "./message/agent-message"
@@ -108,7 +110,7 @@ export function ChatSidebar({
}) {
const isMobile = useIsMobile()
const [input, setInput] = useState("")
- const [selectedModel, setSelectedModel] = useState<ModelId>("gemini-2.5-pro")
+ const [selectedModel, setSelectedModel] = useState<ModelId>("claude-sonnet-4.5")
const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null)
const [hoveredMessageId, setHoveredMessageId] = useState<string | null>(null)
const [messageFeedback, setMessageFeedback] = useState<
@@ -138,7 +140,7 @@ export function ChatSidebar({
const [currentChatId, setCurrentChatId] = useState<string>(() => generateId())
const [pendingThreadLoad, setPendingThreadLoad] = useState<{
id: string
- messages: UIMessage[]
+ messages: AgentMessageType[]
} | null>(null)
// Adjust chat height based on scroll position (desktop only)
@@ -159,24 +161,15 @@ export function ChatSidebar({
return () => window.removeEventListener("scroll", handleWindowScroll)
}, [isMobile])
- const { messages, sendMessage, status, setMessages, stop } = useChat({
+ const { messages, sendMessage, status, setMessages, stop } = useClaudeAgent({
id: currentChatId ?? undefined,
- transport: new DefaultChatTransport({
- api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/v2`,
- credentials: "include",
- body: {
- metadata: {
- projectId: selectedProject,
- model: selectedModel,
- chatId: currentChatId,
- },
- },
- }),
+ metadata: {
+ projectId: selectedProject,
+ chatId: currentChatId,
+ },
onFinish: async (result) => {
if (result.message.role !== "assistant") return
- // Mark this message as needing follow-up generation
- // We'll generate it after the message is fully in the messages array
if (result.message.id) {
pendingFollowUpGenerations.current.add(result.message.id)
}
@@ -229,38 +222,20 @@ export function ChatSidebar({
}))
try {
- // Get recent messages for context
const recentMessages = messages.slice(-5).map((msg) => ({
role: msg.role,
content: msg.parts
- .filter((p) => p.type === "text")
+ .filter((p): p is { type: "text"; text: string } => p.type === "text")
.map((p) => p.text)
.join(" "),
}))
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/follow-ups`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- credentials: "include",
- body: JSON.stringify({
- messages: recentMessages,
- assistantResponse: assistantText,
- }),
- },
- )
-
- if (response.ok) {
- const data = await response.json()
- if (data.questions && Array.isArray(data.questions)) {
- setFollowUpQuestions((prev) => ({
- ...prev,
- [message.id]: data.questions,
- }))
- }
+ const questions = await generateFollowUpQuestions(recentMessages)
+ if (questions.length > 0) {
+ setFollowUpQuestions((prev) => ({
+ ...prev,
+ [message.id]: questions,
+ }))
}
} catch (error) {
console.error("Failed to generate follow-up questions:", error)
@@ -402,21 +377,29 @@ export function ChatSidebar({
)
if (response.ok) {
const data = await response.json()
- const uiMessages = data.messages.map(
+ const agentMessages: AgentMessageType[] = data.messages.map(
(m: {
id: string
role: string
- parts: unknown
+ parts: Array<{ type: string; text?: string }>
createdAt: string
- }) => ({
- id: m.id,
- role: m.role,
- parts: m.parts || [],
- createdAt: new Date(m.createdAt),
- }),
+ }) => {
+ const textParts = (m.parts || []).filter(
+ (p): p is { type: "text"; text: string } =>
+ p.type === "text" && typeof p.text === "string"
+ )
+ const content = textParts.map((p) => p.text).join(" ")
+ return {
+ id: m.id,
+ role: m.role as "user" | "assistant",
+ content,
+ parts: m.parts || [],
+ createdAt: new Date(m.createdAt),
+ }
+ }
)
setCurrentChatId(threadId)
- setPendingThreadLoad({ id: threadId, messages: uiMessages })
+ setPendingThreadLoad({ id: threadId, messages: agentMessages })
analytics.chatThreadLoaded({ thread_id: threadId })
setIsHistoryOpen(false)
setConfirmingDeleteId(null)
diff --git a/apps/web/components/new/chat/input/chain-of-thought.tsx b/apps/web/components/new/chat/input/chain-of-thought.tsx
index b1923146..d78ad272 100644
--- a/apps/web/components/new/chat/input/chain-of-thought.tsx
+++ b/apps/web/components/new/chat/input/chain-of-thought.tsx
@@ -1,16 +1,8 @@
import { useAuth } from "@lib/auth-context"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
-import type { UIMessage } from "@ai-sdk/react"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/lib/fonts"
-
-interface MemoryResult {
- documentId?: string
- title?: string
- content?: string
- url?: string
- score?: number
-}
+import type { AgentMessage, MemoryResult } from "@/lib/agent/types"
interface ReasoningStep {
type: string
@@ -18,13 +10,13 @@ interface ReasoningStep {
message: string
}
-export function ChainOfThought({ messages }: { messages: UIMessage[] }) {
+export function ChainOfThought({ messages }: { messages: AgentMessage[] }) {
const { user } = useAuth()
// Group messages into user-assistant pairs
const messagePairs: Array<{
- userMessage: UIMessage
- agentMessage?: UIMessage
+ userMessage: AgentMessage
+ agentMessage?: AgentMessage
}> = []
for (let i = 0; i < messages.length; i++) {
@@ -46,9 +38,10 @@ export function ChainOfThought({ messages }: { messages: UIMessage[] }) {
<div className="absolute left-[11px] top-0 bottom-0 w-px bg-[#151F31] self-stretch mb-0 -z-10" />
{messagePairs.map((pair, pairIdx) => {
- const userMessageText =
- pair.userMessage.parts.find((part) => part.type === "text")?.text ??
- ""
+ const textPart = pair.userMessage.parts.find(
+ (part): part is { type: "text"; text: string } => part.type === "text"
+ )
+ const userMessageText = textPart?.text ?? ""
const reasoningSteps: ReasoningStep[] = []
if (pair.agentMessage) {
diff --git a/apps/web/components/new/chat/message/agent-message.tsx b/apps/web/components/new/chat/message/agent-message.tsx
index f4528ef5..705785af 100644
--- a/apps/web/components/new/chat/message/agent-message.tsx
+++ b/apps/web/components/new/chat/message/agent-message.tsx
@@ -1,13 +1,13 @@
"use client"
-import type { UIMessage } from "@ai-sdk/react"
import { Streamdown } from "streamdown"
import { RelatedMemories } from "./related-memories"
import { MessageActions } from "./message-actions"
import { FollowUpQuestions } from "./follow-up-questions"
+import type { AgentMessage as AgentMessageType } from "@/lib/agent/types"
interface AgentMessageProps {
- message: UIMessage
+ message: AgentMessageType
index: number
messagesLength: number
hoveredMessageId: string | null
@@ -43,7 +43,7 @@ export function AgentMessage({
index === messagesLength - 1 && message.role === "assistant"
const isHovered = hoveredMessageId === message.id
const messageText = message.parts
- .filter((part) => part.type === "text")
+ .filter((part): part is { type: "text"; text: string } => part.type === "text")
.map((part) => part.text)
.join(" ")
diff --git a/apps/web/components/new/chat/message/related-memories.tsx b/apps/web/components/new/chat/message/related-memories.tsx
index ad83d03e..2dbfcc6f 100644
--- a/apps/web/components/new/chat/message/related-memories.tsx
+++ b/apps/web/components/new/chat/message/related-memories.tsx
@@ -1,18 +1,10 @@
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
-import type { UIMessage } from "@ai-sdk/react"
import { dmSansClassName } from "@/lib/fonts"
import { cn } from "@lib/utils"
-
-interface MemoryResult {
- documentId?: string
- title?: string
- content?: string
- url?: string
- score?: number
-}
+import type { AgentMessage, MemoryResult } from "@/lib/agent/types"
interface RelatedMemoriesProps {
- message: UIMessage
+ message: AgentMessage
expandedMemories: string | null
onToggle: (messageId: string) => void
}
diff --git a/apps/web/components/new/chat/message/user-message.tsx b/apps/web/components/new/chat/message/user-message.tsx
index 8e5d8c5b..3507fc40 100644
--- a/apps/web/components/new/chat/message/user-message.tsx
+++ b/apps/web/components/new/chat/message/user-message.tsx
@@ -1,10 +1,10 @@
"use client"
import { Copy, Check } from "lucide-react"
-import type { UIMessage } from "@ai-sdk/react"
+import type { AgentMessage } from "@/lib/agent/types"
interface UserMessageProps {
- message: UIMessage
+ message: AgentMessage
copiedMessageId: string | null
onCopy: (messageId: string, text: string) => void
}
@@ -15,7 +15,7 @@ export function UserMessage({
onCopy,
}: UserMessageProps) {
const text = message.parts
- .filter((part) => part.type === "text")
+ .filter((part): part is { type: "text"; text: string } => part.type === "text")
.map((part) => part.text)
.join(" ")
diff --git a/apps/web/components/new/onboarding/setup/chat-sidebar.tsx b/apps/web/components/new/onboarding/setup/chat-sidebar.tsx
index 47af432d..20828d86 100644
--- a/apps/web/components/new/onboarding/setup/chat-sidebar.tsx
+++ b/apps/web/components/new/onboarding/setup/chat-sidebar.tsx
@@ -2,8 +2,7 @@
import { useState, useEffect, useCallback, useRef } from "react"
import { motion, AnimatePresence } from "motion/react"
-import { useChat } from "@ai-sdk/react"
-import { DefaultChatTransport } from "ai"
+import { useClaudeAgent } from "@/hooks/use-claude-agent"
import NovaOrb from "@/components/nova/nova-orb"
import { Button } from "@ui/components/button"
import {
@@ -80,17 +79,10 @@ export function ChatSidebar({ formData }: ChatSidebarProps) {
messages: chatMessages,
sendMessage,
status,
- } = useChat({
- transport: new DefaultChatTransport({
- api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/v2`,
- credentials: "include",
- body: {
- metadata: {
- projectId: selectedProject,
- model: "gemini-2.5-pro",
- },
- },
- }),
+ } = useClaudeAgent({
+ metadata: {
+ projectId: selectedProject,
+ },
})
const buildOnboardingContext = useCallback(() => {
diff --git a/apps/web/components/views/chat/chat-messages.tsx b/apps/web/components/views/chat/chat-messages.tsx
index 304db7aa..5069231d 100644
--- a/apps/web/components/views/chat/chat-messages.tsx
+++ b/apps/web/components/views/chat/chat-messages.tsx
@@ -1,9 +1,12 @@
"use client"
-import { useChat, useCompletion, type UIMessage } from "@ai-sdk/react"
import { cn } from "@lib/utils"
import { Button } from "@ui/components/button"
-import { DefaultChatTransport } from "ai"
+import {
+ useClaudeAgent,
+ generateChatTitle,
+} from "@/hooks/use-claude-agent"
+import type { AgentMessage, AgentMessagePart } from "@/lib/agent/types"
import {
ArrowUp,
Check,
@@ -20,9 +23,8 @@ import { Streamdown } from "streamdown"
import { TextShimmer } from "@/components/text-shimmer"
import { usePersistentChat, useProject } from "@/stores"
import { useGraphHighlights } from "@/stores/highlights"
-import { ModelIcon } from "@/lib/models"
+import { ModelIcon, type ModelId } from "@/lib/models"
import { Spinner } from "../../spinner"
-import { areUIMessageArraysEqual } from "@/stores/chat"
interface MemoryResult {
documentId?: string
@@ -59,6 +61,18 @@ interface ChatMessage {
parts: MessagePart[]
}
+function areMessagesEqual(a: AgentMessage[], b: AgentMessage[]): boolean {
+ if (a === b) return true
+ if (a.length !== b.length) return false
+ for (let i = 0; i < a.length; i++) {
+ const msgA = a[i]
+ const msgB = b[i]
+ if (!msgA || !msgB) return false
+ if (msgA.id !== msgB.id) return false
+ }
+ return true
+}
+
function ExpandableMemories({ foundCount, results }: ExpandableMemoriesProps) {
const [isExpanded, setIsExpanded] = useState(false)
@@ -243,50 +257,58 @@ export function ChatMessages() {
const storageKey = `chat-model-${currentChatId}`
const [input, setInput] = useState("")
- const [selectedModel, setSelectedModel] = useState<
- "gpt-5" | "claude-sonnet-4.5" | "gemini-2.5-pro"
- >("gemini-2.5-pro")
+ const [selectedModel, setSelectedModel] = useState<ModelId>("claude-sonnet-4.5")
const activeChatIdRef = useRef<string | null>(null)
const shouldGenerateTitleRef = useRef<boolean>(false)
const hasRunInitialMessageRef = useRef<boolean>(false)
- const lastSavedMessagesRef = useRef<UIMessage[] | null>(null)
+ const lastSavedMessagesRef = useRef<AgentMessage[] | null>(null)
const lastSavedActiveIdRef = useRef<string | null>(null)
const lastLoadedChatIdRef = useRef<string | null>(null)
- const lastLoadedMessagesRef = useRef<UIMessage[] | null>(null)
+ const lastLoadedMessagesRef = useRef<AgentMessage[] | null>(null)
const { setDocumentIds } = useGraphHighlights()
- const { messages, sendMessage, status, stop, setMessages, id, regenerate } =
- useChat({
- id: currentChatId ?? undefined,
- transport: new DefaultChatTransport({
- api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat`,
- credentials: "include",
- body: {
- metadata: {
- projectId: selectedProject,
- model: selectedModel,
- chatId: currentChatId,
- },
- },
- }),
- onFinish: (result) => {
- const activeId = activeChatIdRef.current
- if (!activeId) return
- if (result.message.role !== "assistant") return
-
- if (shouldGenerateTitleRef.current) {
- const textPart = result.message.parts.find(
- (p: { type?: string; text?: string }) => p?.type === "text",
- ) as { text?: string } | undefined
- const text = textPart?.text?.trim()
- if (text) {
- shouldGenerateTitleRef.current = false
- complete(text)
- }
+ const { messages, sendMessage, status, stop, setMessages } = useClaudeAgent({
+ id: currentChatId ?? undefined,
+ metadata: {
+ projectId: selectedProject,
+ chatId: currentChatId ?? undefined,
+ },
+ onFinish: async (result) => {
+ const activeId = activeChatIdRef.current
+ if (!activeId) return
+ if (result.message.role !== "assistant") return
+
+ if (shouldGenerateTitleRef.current) {
+ const textPart = result.message.parts.find(
+ (p): p is { type: "text"; text: string } => p?.type === "text"
+ )
+ const text = textPart?.text?.trim()
+ if (text) {
+ shouldGenerateTitleRef.current = false
+ const allMessages = [...messages, result.message]
+ const recentMessages = allMessages.slice(-4).map((msg) => ({
+ role: msg.role,
+ content: msg.parts
+ .filter((p): p is { type: "text"; text: string } => p.type === "text")
+ .map((p) => p.text)
+ .join(" "),
+ }))
+ const title = await generateChatTitle(recentMessages)
+ setConversationTitle(activeId, title)
}
- },
- })
+ }
+ },
+ })
+
+ const id = currentChatId
+
+ const regenerate = useCallback(
+ (_options: { messageId: string }) => {
+ toast.info("Regenerate is not yet supported with Claude Agent SDK")
+ },
+ []
+ )
useEffect(() => {
lastLoadedMessagesRef.current = messages
@@ -350,9 +372,10 @@ export function ChatMessages() {
if (msgs && msgs.length > 0) {
const currentMessages = lastLoadedMessagesRef.current
- if (!currentMessages || !areUIMessageArraysEqual(currentMessages, msgs)) {
- lastLoadedMessagesRef.current = msgs
- setMessages(msgs)
+ const agentMsgs = msgs as unknown as AgentMessage[]
+ if (!currentMessages || !areMessagesEqual(currentMessages, agentMsgs)) {
+ lastLoadedMessagesRef.current = agentMsgs
+ setMessages(agentMsgs)
}
} else if (!currentChatId) {
if (
@@ -380,24 +403,15 @@ export function ChatMessages() {
}
const lastSaved = lastSavedMessagesRef.current
- if (lastSaved && areUIMessageArraysEqual(lastSaved, messages)) {
+ if (lastSaved && areMessagesEqual(lastSaved, messages)) {
return
}
lastSavedMessagesRef.current = messages
- setConversation(activeId, messages)
+ // Cast to unknown first to satisfy persistent chat store which uses UIMessage type
+ setConversation(activeId, messages as unknown as import("@ai-sdk/react").UIMessage[])
}, [messages, currentChatId, id, setConversation])
- const { complete } = useCompletion({
- api: `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/title`,
- credentials: "include",
- onFinish: (_, completion) => {
- const activeId = activeChatIdRef.current
- if (!completion || !activeId) return
- setConversationTitle(activeId, completion.trim())
- },
- })
-
// Update graph highlights from the most recent tool-searchMemories output
useEffect(() => {
try {
@@ -550,10 +564,8 @@ export function ChatMessages() {
"count" in output
? Number(output.count) || 0
: 0
- // @ts-expect-error
const results = Array.isArray(output?.results)
- ? // @ts-expect-error
- output.results
+ ? output.results
: []
return (
diff --git a/apps/web/hooks/use-claude-agent.ts b/apps/web/hooks/use-claude-agent.ts
new file mode 100644
index 00000000..1a0c8ef5
--- /dev/null
+++ b/apps/web/hooks/use-claude-agent.ts
@@ -0,0 +1,238 @@
+"use client"
+
+import { useState, useCallback, useRef } from "react"
+import type {
+ AgentMessage,
+ AgentMessagePart,
+ ClaudeAgentStatus,
+ ChatMetadata,
+} from "@/lib/agent/types"
+import { generateId } from "@lib/generate-id"
+
+interface UseClaudeAgentOptions {
+ id?: string
+ metadata: ChatMetadata
+ onFinish?: (result: { message: AgentMessage }) => void
+ onError?: (error: Error) => void
+}
+
+interface UseClaudeAgentReturn {
+ messages: AgentMessage[]
+ sendMessage: (options: { text: string }) => void
+ status: ClaudeAgentStatus
+ setMessages: (messages: AgentMessage[]) => void
+ stop: () => void
+}
+
+export function useClaudeAgent(options: UseClaudeAgentOptions): UseClaudeAgentReturn {
+ const { metadata, onFinish, onError } = options
+ const [messages, setMessages] = useState<AgentMessage[]>([])
+ const [status, setStatus] = useState<ClaudeAgentStatus>("idle")
+ const abortControllerRef = useRef<AbortController | null>(null)
+ const currentAssistantMessageRef = useRef<AgentMessage | null>(null)
+
+ const sendMessage = useCallback(
+ async (messageOptions: { text: string }) => {
+ const { text } = messageOptions
+
+ const userMessage: AgentMessage = {
+ id: generateId(),
+ role: "user",
+ content: text,
+ parts: [{ type: "text", text }],
+ createdAt: new Date(),
+ }
+
+ setMessages((prev) => [...prev, userMessage])
+ setStatus("submitted")
+
+ abortControllerRef.current = new AbortController()
+
+ try {
+ const allMessages = [...messages, userMessage]
+ const requestMessages = allMessages.map((msg) => ({
+ role: msg.role,
+ content: msg.content,
+ }))
+
+ const response = await fetch("/api/agent/chat", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ messages: requestMessages,
+ metadata,
+ }),
+ signal: abortControllerRef.current.signal,
+ })
+
+ if (!response.ok) {
+ throw new Error(`HTTP error: ${response.status}`)
+ }
+
+ const reader = response.body?.getReader()
+ if (!reader) {
+ throw new Error("No response body")
+ }
+
+ const decoder = new TextDecoder()
+ let buffer = ""
+
+ currentAssistantMessageRef.current = {
+ id: generateId(),
+ role: "assistant",
+ content: "",
+ parts: [],
+ createdAt: new Date(),
+ }
+
+ setMessages((prev) => [...prev, currentAssistantMessageRef.current!])
+ setStatus("streaming")
+
+ while (true) {
+ const { done, value } = await reader.read()
+ if (done) break
+
+ buffer += decoder.decode(value, { stream: true })
+ const lines = buffer.split("\n")
+ buffer = lines.pop() ?? ""
+
+ for (const line of lines) {
+ if (line.startsWith("data: ")) {
+ const data = line.slice(6)
+
+ if (data === "[DONE]") {
+ setStatus("idle")
+ if (currentAssistantMessageRef.current && onFinish) {
+ onFinish({ message: currentAssistantMessageRef.current })
+ }
+ continue
+ }
+
+ try {
+ const event = JSON.parse(data)
+ handleStreamEvent(event)
+ } catch {
+ // Ignore JSON parse errors
+ }
+ }
+ }
+ }
+ } catch (error) {
+ if ((error as Error).name === "AbortError") {
+ setStatus("idle")
+ return
+ }
+
+ console.error("Claude agent error:", error)
+ setStatus("error")
+ onError?.(error instanceof Error ? error : new Error(String(error)))
+ }
+ },
+ [messages, metadata, onFinish, onError]
+ )
+
+ const handleStreamEvent = useCallback(
+ (event: { type: string; parts?: AgentMessagePart[]; id?: string; error?: string }) => {
+ if (!currentAssistantMessageRef.current) return
+
+ if (event.type === "error") {
+ setStatus("error")
+ onError?.(new Error(event.error ?? "Unknown error"))
+ return
+ }
+
+ if (event.type === "assistant" && event.parts) {
+ const textParts = event.parts.filter(
+ (p): p is { type: "text"; text: string } => p.type === "text"
+ )
+
+ if (textParts.length > 0) {
+ const newText = textParts.map((p) => p.text).join("")
+ currentAssistantMessageRef.current.content += newText
+ currentAssistantMessageRef.current.parts = [
+ ...currentAssistantMessageRef.current.parts.filter((p) => p.type !== "text"),
+ { type: "text", text: currentAssistantMessageRef.current.content },
+ ]
+ }
+ }
+
+ if ((event.type === "tool_use" || event.type === "tool_result") && event.parts) {
+ currentAssistantMessageRef.current.parts = [
+ ...currentAssistantMessageRef.current.parts,
+ ...event.parts,
+ ]
+ }
+
+ setMessages((prev) => {
+ const newMessages = [...prev]
+ const lastIndex = newMessages.length - 1
+ if (lastIndex >= 0 && newMessages[lastIndex]?.role === "assistant") {
+ newMessages[lastIndex] = { ...currentAssistantMessageRef.current! }
+ }
+ return newMessages
+ })
+ },
+ [onError]
+ )
+
+ const stop = useCallback(() => {
+ abortControllerRef.current?.abort()
+ setStatus("idle")
+ }, [])
+
+ return {
+ messages,
+ sendMessage,
+ status,
+ setMessages,
+ stop,
+ }
+}
+
+export async function generateFollowUpQuestions(
+ messages: Array<{ role: string; content: string }>
+): Promise<string[]> {
+ try {
+ const response = await fetch("/api/agent/follow-ups", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ messages }),
+ })
+
+ if (!response.ok) {
+ return []
+ }
+
+ const data = await response.json()
+ return data.questions ?? []
+ } catch {
+ return []
+ }
+}
+
+export async function generateChatTitle(
+ messages: Array<{ role: string; content: string }>
+): Promise<string> {
+ try {
+ const response = await fetch("/api/agent/title", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ messages }),
+ })
+
+ if (!response.ok) {
+ return "New Chat"
+ }
+
+ const data = await response.json()
+ return data.title ?? "New Chat"
+ } catch {
+ return "New Chat"
+ }
+}
diff --git a/apps/web/lib/agent/tools.ts b/apps/web/lib/agent/tools.ts
new file mode 100644
index 00000000..23986a7e
--- /dev/null
+++ b/apps/web/lib/agent/tools.ts
@@ -0,0 +1,164 @@
+import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk"
+import { z } from "zod"
+import type { SearchMemoriesOutput, AddMemoryOutput } from "./types"
+
+const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai"
+
+interface ToolContext {
+ cookies: string
+ projectId: string
+}
+
+export function createSupermemoryMcpServer(context: ToolContext) {
+ const searchMemoriesTool = tool(
+ "searchMemories",
+ "Search through user's memories/documents to find relevant information. Use this when you need context about something the user has saved.",
+ {
+ query: z.string().describe("The search query to find relevant memories"),
+ limit: z.number().min(1).max(20).optional().describe("Maximum number of results to return (default: 5)"),
+ containerTags: z.array(z.string()).optional().describe("Filter by container tags"),
+ },
+ async (args): Promise<{ content: Array<{ type: "text"; text: string }> }> => {
+ try {
+ const response = await fetch(`${BACKEND_URL}/v3/search`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Cookie: context.cookies,
+ "X-Project-Id": context.projectId,
+ },
+ body: JSON.stringify({
+ q: args.query,
+ limit: args.limit ?? 5,
+ containerTags: args.containerTags,
+ }),
+ })
+
+ if (!response.ok) {
+ throw new Error(`Search failed: ${response.statusText}`)
+ }
+
+ const data = await response.json()
+ const results = data.results ?? data.documents ?? []
+
+ const output: SearchMemoriesOutput = {
+ count: results.length,
+ results: results.map((r: {
+ id?: string
+ documentId?: string
+ title?: string
+ content?: string
+ chunk?: string
+ url?: string
+ score?: number
+ }) => ({
+ documentId: r.id ?? r.documentId ?? "",
+ title: r.title,
+ content: r.content ?? r.chunk,
+ url: r.url,
+ score: r.score,
+ })),
+ }
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(output),
+ },
+ ],
+ }
+ } catch (error) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify({
+ count: 0,
+ results: [],
+ error: error instanceof Error ? error.message : "Search failed",
+ }),
+ },
+ ],
+ }
+ }
+ }
+ )
+
+ const addMemoryTool = tool(
+ "addMemory",
+ "Add a new memory/document for the user. Use this when the user wants to save information for later.",
+ {
+ content: z.string().describe("The content to save as a memory"),
+ title: z.string().optional().describe("Optional title for the memory"),
+ url: z.string().optional().describe("Optional URL associated with the content"),
+ containerTags: z.array(z.string()).optional().describe("Tags to organize the memory"),
+ },
+ async (args): Promise<{ content: Array<{ type: "text"; text: string }> }> => {
+ try {
+ const response = await fetch(`${BACKEND_URL}/v3/documents`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Cookie: context.cookies,
+ "X-Project-Id": context.projectId,
+ },
+ body: JSON.stringify({
+ content: args.content,
+ title: args.title,
+ url: args.url,
+ containerTags: args.containerTags,
+ }),
+ })
+
+ if (!response.ok) {
+ throw new Error(`Add memory failed: ${response.statusText}`)
+ }
+
+ const data = await response.json()
+ const output: AddMemoryOutput = {
+ id: data.id ?? data.documentId ?? "",
+ status: "success",
+ message: "Memory added successfully",
+ }
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(output),
+ },
+ ],
+ }
+ } catch (error) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify({
+ id: "",
+ status: "error",
+ message: error instanceof Error ? error.message : "Failed to add memory",
+ }),
+ },
+ ],
+ }
+ }
+ }
+ )
+
+ return createSdkMcpServer({
+ name: "supermemory",
+ version: "1.0.0",
+ tools: [searchMemoriesTool, addMemoryTool],
+ })
+}
+
+export const SUPERMEMORY_SYSTEM_PROMPT = `You are Nova, a helpful AI assistant for Supermemory. You help users search and manage their memories - content they've saved from the web, notes, documents, and other information.
+
+When a user asks a question:
+1. First search their memories to find relevant context using the searchMemories tool
+2. Use that context to provide helpful, accurate answers
+3. If the user wants to save something, use the addMemory tool
+
+Be conversational, helpful, and concise. Reference specific memories when relevant.`
diff --git a/apps/web/lib/agent/types.ts b/apps/web/lib/agent/types.ts
new file mode 100644
index 00000000..9a85026d
--- /dev/null
+++ b/apps/web/lib/agent/types.ts
@@ -0,0 +1,75 @@
+export interface AgentMessage {
+ id: string
+ role: "user" | "assistant"
+ content: string
+ parts: AgentMessagePart[]
+ createdAt?: Date
+}
+
+export type AgentMessagePart =
+ | { type: "text"; text: string }
+ | {
+ type: "tool-searchMemories"
+ state: "input-available" | "input-streaming" | "output-available" | "output-error"
+ input?: SearchMemoriesInput
+ output?: SearchMemoriesOutput
+ }
+ | {
+ type: "tool-addMemory"
+ state: "input-available" | "input-streaming" | "output-available" | "output-error"
+ input?: AddMemoryInput
+ output?: AddMemoryOutput
+ }
+
+export interface SearchMemoriesInput {
+ query: string
+ limit?: number
+ containerTags?: string[]
+}
+
+export interface SearchMemoriesOutput {
+ count: number
+ results: MemoryResult[]
+}
+
+export interface AddMemoryInput {
+ content: string
+ title?: string
+ url?: string
+ containerTags?: string[]
+}
+
+export interface AddMemoryOutput {
+ id: string
+ status: "success" | "error"
+ message?: string
+}
+
+export interface MemoryResult {
+ documentId: string
+ title?: string
+ content?: string
+ url?: string
+ score?: number
+}
+
+export interface ChatMetadata {
+ projectId: string
+ model?: string
+ chatId?: string
+}
+
+export interface ChatRequest {
+ messages: Array<{ role: "user" | "assistant"; content: string }>
+ metadata: ChatMetadata
+}
+
+export interface FollowUpRequest {
+ messages: Array<{ role: "user" | "assistant"; content: string }>
+}
+
+export interface TitleRequest {
+ messages: Array<{ role: "user" | "assistant"; content: string }>
+}
+
+export type ClaudeAgentStatus = "idle" | "submitted" | "streaming" | "error"
diff --git a/apps/web/package.json b/apps/web/package.json
index aa37a4bd..624d68f7 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -18,6 +18,7 @@
"@ai-sdk/google": "^3.0.9",
"@ai-sdk/react": "^3.0.39",
"@ai-sdk/xai": "^3.0.23",
+ "@anthropic-ai/claude-agent-sdk": "^0.2.12",
"@better-fetch/fetch": "^1.1.18",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
diff --git a/packages/ui/components/badge.tsx b/packages/ui/components/badge.tsx
index c5a03c29..c3244a04 100644
--- a/packages/ui/components/badge.tsx
+++ b/packages/ui/components/badge.tsx
@@ -31,7 +31,7 @@ function Badge({
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
- const Comp = asChild ? Slot : "span";
+ const Comp = (asChild ? Slot : "span") as React.ElementType;
return (
<Comp
diff --git a/packages/ui/components/breadcrumb.tsx b/packages/ui/components/breadcrumb.tsx
index 1582ccdb..940f030d 100644
--- a/packages/ui/components/breadcrumb.tsx
+++ b/packages/ui/components/breadcrumb.tsx
@@ -37,7 +37,7 @@ function BreadcrumbLink({
}: React.ComponentProps<"a"> & {
asChild?: boolean;
}) {
- const Comp = asChild ? Slot : "a";
+ const Comp = (asChild ? Slot : "a") as React.ElementType;
return (
<Comp
diff --git a/packages/ui/components/button.tsx b/packages/ui/components/button.tsx
index ae77aa5d..6784d8c3 100644
--- a/packages/ui/components/button.tsx
+++ b/packages/ui/components/button.tsx
@@ -54,7 +54,7 @@ function Button({
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
- const Comp = asChild ? Slot : "button";
+ const Comp = (asChild ? Slot : "button") as React.ElementType;
return (
<Comp
diff --git a/packages/ui/components/sidebar.tsx b/packages/ui/components/sidebar.tsx
index a2406d18..cb322745 100644
--- a/packages/ui/components/sidebar.tsx
+++ b/packages/ui/components/sidebar.tsx
@@ -397,7 +397,7 @@ function SidebarGroupLabel({
asChild = false,
...props
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
- const Comp = asChild ? Slot : "div";
+ const Comp = (asChild ? Slot : "div") as React.ElementType;
return (
<Comp
@@ -418,7 +418,7 @@ function SidebarGroupAction({
asChild = false,
...props
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
- const Comp = asChild ? Slot : "button";
+ const Comp = (asChild ? Slot : "button") as React.ElementType;
return (
<Comp
@@ -507,7 +507,7 @@ function SidebarMenuButton({
isActive?: boolean;
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants>) {
- const Comp = asChild ? Slot : "button";
+ const Comp = (asChild ? Slot : "button") as React.ElementType;
const { isMobile, state } = useSidebar();
const button = (
@@ -553,7 +553,7 @@ function SidebarMenuAction({
asChild?: boolean;
showOnHover?: boolean;
}) {
- const Comp = asChild ? Slot : "button";
+ const Comp = (asChild ? Slot : "button") as React.ElementType;
return (
<Comp
@@ -676,7 +676,7 @@ function SidebarMenuSubButton({
size?: "sm" | "md";
isActive?: boolean;
}) {
- const Comp = asChild ? Slot : "a";
+ const Comp = (asChild ? Slot : "a") as React.ElementType;
return (
<Comp
diff --git a/packages/ui/text/heading/heading-h1-bold.tsx b/packages/ui/text/heading/heading-h1-bold.tsx
index b76f3b9b..bacf9bf5 100644
--- a/packages/ui/text/heading/heading-h1-bold.tsx
+++ b/packages/ui/text/heading/heading-h1-bold.tsx
@@ -1,14 +1,19 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function HeadingH1Bold({
className,
asChild,
...props
-}: React.ComponentProps<"h1"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h1";
+}: Omit<React.ComponentProps<"h1">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-sm sm:text-base md:text-lg lg:text-xl font-bold leading-[32px] tracking-[-0.4px]", className)} {...props} />;
+ }
return (
- <Comp
+ <h1
className={cn(
"text-sm sm:text-base md:text-lg lg:text-xl font-bold leading-[32px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/heading/heading-h1-medium.tsx b/packages/ui/text/heading/heading-h1-medium.tsx
index 5724e1f1..9a75d7c7 100644
--- a/packages/ui/text/heading/heading-h1-medium.tsx
+++ b/packages/ui/text/heading/heading-h1-medium.tsx
@@ -1,14 +1,19 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function HeadingH1Medium({
className,
asChild,
...props
-}: React.ComponentProps<"h1"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h1";
+}: Omit<React.ComponentProps<"h1">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-sm sm:text-base md:text-lg lg:text-xl font-medium leading-[32px] tracking-[-0.4px]", className)} {...props} />;
+ }
return (
- <Comp
+ <h1
className={cn(
"text-sm sm:text-base md:text-lg lg:text-xl font-medium leading-[32px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/heading/heading-h2-bold.tsx b/packages/ui/text/heading/heading-h2-bold.tsx
index 6711de50..91e01d34 100644
--- a/packages/ui/text/heading/heading-h2-bold.tsx
+++ b/packages/ui/text/heading/heading-h2-bold.tsx
@@ -1,14 +1,19 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function HeadingH2Bold({
className,
asChild,
...props
-}: React.ComponentProps<"h2"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h2";
+}: Omit<React.ComponentProps<"h2">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-xs sm:text-sm md:text-base lg:text-lg font-bold leading-[30px] tracking-[-0.4px]", className)} {...props} />;
+ }
return (
- <Comp
+ <h2
className={cn(
"text-xs sm:text-sm md:text-base lg:text-lg font-bold leading-[30px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/heading/heading-h2-medium.tsx b/packages/ui/text/heading/heading-h2-medium.tsx
index afac0a42..8316511a 100644
--- a/packages/ui/text/heading/heading-h2-medium.tsx
+++ b/packages/ui/text/heading/heading-h2-medium.tsx
@@ -1,14 +1,19 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function HeadingH2Medium({
className,
asChild,
...props
-}: React.ComponentProps<"h2"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h2";
+}: Omit<React.ComponentProps<"h2">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-xs sm:text-sm md:text-base lg:text-lg font-medium leading-[30px] tracking-[-0.4px]", className)} {...props} />;
+ }
return (
- <Comp
+ <h2
className={cn(
"text-xs sm:text-sm md:text-base lg:text-lg font-medium leading-[30px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/heading/heading-h3-bold.tsx b/packages/ui/text/heading/heading-h3-bold.tsx
index be15a33c..7a9b72a8 100644
--- a/packages/ui/text/heading/heading-h3-bold.tsx
+++ b/packages/ui/text/heading/heading-h3-bold.tsx
@@ -1,14 +1,19 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function HeadingH3Bold({
className,
asChild,
...props
-}: React.ComponentProps<"h3"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h3";
+}: Omit<React.ComponentProps<"h3">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.625rem] sm:text-xs md:text-sm lg:text-base font-bold leading-[28px] tracking-[-0.4px]", className)} {...props} />;
+ }
return (
- <Comp
+ <h3
className={cn(
"text-[0.625rem] sm:text-xs md:text-sm lg:text-base font-bold leading-[28px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/heading/heading-h3-medium.tsx b/packages/ui/text/heading/heading-h3-medium.tsx
index cdaa24a2..be9c44be 100644
--- a/packages/ui/text/heading/heading-h3-medium.tsx
+++ b/packages/ui/text/heading/heading-h3-medium.tsx
@@ -1,14 +1,19 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function HeadingH3Medium({
className,
asChild,
...props
-}: React.ComponentProps<"h3"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h3";
+}: Omit<React.ComponentProps<"h3">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.625rem] sm:text-xs md:text-sm lg:text-base font-medium leading-[28px] tracking-[-0.4px]", className)} {...props} />;
+ }
return (
- <Comp
+ <h3
className={cn(
"text-[0.625rem] sm:text-xs md:text-sm lg:text-base font-medium leading-[28px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/heading/heading-h4-bold.tsx b/packages/ui/text/heading/heading-h4-bold.tsx
index 5e99c031..e3596710 100644
--- a/packages/ui/text/heading/heading-h4-bold.tsx
+++ b/packages/ui/text/heading/heading-h4-bold.tsx
@@ -1,14 +1,19 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function HeadingH4Bold({
className,
asChild,
...props
-}: React.ComponentProps<"h4"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h4";
+}: Omit<React.ComponentProps<"h4">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.5rem] sm:text-[0.625rem] md:text-xs lg:text-sm font-bold leading-[24px] tracking-[-0.4px]", className)} {...props} />;
+ }
return (
- <Comp
+ <h4
className={cn(
"text-[0.5rem] sm:text-[0.625rem] md:text-xs lg:text-sm font-bold leading-[24px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/heading/heading-h4-medium.tsx b/packages/ui/text/heading/heading-h4-medium.tsx
index 1a536508..d87b2c05 100644
--- a/packages/ui/text/heading/heading-h4-medium.tsx
+++ b/packages/ui/text/heading/heading-h4-medium.tsx
@@ -1,14 +1,19 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function HeadingH4Medium({
className,
asChild,
...props
-}: React.ComponentProps<"h4"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h4";
+}: Omit<React.ComponentProps<"h4">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.5rem] sm:text-[0.625rem] md:text-xs lg:text-sm font-medium leading-[24px] tracking-[-0.4px]", className)} {...props} />;
+ }
return (
- <Comp
+ <h4
className={cn(
"text-[0.5rem] sm:text-[0.625rem] md:text-xs lg:text-sm font-medium leading-[24px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/label/label-1-medium.tsx b/packages/ui/text/label/label-1-medium.tsx
index e599f3e7..0a9782a2 100644
--- a/packages/ui/text/label/label-1-medium.tsx
+++ b/packages/ui/text/label/label-1-medium.tsx
@@ -1,19 +1,15 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Label1Medium({
className,
asChild,
...props
-}: React.ComponentProps<"p"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "p";
- return (
- <Comp
- className={cn(
- "text-[0.875rem] md:text-[1rem] font-medium leading-[1.5rem] tracking-[-0.4px]",
- className,
- )}
- {...props}
- />
- );
+}: Omit<React.ComponentProps<"p">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.875rem] md:text-[1rem] font-medium leading-[1.5rem] tracking-[-0.4px]", className)} {...props} />;
+ }
+ return <p className={cn("text-[0.875rem] md:text-[1rem] font-medium leading-[1.5rem] tracking-[-0.4px]", className)} {...props} />;
}
diff --git a/packages/ui/text/label/label-1-regular.tsx b/packages/ui/text/label/label-1-regular.tsx
index ad9ea319..6bfe79cc 100644
--- a/packages/ui/text/label/label-1-regular.tsx
+++ b/packages/ui/text/label/label-1-regular.tsx
@@ -1,19 +1,15 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Label1Regular({
className,
asChild,
...props
-}: React.ComponentProps<"p"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "p";
- return (
- <Comp
- className={cn(
- "text-[0.875rem] md:text-[1rem] font-normal leading-[1.5rem] tracking-[-0.4px]",
- className,
- )}
- {...props}
- />
- );
+}: Omit<React.ComponentProps<"p">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.875rem] md:text-[1rem] font-normal leading-[1.5rem] tracking-[-0.4px]", className)} {...props} />;
+ }
+ return <p className={cn("text-[0.875rem] md:text-[1rem] font-normal leading-[1.5rem] tracking-[-0.4px]", className)} {...props} />;
}
diff --git a/packages/ui/text/label/label-2-medium.tsx b/packages/ui/text/label/label-2-medium.tsx
index 89aa2f2d..6fbfadbc 100644
--- a/packages/ui/text/label/label-2-medium.tsx
+++ b/packages/ui/text/label/label-2-medium.tsx
@@ -1,19 +1,15 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Label2Medium({
className,
asChild,
...props
-}: React.ComponentProps<"p"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "p";
- return (
- <Comp
- className={cn(
- "text-[0.25rem] sm:text-[0.375rem] md:text-[0.5rem] lg:text-[0.625rem] font-medium leading-[18px] tracking-[-0.4px] text-muted-foreground",
- className,
- )}
- {...props}
- />
- );
+}: Omit<React.ComponentProps<"p">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.25rem] sm:text-[0.375rem] md:text-[0.5rem] lg:text-[0.625rem] font-medium leading-[18px] tracking-[-0.4px] text-muted-foreground", className)} {...props} />;
+ }
+ return <p className={cn("text-[0.25rem] sm:text-[0.375rem] md:text-[0.5rem] lg:text-[0.625rem] font-medium leading-[18px] tracking-[-0.4px] text-muted-foreground", className)} {...props} />;
}
diff --git a/packages/ui/text/label/label-2-regular.tsx b/packages/ui/text/label/label-2-regular.tsx
index 951dc5bf..26ac60ea 100644
--- a/packages/ui/text/label/label-2-regular.tsx
+++ b/packages/ui/text/label/label-2-regular.tsx
@@ -1,19 +1,15 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Label2Regular({
className,
asChild,
...props
-}: React.ComponentProps<"p"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "p";
- return (
- <Comp
- className={cn(
- "text-[0.25rem] sm:text-[0.375rem] md:text-[0.5rem] lg:text-[0.625rem] font-normal leading-[18px] tracking-[-0.4px] text-muted-foreground",
- className,
- )}
- {...props}
- />
- );
+}: Omit<React.ComponentProps<"p">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.25rem] sm:text-[0.375rem] md:text-[0.5rem] lg:text-[0.625rem] font-normal leading-[18px] tracking-[-0.4px] text-muted-foreground", className)} {...props} />;
+ }
+ return <p className={cn("text-[0.25rem] sm:text-[0.375rem] md:text-[0.5rem] lg:text-[0.625rem] font-normal leading-[18px] tracking-[-0.4px] text-muted-foreground", className)} {...props} />;
}
diff --git a/packages/ui/text/label/label-3-medium.tsx b/packages/ui/text/label/label-3-medium.tsx
index 5308452e..e4baabfd 100644
--- a/packages/ui/text/label/label-3-medium.tsx
+++ b/packages/ui/text/label/label-3-medium.tsx
@@ -1,19 +1,15 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Label3Medium({
className,
asChild,
...props
-}: React.ComponentProps<"p"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "p";
- return (
- <Comp
- className={cn(
- "text-[0.125rem] sm:text-[0.25rem] md:text-[0.375rem] lg:text-[0.5rem] font-medium leading-[16px] tracking-[-0.2px] text-muted-foreground",
- className,
- )}
- {...props}
- />
- );
+}: Omit<React.ComponentProps<"p">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.125rem] sm:text-[0.25rem] md:text-[0.375rem] lg:text-[0.5rem] font-medium leading-[16px] tracking-[-0.2px] text-muted-foreground", className)} {...props} />;
+ }
+ return <p className={cn("text-[0.125rem] sm:text-[0.25rem] md:text-[0.375rem] lg:text-[0.5rem] font-medium leading-[16px] tracking-[-0.2px] text-muted-foreground", className)} {...props} />;
}
diff --git a/packages/ui/text/label/label-3-regular.tsx b/packages/ui/text/label/label-3-regular.tsx
index 9ca0d65e..092721d0 100644
--- a/packages/ui/text/label/label-3-regular.tsx
+++ b/packages/ui/text/label/label-3-regular.tsx
@@ -1,19 +1,15 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Label3Regular({
className,
asChild,
...props
-}: React.ComponentProps<"p"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "p";
- return (
- <Comp
- className={cn(
- "text-[0.125rem] sm:text-[0.25rem] md:text-[0.375rem] lg:text-[0.5rem] font-normal leading-[16px] tracking-[-0.2px] text-muted-foreground",
- className,
- )}
- {...props}
- />
- );
+}: Omit<React.ComponentProps<"p">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return <SlotComp className={cn("text-[0.125rem] sm:text-[0.25rem] md:text-[0.375rem] lg:text-[0.5rem] font-normal leading-[16px] tracking-[-0.2px] text-muted-foreground", className)} {...props} />;
+ }
+ return <p className={cn("text-[0.125rem] sm:text-[0.25rem] md:text-[0.375rem] lg:text-[0.5rem] font-normal leading-[16px] tracking-[-0.2px] text-muted-foreground", className)} {...props} />;
}
diff --git a/packages/ui/text/title/title-1-bold.tsx b/packages/ui/text/title/title-1-bold.tsx
index a87e637b..dfcb7756 100644
--- a/packages/ui/text/title/title-1-bold.tsx
+++ b/packages/ui/text/title/title-1-bold.tsx
@@ -1,14 +1,26 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Title1Bold({
className,
asChild,
...props
-}: React.ComponentProps<"h1"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h1";
+}: Omit<React.ComponentProps<"h1">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return (
+ <SlotComp
+ className={cn(
+ "text-xl sm:text-2xl md:text-3xl lg:text-4xl font-bold leading-[70px] tracking-[-0.8px]",
+ className,
+ )}
+ {...props}
+ />
+ );
+ }
return (
- <Comp
+ <h1
className={cn(
"text-xl sm:text-2xl md:text-3xl lg:text-4xl font-bold leading-[70px] tracking-[-0.8px]",
className,
diff --git a/packages/ui/text/title/title-1-medium.tsx b/packages/ui/text/title/title-1-medium.tsx
index 2ac13520..4face639 100644
--- a/packages/ui/text/title/title-1-medium.tsx
+++ b/packages/ui/text/title/title-1-medium.tsx
@@ -1,14 +1,26 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Title1Medium({
className,
asChild,
...props
-}: React.ComponentProps<"h1"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h1";
+}: Omit<React.ComponentProps<"h1">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return (
+ <SlotComp
+ className={cn(
+ "text-xl sm:text-2xl md:text-3xl lg:text-4xl font-medium leading-[70px] tracking-[-0.8px]",
+ className,
+ )}
+ {...props}
+ />
+ );
+ }
return (
- <Comp
+ <h1
className={cn(
"text-xl sm:text-2xl md:text-3xl lg:text-4xl font-medium leading-[70px] tracking-[-0.8px]",
className,
diff --git a/packages/ui/text/title/title-2-bold.tsx b/packages/ui/text/title/title-2-bold.tsx
index 38bbe34e..745f0da4 100644
--- a/packages/ui/text/title/title-2-bold.tsx
+++ b/packages/ui/text/title/title-2-bold.tsx
@@ -1,14 +1,26 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Title2Bold({
className,
asChild,
...props
-}: React.ComponentProps<"h2"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h2";
+}: Omit<React.ComponentProps<"h2">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return (
+ <SlotComp
+ className={cn(
+ "text-lg sm:text-xl md:text-2xl lg:text-3xl font-bold leading-[48px] tracking-[-0.4px]",
+ className,
+ )}
+ {...props}
+ />
+ );
+ }
return (
- <Comp
+ <h2
className={cn(
"text-lg sm:text-xl md:text-2xl lg:text-3xl font-bold leading-[48px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/title/title-2-medium.tsx b/packages/ui/text/title/title-2-medium.tsx
index c5a5deae..a3b6b473 100644
--- a/packages/ui/text/title/title-2-medium.tsx
+++ b/packages/ui/text/title/title-2-medium.tsx
@@ -1,14 +1,26 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Title2Medium({
className,
asChild,
...props
-}: React.ComponentProps<"h2"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h2";
+}: Omit<React.ComponentProps<"h2">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return (
+ <SlotComp
+ className={cn(
+ "text-lg sm:text-xl md:text-2xl lg:text-3xl font-medium leading-[32px] md:leading-[48px] tracking-[-0.4px]",
+ className,
+ )}
+ {...props}
+ />
+ );
+ }
return (
- <Comp
+ <h2
className={cn(
"text-lg sm:text-xl md:text-2xl lg:text-3xl font-medium leading-[32px] md:leading-[48px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/title/title-3-bold.tsx b/packages/ui/text/title/title-3-bold.tsx
index cf9ab777..d3fb0207 100644
--- a/packages/ui/text/title/title-3-bold.tsx
+++ b/packages/ui/text/title/title-3-bold.tsx
@@ -1,14 +1,26 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Title3Bold({
className,
asChild,
...props
-}: React.ComponentProps<"h3"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h3";
+}: Omit<React.ComponentProps<"h3">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return (
+ <SlotComp
+ className={cn(
+ "text-base sm:text-lg md:text-xl lg:text-2xl font-bold leading-[40px] tracking-[-0.4px]",
+ className,
+ )}
+ {...props}
+ />
+ );
+ }
return (
- <Comp
+ <h3
className={cn(
"text-base sm:text-lg md:text-xl lg:text-2xl font-bold leading-[40px] tracking-[-0.4px]",
className,
diff --git a/packages/ui/text/title/title-3-medium.tsx b/packages/ui/text/title/title-3-medium.tsx
index f862e618..b71d65f1 100644
--- a/packages/ui/text/title/title-3-medium.tsx
+++ b/packages/ui/text/title/title-3-medium.tsx
@@ -1,14 +1,26 @@
import { cn } from "@lib/utils";
-import { Root } from "@radix-ui/react-slot";
+import { Slot } from "@radix-ui/react-slot";
+
+const SlotComp = Slot as React.ComponentType<React.HTMLAttributes<HTMLElement> & { children?: React.ReactNode }>;
export function Title3Medium({
className,
asChild,
...props
-}: React.ComponentProps<"h3"> & { asChild?: boolean }) {
- const Comp = asChild ? Root : "h3";
+}: Omit<React.ComponentProps<"h3">, "ref"> & { asChild?: boolean }) {
+ if (asChild) {
+ return (
+ <SlotComp
+ className={cn(
+ "text-base sm:text-lg md:text-xl lg:text-2xl font-medium leading-[40px] tracking-[-0.4px]",
+ className,
+ )}
+ {...props}
+ />
+ );
+ }
return (
- <Comp
+ <h3
className={cn(
"text-base sm:text-lg md:text-xl lg:text-2xl font-medium leading-[40px] tracking-[-0.4px]",
className,