diff options
| author | MaheshtheDev <[email protected]> | 2026-01-15 21:53:53 +0000 |
|---|---|---|
| committer | MaheshtheDev <[email protected]> | 2026-01-15 21:53:53 +0000 |
| commit | 59c294b29998a861a870629d513f6da74b3d76ac (patch) | |
| tree | 265c9fe27984c6d322ba2e51b0fc91bc2302698d | |
| parent | chore: quick bugs squash across the elements and added few more changes (#671) (diff) | |
| download | supermemory-59c294b29998a861a870629d513f6da74b3d76ac.tar.xz supermemory-59c294b29998a861a870629d513f6da74b3d76ac.zip | |
feat: deep-research on user profile and tiptap integration (#672)01-14-feat_deep-research_on_user_profile
deep-research on user profile
add novel integration
tiptap 3.x integration
59 files changed, 1805 insertions, 365 deletions
diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 44f4ab85..8564713b 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -3,21 +3,9 @@ name: Claude Code Review on: pull_request: types: [opened, synchronize] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" jobs: claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - runs-on: ubuntu-latest permissions: contents: read @@ -38,17 +26,53 @@ jobs: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} use_sticky_comment: true prompt: | - Please review this pull request and provide feedback on: - - Code quality and best practices - - Potential bugs or issues - - Performance considerations - - Security concerns - - Test coverage + You are reviewing a PR for supermemory - a Turbo monorepo with multiple apps and packages. + + ## Repository Structure Context + + **Apps (apps/):** + - `web` - Next.js web application (no tests) + - `mcp` - Model Context Protocol server on Cloudflare Workers (no tests) + - `browser-extension` - WXT-based browser extension (no tests) + - `raycast-extension` - Raycast app extension (no tests) + - `docs` - Mintlify documentation site + + **Published Packages (packages/) - with tests:** + - `tools` - AI SDK memory tools (Vitest) + - `ai-sdk` - supermemory AI SDK wrapper (Vitest) + - `openai-sdk-python` - Python OpenAI integration (pytest) + - `pipecat-sdk-python` - Python Pipecat integration (pytest) + + **Internal Packages (packages/) - no tests:** + - `ui` - Shared React/Radix UI components + - `lib` - Shared utilities + - `hooks` - Custom React hooks + - `memory-graph` - D3-based graph visualization + - `validation` - Zod schemas + + ## Review Instructions + + 1. First, run `gh pr diff` to see what files changed + + 2. Based on the changes, review for: + - **Code quality**: Follow Biome linting rules (double quotes, tabs, no default exports) + - **Type safety**: Ensure proper TypeScript usage + - **Security**: Check for injection vulnerabilities, credential exposure, unsafe patterns + - **Performance**: Look for unnecessary re-renders (React), N+1 queries, memory leaks + + 3. **Test coverage** - ONLY review if changes touch these packages: + - `packages/tools/**` or `packages/ai-sdk/**` → Check for Vitest tests + - `packages/openai-sdk-python/**` or `packages/pipecat-sdk-python/**` → Check for pytest tests + - Skip test coverage review for apps and other packages (they have no test setup) + + 4. **Package-specific concerns:** + - Published packages (tools, ai-sdk, memory-graph, *-python): Check for breaking API changes + - UI package: Check accessibility (a11y) and component API consistency + - Web app: Check for proper data fetching patterns (TanStack Query), Zustand state management + - MCP server: Check Hono routing and Cloudflare Workers compatibility - Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. + 5. Be concise and actionable. Focus on issues that matter, not style nitpicks (Biome handles that). Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' diff --git a/apps/web/.env.example b/apps/web/.env.example index 969d7377..aaf5fab4 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -1,2 +1,4 @@ NEXT_PUBLIC_BACKEND_URL=https://api.supermemory.ai -NEXT_PUBLIC_POSTHOG_KEY=
\ No newline at end of file +NEXT_PUBLIC_POSTHOG_KEY= +EXA_API_KEY= +XAI_API_KEY=
\ No newline at end of file diff --git a/apps/web/app/(auth)/login/new/page.tsx b/apps/web/app/(auth)/login/new/page.tsx index ddea896e..4fc7536b 100644 --- a/apps/web/app/(auth)/login/new/page.tsx +++ b/apps/web/app/(auth)/login/new/page.tsx @@ -14,7 +14,7 @@ import { InitialHeader } from "@/components/initial-header" import { useRouter, useSearchParams } from "next/navigation" import { useState, useEffect } from "react" import { motion } from "motion/react" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { Logo } from "@ui/assets/Logo" @@ -30,7 +30,11 @@ function AnimatedGradientBackground() { }} transition={{ y: { duration: 0.75, ease: "easeOut" }, - opacity: { duration: 8, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" }, + opacity: { + duration: 8, + repeat: Number.POSITIVE_INFINITY, + ease: "easeInOut", + }, }} /> <motion.div @@ -42,7 +46,11 @@ function AnimatedGradientBackground() { }} transition={{ y: { duration: 0.75, ease: "easeOut" }, - opacity: { duration: 8, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" }, + opacity: { + duration: 8, + repeat: Number.POSITIVE_INFINITY, + ease: "easeInOut", + }, }} /> <motion.div diff --git a/apps/web/app/api/exa/fetch-content/route.ts b/apps/web/app/api/onboarding/extract-content/route.ts index 6cdb40d5..6cdb40d5 100644 --- a/apps/web/app/api/exa/fetch-content/route.ts +++ b/apps/web/app/api/onboarding/extract-content/route.ts diff --git a/apps/web/app/api/onboarding/research/route.ts b/apps/web/app/api/onboarding/research/route.ts new file mode 100644 index 00000000..d0d6eded --- /dev/null +++ b/apps/web/app/api/onboarding/research/route.ts @@ -0,0 +1,81 @@ +import { xai } from "@ai-sdk/xai" +import { generateText } from "ai" + +interface ResearchRequest { + xUrl: string + name?: string + email?: string +} + +// prompt to get user context from X/Twitter profile +function finalPrompt(xUrl: string, userContext: string) { + return `You are researching a user based on their X/Twitter profile to help personalize their experience. + +X/Twitter Profile URL: ${xUrl}${userContext} + +Please analyze this X/Twitter profile and provide a comprehensive but concise summary of the user. Include: +- Professional background and current role (if available) +- Key interests and topics they engage with +- Notable projects, achievements, or affiliations +- Their expertise areas +- Any other relevant information that helps understand who they are + +Format the response as clear, readable paragraphs. Focus on factual information from their profile. If certain information is not available, skip that section rather than speculating.` +} + +export async function POST(req: Request) { + try { + const { xUrl, name, email }: ResearchRequest = await req.json() + + if (!xUrl?.trim()) { + return Response.json( + { error: "X/Twitter URL is required" }, + { status: 400 }, + ) + } + + const lowerUrl = xUrl.toLowerCase() + if (!lowerUrl.includes("x.com") && !lowerUrl.includes("twitter.com")) { + return Response.json( + { error: "URL must be an X/Twitter profile link" }, + { status: 400 }, + ) + } + + const contextParts: string[] = [] + if (name) contextParts.push(`Name: ${name}`) + if (email) contextParts.push(`Email: ${email}`) + const userContext = + contextParts.length > 0 + ? `\n\nAdditional context about the user:\n${contextParts.join("\n")}` + : "" + + const { text } = await generateText({ + model: xai("grok-4-1-fast-reasoning"), + prompt: finalPrompt(xUrl, userContext), + providerOptions: { + xai: { + searchParameters: { + mode: "on", + sources: [ + { + type: "web", + safeSearch: true, + }, + { + type: "x", + includedXHandles: [lowerUrl.replace("https://x.com/", "").replace("https://twitter.com/", "")], + postFavoriteCount: 10, + }, + ], + }, + }, + }, + }) + + return Response.json({ text }) + } catch (error) { + console.error("Research API error:", error) + return Response.json({ error: "Internal server error" }, { status: 500 }) + } +} diff --git a/apps/web/app/new/onboarding/page.tsx b/apps/web/app/new/onboarding/page.tsx index 57b5b4fb..1b4962e4 100644 --- a/apps/web/app/new/onboarding/page.tsx +++ b/apps/web/app/new/onboarding/page.tsx @@ -6,18 +6,18 @@ import { useState, useEffect } from "react" import { useAuth } from "@lib/auth-context" import { cn } from "@lib/utils" -import { InputStep } from "./welcome/input-step" -import { GreetingStep } from "./welcome/greeting-step" -import { WelcomeStep } from "./welcome/welcome-step" -import { ContinueStep } from "./welcome/continue-step" -import { FeaturesStep } from "./welcome/features-step" -import { MemoriesStep } from "./welcome/memories-step" -import { RelatableQuestion } from "./setup/relatable-question" -import { IntegrationsStep } from "./setup/integrations-step" +import { InputStep } from "../../../components/new/onboarding/welcome/input-step" +import { GreetingStep } from "../../../components/new/onboarding/welcome/greeting-step" +import { WelcomeStep } from "../../../components/new/onboarding/welcome/welcome-step" +import { ContinueStep } from "../../../components/new/onboarding/welcome/continue-step" +import { FeaturesStep } from "../../../components/new/onboarding/welcome/features-step" +import { ProfileStep } from "../../../components/new/onboarding/welcome/profile-step" +import { RelatableQuestion } from "../../../components/new/onboarding/setup/relatable-question" +import { IntegrationsStep } from "../../../components/new/onboarding/setup/integrations-step" import { InitialHeader } from "@/components/initial-header" -import { SetupHeader } from "./setup/header" -import { ChatSidebar } from "./setup/chat-sidebar" +import { SetupHeader } from "../../../components/new/onboarding/setup/header" +import { ChatSidebar } from "../../../components/new/onboarding/setup/chat-sidebar" import { Logo } from "@ui/assets/Logo" import NovaOrb from "@/components/nova/nova-orb" import { AnimatedGradientBackground } from "@/components/new/animated-gradient-background" @@ -152,7 +152,7 @@ export default function OnboardingPage() { case "features": return <FeaturesStep key="features" /> case "memories": - return <MemoriesStep key="memories" onSubmit={setMemoryFormData} /> + return <ProfileStep key="profile" onSubmit={setMemoryFormData} /> default: return null } diff --git a/apps/web/app/new/settings/page.tsx b/apps/web/app/new/settings/page.tsx index fbb0bc08..2b5df3ca 100644 --- a/apps/web/app/new/settings/page.tsx +++ b/apps/web/app/new/settings/page.tsx @@ -6,7 +6,7 @@ import { motion } from "motion/react" import NovaOrb from "@/components/nova/nova-orb" import { useState, useEffect, useRef } from "react" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import Account from "@/components/new/settings/account" import Integrations from "@/components/new/settings/integrations" import ConnectionsMCP from "@/components/new/settings/connections-mcp" @@ -175,7 +175,11 @@ export default function SettingsPage() { return ( <div className="h-screen flex flex-col overflow-hidden"> <header className="flex justify-between items-center px-6 py-3 shrink-0"> - <button type="button" onClick={() => router.push("/new")} className="cursor-pointer"> + <button + type="button" + onClick={() => router.push("/new")} + className="cursor-pointer" + > <Logo className="h-7" /> </button> <div className="flex items-center gap-2"> diff --git a/apps/web/components/new/add-document/connections.tsx b/apps/web/components/new/add-document/connections.tsx index 5a89cf70..b146fb4c 100644 --- a/apps/web/components/new/add-document/connections.tsx +++ b/apps/web/components/new/add-document/connections.tsx @@ -10,7 +10,7 @@ import { Check, Loader, Trash2, Zap } from "lucide-react" import { useEffect, useState } from "react" import { toast } from "sonner" import type { z } from "zod" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { Button } from "@ui/components/button" diff --git a/apps/web/components/new/add-document/file.tsx b/apps/web/components/new/add-document/file.tsx index 8e7dc4c4..bb605f03 100644 --- a/apps/web/components/new/add-document/file.tsx +++ b/apps/web/components/new/add-document/file.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { FileIcon } from "lucide-react" import { useHotkeys } from "react-hotkeys-hook" @@ -19,7 +19,12 @@ interface FileContentProps { isOpen?: boolean } -export function FileContent({ onSubmit, onDataChange, isSubmitting, isOpen }: FileContentProps) { +export function FileContent({ + onSubmit, + onDataChange, + isSubmitting, + isOpen, +}: FileContentProps) { const [isDragging, setIsDragging] = useState(false) const [selectedFile, setSelectedFile] = useState<File | null>(null) const [title, setTitle] = useState("") @@ -33,8 +38,16 @@ export function FileContent({ onSubmit, onDataChange, isSubmitting, isOpen }: Fi } } - const updateData = (newFile: File | null, newTitle: string, newDescription: string) => { - onDataChange?.({ file: newFile, title: newTitle, description: newDescription }) + const updateData = ( + newFile: File | null, + newTitle: string, + newDescription: string, + ) => { + onDataChange?.({ + file: newFile, + title: newTitle, + description: newDescription, + }) } const handleFileChange = (file: File | null) => { diff --git a/apps/web/components/new/add-document/index.tsx b/apps/web/components/new/add-document/index.tsx index 9e117912..a99505bd 100644 --- a/apps/web/components/new/add-document/index.tsx +++ b/apps/web/components/new/add-document/index.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useMemo, useCallback } from "react" import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { FileTextIcon, GlobeIcon, diff --git a/apps/web/components/new/add-document/link.tsx b/apps/web/components/new/add-document/link.tsx index 544af86d..2efb67dc 100644 --- a/apps/web/components/new/add-document/link.tsx +++ b/apps/web/components/new/add-document/link.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from "react" import { cn } from "@lib/utils" import { Button } from "@ui/components/button" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { useHotkeys } from "react-hotkeys-hook" import { Image as ImageIcon, Loader2 } from "lucide-react" import { toast } from "sonner" diff --git a/apps/web/components/new/add-document/note.tsx b/apps/web/components/new/add-document/note.tsx index 465e295a..4f833ca2 100644 --- a/apps/web/components/new/add-document/note.tsx +++ b/apps/web/components/new/add-document/note.tsx @@ -1,7 +1,7 @@ "use client" import { useState, useEffect } from "react" -import { useHotkeys } from "react-hotkeys-hook" +import { TextEditor } from "../text-editor" interface NoteContentProps { onSubmit?: (content: string) => void @@ -31,11 +31,6 @@ export function NoteContent({ onContentChange?.(newContent) } - useHotkeys("mod+enter", handleSubmit, { - enabled: isOpen && canSubmit, - enableOnFormTags: ["TEXTAREA"], - }) - // Reset content when modal closes useEffect(() => { if (!isOpen) { @@ -45,12 +40,12 @@ export function NoteContent({ }, [isOpen, onContentChange]) return ( - <textarea - value={content} - onChange={(e) => handleContentChange(e.target.value)} - placeholder="Write your note here..." - disabled={isSubmitting} - className="w-full h-full p-4 mb-4! rounded-[14px] bg-[#14161A] shadow-inside-out resize-none disabled:opacity-50 outline-none" - /> + <div className="p-4 overflow-y-auto flex-1 w-full h-full mb-4! bg-[#14161A] shadow-inside-out rounded-[14px]"> + <TextEditor + content={undefined} + onContentChange={handleContentChange} + onSubmit={handleSubmit} + /> + </div> ) } diff --git a/apps/web/components/new/chat/index.tsx b/apps/web/components/new/chat/index.tsx index 3460c456..08f4e2ef 100644 --- a/apps/web/components/new/chat/index.tsx +++ b/apps/web/components/new/chat/index.tsx @@ -14,7 +14,7 @@ import { SquarePenIcon, } from "lucide-react" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import ChatInput from "./input" import ChatModelSelector from "./model-selector" import { GradientLogo, LogoBgGradient } from "@ui/assets/Logo" @@ -110,7 +110,6 @@ export function ChatSidebar({ }, }, }), - maxSteps: 10, onFinish: async (result) => { if (result.message.role !== "assistant") return @@ -285,14 +284,20 @@ export function ChatSidebar({ useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { + const activeElement = document.activeElement as HTMLElement | null + const isInEditableContext = + activeElement?.tagName === "INPUT" || + activeElement?.tagName === "TEXTAREA" || + activeElement?.isContentEditable || + activeElement?.closest('[contenteditable="true"]') + if ( e.key.toLowerCase() === "t" && !e.metaKey && !e.ctrlKey && !e.altKey && isChatOpen && - document.activeElement?.tagName !== "INPUT" && - document.activeElement?.tagName !== "TEXTAREA" + !isInEditableContext ) { e.preventDefault() handleNewChat() 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 0371a97e..b1923146 100644 --- a/apps/web/components/new/chat/input/chain-of-thought.tsx +++ b/apps/web/components/new/chat/input/chain-of-thought.tsx @@ -2,7 +2,7 @@ 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 "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" interface MemoryResult { documentId?: string diff --git a/apps/web/components/new/chat/input/index.tsx b/apps/web/components/new/chat/input/index.tsx index 4294add5..40c1949d 100644 --- a/apps/web/components/new/chat/input/index.tsx +++ b/apps/web/components/new/chat/input/index.tsx @@ -3,7 +3,7 @@ import { ChevronUpIcon } from "lucide-react" import NovaOrb from "@/components/nova/nova-orb" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { useRef, useState } from "react" import { motion } from "motion/react" import { SendButton, StopButton } from "./actions" diff --git a/apps/web/components/new/chat/message/related-memories.tsx b/apps/web/components/new/chat/message/related-memories.tsx index f3b406db..ad83d03e 100644 --- a/apps/web/components/new/chat/message/related-memories.tsx +++ b/apps/web/components/new/chat/message/related-memories.tsx @@ -1,6 +1,6 @@ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react" import type { UIMessage } from "@ai-sdk/react" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" interface MemoryResult { diff --git a/apps/web/components/new/chat/model-selector.tsx b/apps/web/components/new/chat/model-selector.tsx index 9cecafc2..c1952bea 100644 --- a/apps/web/components/new/chat/model-selector.tsx +++ b/apps/web/components/new/chat/model-selector.tsx @@ -3,7 +3,7 @@ import { useState } from "react" import { cn } from "@lib/utils" import { Button } from "@ui/components/button" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { ChevronDownIcon } from "lucide-react" import { models, type ModelId, modelNames } from "@/lib/models" diff --git a/apps/web/components/new/document-cards/file-preview.tsx b/apps/web/components/new/document-cards/file-preview.tsx index 6f72c0e3..93e53463 100644 --- a/apps/web/components/new/document-cards/file-preview.tsx +++ b/apps/web/components/new/document-cards/file-preview.tsx @@ -3,7 +3,7 @@ import { useState } from "react" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { PDF } from "@ui/assets/icons" import { FileText, Image, Video } from "lucide-react" diff --git a/apps/web/components/new/document-cards/google-docs-preview.tsx b/apps/web/components/new/document-cards/google-docs-preview.tsx index ee3e6c72..6c783c7d 100644 --- a/apps/web/components/new/document-cards/google-docs-preview.tsx +++ b/apps/web/components/new/document-cards/google-docs-preview.tsx @@ -2,7 +2,7 @@ import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema> diff --git a/apps/web/components/new/document-cards/mcp-preview.tsx b/apps/web/components/new/document-cards/mcp-preview.tsx index 55c98169..a8a792fb 100644 --- a/apps/web/components/new/document-cards/mcp-preview.tsx +++ b/apps/web/components/new/document-cards/mcp-preview.tsx @@ -2,7 +2,7 @@ import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { ClaudeDesktopIcon, MCPIcon } from "@ui/assets/icons" @@ -13,11 +13,16 @@ export function McpPreview({ document }: { document: DocumentWithMemories }) { return ( <div className="bg-[#0B1017] p-3 rounded-[18px] space-y-2"> <div className="flex items-center justify-between gap-1"> - <p className={cn(dmSansClassName(), "text-[12px] font-semibold flex items-center gap-1")}> - <ClaudeDesktopIcon className="size-3" /> + <p + className={cn( + dmSansClassName(), + "text-[12px] font-semibold flex items-center gap-1", + )} + > + <ClaudeDesktopIcon className="size-3" /> Claude Desktop </p> - <MCPIcon className="size-6" /> + <MCPIcon className="size-6" /> </div> <div className="space-y-[6px]"> {document.title && ( diff --git a/apps/web/components/new/document-cards/note-preview.tsx b/apps/web/components/new/document-cards/note-preview.tsx index 288b6fa5..2becc237 100644 --- a/apps/web/components/new/document-cards/note-preview.tsx +++ b/apps/web/components/new/document-cards/note-preview.tsx @@ -2,12 +2,54 @@ import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" +import { useMemo } from "react" type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema> type DocumentWithMemories = DocumentsResponse["documents"][0] +type TipTapNode = { + type: string + text?: string + content?: TipTapNode[] + attrs?: Record<string, unknown> +} + +function extractTextFromTipTapContent(content: string): string { + try { + const json = JSON.parse(content) as TipTapNode + return extractTextFromNode(json) + } catch { + return content + } +} + +function extractTextFromNode(node: TipTapNode): string { + if (node.type === "text" && node.text) { + return node.text + } + + if (!node.content) { + return "" + } + + const texts: string[] = [] + for (const child of node.content) { + const text = extractTextFromNode(child) + if (text) { + texts.push(text) + } + } + + const blockTypes = ["paragraph", "heading", "listItem", "blockquote", "codeBlock"] + if (blockTypes.includes(node.type)) { + return `${texts.join("")}\n` + } + + return texts.join("") +} + function NoteIcon() { return ( <svg @@ -79,6 +121,11 @@ function NoteIcon() { } export function NotePreview({ document }: { document: DocumentWithMemories }) { + const previewText = useMemo(() => { + if (!document.content) return "" + return extractTextFromTipTapContent(document.content).trim() + }, [document.content]) + return ( <div className="bg-[#0B1017] p-3 rounded-[18px] space-y-2"> <div className="flex items-center gap-1"> @@ -98,9 +145,9 @@ export function NotePreview({ document }: { document: DocumentWithMemories }) { {document.title} </p> )} - {document.content && ( + {previewText && ( <p className="text-[10px] text-[#737373] line-clamp-4"> - {document.content} + {previewText} </p> )} </div> diff --git a/apps/web/components/new/document-cards/tweet-preview.tsx b/apps/web/components/new/document-cards/tweet-preview.tsx index 7ba9b392..291bc18a 100644 --- a/apps/web/components/new/document-cards/tweet-preview.tsx +++ b/apps/web/components/new/document-cards/tweet-preview.tsx @@ -11,7 +11,7 @@ import { TweetSkeleton, } from "react-tweet" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" export function TweetPreview({ data, diff --git a/apps/web/components/new/document-cards/website-preview.tsx b/apps/web/components/new/document-cards/website-preview.tsx index d61b1ed6..ab3c5ad3 100644 --- a/apps/web/components/new/document-cards/website-preview.tsx +++ b/apps/web/components/new/document-cards/website-preview.tsx @@ -3,7 +3,7 @@ import { useState } from "react" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" type DocumentsResponse = z.infer<typeof DocumentsWithMemoriesResponseSchema> diff --git a/apps/web/components/new/document-cards/youtube-preview.tsx b/apps/web/components/new/document-cards/youtube-preview.tsx index 101376f8..47c9cb5e 100644 --- a/apps/web/components/new/document-cards/youtube-preview.tsx +++ b/apps/web/components/new/document-cards/youtube-preview.tsx @@ -2,7 +2,7 @@ import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { extractYouTubeVideoId } from "../utils" diff --git a/apps/web/components/new/document-modal/document-icon.tsx b/apps/web/components/new/document-modal/document-icon.tsx index f86228eb..cb279e49 100644 --- a/apps/web/components/new/document-modal/document-icon.tsx +++ b/apps/web/components/new/document-modal/document-icon.tsx @@ -80,7 +80,7 @@ const PDFIcon = ({ className }: { className: string }) => { width="8.25216" height="10.2522" filterUnits="userSpaceOnUse" - color-interpolation-filters="sRGB" + colorInterpolationFilters="sRGB" > <feFlood floodOpacity="0" result="BackgroundImageFix" /> <feBlend @@ -158,7 +158,7 @@ const TextDocumentIcon = ({ className }: { className: string }) => { width="18.0253" height="13.8376" filterUnits="userSpaceOnUse" - color-interpolation-filters="sRGB" + colorInterpolationFilters="sRGB" > <feFlood floodOpacity="0" result="BackgroundImageFix" /> <feBlend diff --git a/apps/web/components/new/document-modal/index.tsx b/apps/web/components/new/document-modal/index.tsx index 74d4e178..bd8ec05d 100644 --- a/apps/web/components/new/document-modal/index.tsx +++ b/apps/web/components/new/document-modal/index.tsx @@ -2,19 +2,24 @@ import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" -import { ArrowUpRightIcon, XIcon } from "lucide-react" +import { ArrowUpRightIcon, XIcon, Loader2 } from "lucide-react" import type { z } from "zod" import * as DialogPrimitive from "@radix-ui/react-dialog" import { cn } from "@lib/utils" import dynamic from "next/dynamic" import { Title } from "./title" import { Summary as DocumentSummary } from "./summary" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { GraphListMemories, type MemoryEntry } from "./graph-list-memories" import { YoutubeVideo } from "./content/yt-video" import { TweetContent } from "./content/tweet" -import { isTwitterUrl } from "@/utils/url-helpers" +import { isTwitterUrl } from "@/lib/url-helpers" import { NotionDoc } from "./content/notion-doc" +import { TextEditor } from "../text-editor" +import { useState, useEffect, useCallback, useMemo } from "react" +import { motion, AnimatePresence } from "motion/react" +import { Button } from "@repo/ui/components/button" +import { useDocumentMutations } from "@/hooks/use-document-mutations" // Dynamically importing to prevent DOMMatrix error const PdfViewer = dynamic( @@ -43,7 +48,49 @@ export function DocumentModal({ isOpen, onClose, }: DocumentModalProps) { - console.log(_document) + const { updateMutation } = useDocumentMutations() + + const { initialEditorContent, initialEditorString } = useMemo(() => { + const content = _document?.content as string | null | undefined + return { + initialEditorContent: content ?? undefined, + initialEditorString: content ?? "", + } + }, [_document?.content]) + + const [draftContentString, setDraftContentString] = + useState(initialEditorString) + const [editorResetNonce, setEditorResetNonce] = useState(0) + const [lastSavedContent, setLastSavedContent] = useState<string | null>(null) + + const resetEditor = useCallback(() => { + setDraftContentString(initialEditorString) + setEditorResetNonce((n) => n + 1) + setLastSavedContent(null) + }, [initialEditorString]) + + useEffect(() => { + setDraftContentString(initialEditorString) + setEditorResetNonce((n) => n + 1) + setLastSavedContent(null) + }, [initialEditorString]) + + useEffect(() => { + if (!isOpen) resetEditor() + }, [isOpen, resetEditor]) + + const hasUnsavedChanges = + draftContentString !== initialEditorString && + draftContentString !== lastSavedContent + + const handleSave = useCallback(() => { + if (!_document?.id) return + updateMutation.mutate( + { documentId: _document.id, content: draftContentString }, + { onSuccess: (_data, variables) => setLastSavedContent(variables.content) }, + ) + }, [_document?.id, draftContentString, updateMutation]) + return ( <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}> <DialogContent @@ -79,7 +126,7 @@ export function DocumentModal({ </a> )} <DialogPrimitive.Close - className="bg-[#0D121A] w-7 h-7 flex items-center justify-center focus:ring-ring rounded-full transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 shadow-[inset_0_2px_4px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(0,0,0,0.1)]" + className="bg-[#0D121A] w-7 h-7 flex items-center justify-center focus:ring-ring rounded-full transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none cursor-pointer [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 shadow-[inset_0_2px_4px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(0,0,0,0.1)]" data-slot="dialog-close" > <XIcon stroke="#737373" /> @@ -91,7 +138,7 @@ export function DocumentModal({ <div id="document-preview" className={cn( - "bg-[#14161A] rounded-[14px] overflow-hidden flex flex-col shadow-[inset_0_2px_4px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(0,0,0,0.1)]", + "bg-[#14161A] rounded-[14px] overflow-hidden flex flex-col shadow-[inset_0_2px_4px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(0,0,0,0.1)] relative", )} > {(_document?.type === "tweet" || @@ -104,9 +151,66 @@ export function DocumentModal({ /> )} {_document?.type === "text" && ( - <div className="p-4 overflow-y-auto flex-1"> - {_document.content} - </div> + <> + <div className="p-4 overflow-y-auto flex-1 scrollbar-thin"> + <TextEditor + key={`${_document.id}-${editorResetNonce}`} + content={initialEditorContent} + onContentChange={setDraftContentString} + onSubmit={handleSave} + /> + </div> + <AnimatePresence> + {hasUnsavedChanges && ( + <motion.div + initial={{ opacity: 0, y: 20 }} + animate={{ opacity: 1, y: 0 }} + exit={{ opacity: 0, y: 20 }} + transition={{ duration: 0.2 }} + className="absolute bottom-4 left-1/2 -translate-x-1/2 flex items-center gap-3 px-4 py-2 bg-[#1B1F24] rounded-full shadow-[0_4px_20px_rgba(0,0,0,0.4),inset_1px_1px_1px_rgba(255,255,255,0.1)]" + > + <span className="text-sm text-[#737373]"> + Unsaved changes + </span> + <Button + variant="ghost" + size="sm" + onClick={resetEditor} + disabled={updateMutation.isPending} + className="text-[#737373]/80 hover:text-white rounded-full px-3" + > + Cancel + </Button> + <Button + variant="insideOut" + size="sm" + onClick={handleSave} + disabled={updateMutation.isPending} + className="hover:text-white rounded-full px-4" + > + {updateMutation.isPending ? ( + <> + <Loader2 className="size-4 animate-spin mr-1" /> + Saving... + </> + ) : ( + <> + Save + <span + className={cn( + "bg-[#21212180] border border-[#73737333] text-[#737373] rounded-sm px-1 py-0.5 text-[10px] flex items-center justify-center", + dmSansClassName(), + )} + > + ⌘+Enter + </span> + </> + )} + </Button> + </motion.div> + )} + </AnimatePresence> + </> )} {_document?.type === "pdf" && <PdfViewer url={_document.url} />} {_document?.type === "notion_doc" && ( diff --git a/apps/web/components/new/document-modal/title.tsx b/apps/web/components/new/document-modal/title.tsx index b8bdc8bb..066c437b 100644 --- a/apps/web/components/new/document-modal/title.tsx +++ b/apps/web/components/new/document-modal/title.tsx @@ -1,5 +1,5 @@ import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import type { DocumentTypeEnum } from "@repo/validation/schemas" import type { z } from "zod" import { getDocumentIcon } from "@/components/new/document-modal/document-icon" diff --git a/apps/web/components/new/header.tsx b/apps/web/components/new/header.tsx index a5aa47d3..3bb2d115 100644 --- a/apps/web/components/new/header.tsx +++ b/apps/web/components/new/header.tsx @@ -18,7 +18,7 @@ import { } from "lucide-react" import { Button } from "@ui/components/button" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { Tabs, TabsList, TabsTrigger } from "@ui/components/tabs" import { useProjectName } from "@/hooks/use-project-name" import { @@ -259,10 +259,12 @@ export function Header({ onAddMemory, onOpenMCP }: HeaderProps) { <Settings className="h-4 w-4" /> Settings </DropdownMenuItem> - <DropdownMenuItem onClick={() => { - authClient.signOut() - router.push("/login/new") - }}> + <DropdownMenuItem + onClick={() => { + authClient.signOut() + router.push("/login/new") + }} + > <LogOut className="h-4 w-4" /> Logout </DropdownMenuItem> diff --git a/apps/web/components/new/mcp-modal/index.tsx b/apps/web/components/new/mcp-modal/index.tsx index d555ae62..6816c21e 100644 --- a/apps/web/components/new/mcp-modal/index.tsx +++ b/apps/web/components/new/mcp-modal/index.tsx @@ -1,4 +1,4 @@ -import { dmSans125ClassName, dmSansClassName } from "@/utils/fonts" +import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts" import { Dialog, DialogContent, DialogFooter } from "@repo/ui/components/dialog" import { cn } from "@lib/utils" import * as DialogPrimitive from "@radix-ui/react-dialog" diff --git a/apps/web/components/new/mcp-modal/mcp-detail-view.tsx b/apps/web/components/new/mcp-modal/mcp-detail-view.tsx index f1254bcb..5cc2fa68 100644 --- a/apps/web/components/new/mcp-modal/mcp-detail-view.tsx +++ b/apps/web/components/new/mcp-modal/mcp-detail-view.tsx @@ -14,7 +14,7 @@ import Image from "next/image" import { toast } from "sonner" import { analytics } from "@/lib/analytics" import { cn } from "@lib/utils" -import { dmMonoClassName, dmSansClassName } from "@/utils/fonts" +import { dmMonoClassName, dmSansClassName } from "@/lib/fonts" import { SyncLogoIcon } from "@ui/assets/icons" const clients = { @@ -78,7 +78,14 @@ export function MCPSteps({ variant = "full" }: MCPStepsProps) { > <div className="absolute left-4 top-0 w-px bg-[#1E293B] z-10" - style={{ height: activeStep === 3 ? isEmbedded ? "100%" : "calc(100% - 4rem)" : "100%" }} + style={{ + height: + activeStep === 3 + ? isEmbedded + ? "100%" + : "calc(100% - 4rem)" + : "100%", + }} /> <div className="flex items-start space-x-4 z-20"> <button diff --git a/apps/web/components/new/memories-grid.tsx b/apps/web/components/new/memories-grid.tsx index e5972d51..e6095645 100644 --- a/apps/web/components/new/memories-grid.tsx +++ b/apps/web/components/new/memories-grid.tsx @@ -6,11 +6,8 @@ import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import { useInfiniteQuery } from "@tanstack/react-query" import { useCallback, memo, useMemo, useState, useRef } from "react" import type { z } from "zod" -import { - Masonry, - useInfiniteLoader, -} from "masonic" -import { dmSansClassName } from "@/utils/fonts" +import { Masonry, useInfiniteLoader } from "masonic" +import { dmSansClassName } from "@/lib/fonts" import { SuperLoader } from "@/components/superloader" import { cn } from "@lib/utils" import { Button } from "@ui/components/button" @@ -151,9 +148,7 @@ export function MemoriesGrid({ isChatOpen }: { isChatOpen: boolean }) { } return ( - <div - className="h-full" - > + <div className="h-full"> <Button className={cn( dmSansClassName(), diff --git a/apps/web/app/new/onboarding/setup/chat-sidebar.tsx b/apps/web/components/new/onboarding/setup/chat-sidebar.tsx index d35ce73d..59742271 100644 --- a/apps/web/app/new/onboarding/setup/chat-sidebar.tsx +++ b/apps/web/components/new/onboarding/setup/chat-sidebar.tsx @@ -5,10 +5,11 @@ import { motion, AnimatePresence } from "motion/react" import NovaOrb from "@/components/nova/nova-orb" import { Button } from "@ui/components/button" import { PanelRightCloseIcon, SendIcon } from "lucide-react" -import { collectValidUrls } from "@/utils/url-helpers" +import { collectValidUrls } from "@/lib/url-helpers" import { $fetch } from "@lib/api" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" +import { useAuth } from "@lib/auth-context" interface ChatSidebarProps { formData: { @@ -20,6 +21,7 @@ interface ChatSidebarProps { } export function ChatSidebar({ formData }: ChatSidebarProps) { + const { user } = useAuth() const [message, setMessage] = useState("") const [isChatOpen, setIsChatOpen] = useState(true) const [messages, setMessages] = useState< @@ -127,9 +129,60 @@ export function ChatSidebar({ formData }: ChatSidebarProps) { useEffect(() => { if (!formData) return - const urls = collectValidUrls(formData.linkedin, formData.otherLinks) + const formDataMessages: typeof messages = [] + + if (formData.twitter) { + formDataMessages.push({ + message: formData.twitter, + url: formData.twitter, + title: "X/Twitter", + description: formData.twitter, + type: "formData" as const, + }) + } + + if (formData.linkedin) { + formDataMessages.push({ + message: formData.linkedin, + url: formData.linkedin, + title: "LinkedIn", + description: formData.linkedin, + type: "formData" as const, + }) + } + + if (formData.otherLinks.length > 0) { + formData.otherLinks.forEach((link) => { + formDataMessages.push({ + message: link, + url: link, + title: "Link", + description: link, + type: "formData" as const, + }) + }) + } + + if (formData.description?.trim()) { + formDataMessages.push({ + message: formData.description, + title: "Likes", + description: formData.description, + type: "formData" as const, + }) + } + + setMessages(formDataMessages) + + const hasContent = + formData.twitter || + formData.linkedin || + formData.otherLinks.length > 0 || + formData.description?.trim() - console.log("urls", urls) + if (!hasContent) return + + const urls = collectValidUrls(formData.linkedin, formData.otherLinks) const processContent = async () => { setIsLoading(true) @@ -137,17 +190,73 @@ export function ChatSidebar({ formData }: ChatSidebarProps) { try { const documentIds: string[] = [] - // Step 1: Fetch content from Exa if URLs exist + if (formData.description?.trim()) { + try { + const descDocResponse = await $fetch("@post/documents", { + body: { + content: formData.description, + containerTags: ["sm_project_default"], + metadata: { + sm_source: "consumer", + description_source: "user_input", + }, + }, + }) + + if (descDocResponse.data?.id) { + documentIds.push(descDocResponse.data.id) + } + } catch (error) { + console.warn("Error creating description document:", error) + } + } + + if (formData.twitter) { + try { + const researchResponse = await fetch("/api/onboarding/research", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + xUrl: formData.twitter, + name: user?.name, + email: user?.email, + }), + }) + + if (researchResponse.ok) { + const { text } = await researchResponse.json() + + if (text?.trim()) { + const xDocResponse = await $fetch("@post/documents", { + body: { + content: text, + containerTags: ["sm_project_default"], + metadata: { + sm_source: "consumer", + onboarding_source: "x_research", + x_url: formData.twitter, + }, + }, + }) + + if (xDocResponse.data?.id) { + documentIds.push(xDocResponse.data.id) + } + } + } + } catch (error) { + console.warn("Error fetching X research:", error) + } + } + if (urls.length > 0) { - const response = await fetch("/api/exa/fetch-content", { + const response = await fetch("/api/onboarding/extract-content", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ urls }), }) const { results } = await response.json() - console.log("results", results) - // Create documents from Exa results for (const result of results) { try { const docResponse = await $fetch("@post/documents", { @@ -171,95 +280,17 @@ export function ChatSidebar({ formData }: ChatSidebarProps) { } } - // Step 2: Create document from description if it exists - if (formData.description?.trim()) { - try { - const descDocResponse = await $fetch("@post/documents", { - body: { - content: formData.description, - containerTags: ["sm_project_default"], - metadata: { - sm_source: "consumer", - description_source: "user_input", - }, - }, - }) - - if (descDocResponse.data?.id) { - documentIds.push(descDocResponse.data.id) - } - } catch (error) { - console.warn("Error creating description document:", error) - } - } - - // Step 3: Poll for memories or show form data if (documentIds.length > 0) { await pollForMemories(documentIds) - } else { - // No documents created, show form data or waiting - const formDataMessages = [] - - if (formData.twitter) { - formDataMessages.push({ - message: `Twitter: ${formData.twitter}`, - url: formData.twitter, - title: "Twitter Profile", - description: `Twitter: ${formData.twitter}`, - type: "formData" as const, - }) - } - - if (formData.linkedin) { - formDataMessages.push({ - message: `LinkedIn: ${formData.linkedin}`, - url: formData.linkedin, - title: "LinkedIn Profile", - description: `LinkedIn: ${formData.linkedin}`, - type: "formData" as const, - }) - } - - if (formData.otherLinks.length > 0) { - formData.otherLinks.forEach((link) => { - formDataMessages.push({ - message: `Link: ${link}`, - url: link, - title: "Other Link", - description: `Link: ${link}`, - type: "formData" as const, - }) - }) - } - - const waitingMessage = { - message: "Waiting for your input", - url: "", - title: "", - description: "Waiting for your input", - type: "waiting" as const, - } - - setMessages([...formDataMessages, waitingMessage]) } } catch (error) { console.warn("Error processing content:", error) - - const waitingMessage = { - message: "Waiting for your input", - url: "", - title: "", - description: "Waiting for your input", - type: "waiting" as const, - } - - setMessages([waitingMessage]) } setIsLoading(false) } processContent() - }, [formData, pollForMemories]) + }, [formData, pollForMemories, user]) return ( <AnimatePresence mode="wait"> @@ -330,6 +361,39 @@ export function ChatSidebar({ formData }: ChatSidebarProps) { )} <div className="w-px flex-1 bg-[#293952]/40" /> </div> + {msg.type === "formData" && ( + <div className="bg-[#293952]/40 rounded-lg p-2 px-3 space-y-1 flex-1"> + {msg.title && ( + <h3 + className="text-sm font-medium" + style={{ + background: + "linear-gradient(90deg, #369BFD 0%, #36FDFD 30%, #36FDB5 100%)", + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent", + backgroundClip: "text", + }} + > + {msg.title} + </h3> + )} + {msg.url && ( + <a + href={msg.url} + target="_blank" + rel="noopener noreferrer" + className="text-xs text-blue-400 hover:underline break-all block" + > + {msg.url} + </a> + )} + {msg.title === "Likes" && msg.description && ( + <p className="text-xs text-white/70 mt-1"> + {msg.description} + </p> + )} + </div> + )} {msg.type === "memory" && ( <div className="space-y-2 w-full max-h-60 overflow-y-auto scrollbar-thin"> {msg.memories?.map((memory) => ( @@ -383,7 +447,7 @@ export function ChatSidebar({ formData }: ChatSidebarProps) { {isLoading && ( <div className="flex items-center gap-2 text-foreground/50"> <NovaOrb size={28} className="blur-none!" /> - <span className="text-sm">Fetching your memories...</span> + <span className="text-sm">Extracting memories...</span> </div> )} </div> diff --git a/apps/web/app/new/onboarding/setup/header.tsx b/apps/web/components/new/onboarding/setup/header.tsx index 4981c6aa..4981c6aa 100644 --- a/apps/web/app/new/onboarding/setup/header.tsx +++ b/apps/web/components/new/onboarding/setup/header.tsx diff --git a/apps/web/app/new/onboarding/setup/integrations-step.tsx b/apps/web/components/new/onboarding/setup/integrations-step.tsx index ff1ce96f..49e3062a 100644 --- a/apps/web/app/new/onboarding/setup/integrations-step.tsx +++ b/apps/web/components/new/onboarding/setup/integrations-step.tsx @@ -3,10 +3,10 @@ import { useState } from "react" import { Button } from "@ui/components/button" import { MCPDetailView } from "@/components/new/mcp-modal/mcp-detail-view" -import { XBookmarksDetailView } from "@/components/x-bookmarks-detail-view" +import { XBookmarksDetailView } from "@/components/new/onboarding/x-bookmarks-detail-view" import { useRouter } from "next/navigation" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { useOnboardingStorage } from "@hooks/use-onboarding-storage" const integrationCards = [ @@ -164,7 +164,9 @@ export function IntegrationsStep() { <Button variant="link" className="text-white hover:text-gray-300 hover:no-underline cursor-pointer" - onClick={() => router.push("/new/onboarding?flow=setup&step=relatable")} + onClick={() => + router.push("/new/onboarding?flow=setup&step=relatable") + } > ← Back </Button> diff --git a/apps/web/app/new/onboarding/setup/relatable-question.tsx b/apps/web/components/new/onboarding/setup/relatable-question.tsx index c853985d..5fbe9bb4 100644 --- a/apps/web/app/new/onboarding/setup/relatable-question.tsx +++ b/apps/web/components/new/onboarding/setup/relatable-question.tsx @@ -5,7 +5,7 @@ import { motion, AnimatePresence } from "motion/react" import { Button } from "@ui/components/button" import { useRouter } from "next/navigation" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" const relatableOptions = [ { diff --git a/apps/web/app/new/onboarding/welcome/continue-step.tsx b/apps/web/components/new/onboarding/welcome/continue-step.tsx index eefab753..86b4593a 100644 --- a/apps/web/app/new/onboarding/welcome/continue-step.tsx +++ b/apps/web/components/new/onboarding/welcome/continue-step.tsx @@ -1,4 +1,4 @@ -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { Button } from "@ui/components/button" import { motion } from "motion/react" diff --git a/apps/web/app/new/onboarding/welcome/features-step.tsx b/apps/web/components/new/onboarding/welcome/features-step.tsx index 6d15e2f8..2671efc1 100644 --- a/apps/web/app/new/onboarding/welcome/features-step.tsx +++ b/apps/web/components/new/onboarding/welcome/features-step.tsx @@ -2,7 +2,7 @@ import { motion } from "motion/react" import { Button } from "@ui/components/button" import { useRouter } from "next/navigation" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" export function FeaturesStep() { const router = useRouter() diff --git a/apps/web/app/new/onboarding/welcome/greeting-step.tsx b/apps/web/components/new/onboarding/welcome/greeting-step.tsx index 744e3719..744e3719 100644 --- a/apps/web/app/new/onboarding/welcome/greeting-step.tsx +++ b/apps/web/components/new/onboarding/welcome/greeting-step.tsx diff --git a/apps/web/app/new/onboarding/welcome/input-step.tsx b/apps/web/components/new/onboarding/welcome/input-step.tsx index 077d4944..077d4944 100644 --- a/apps/web/app/new/onboarding/welcome/input-step.tsx +++ b/apps/web/components/new/onboarding/welcome/input-step.tsx diff --git a/apps/web/app/new/onboarding/welcome/memories-step.tsx b/apps/web/components/new/onboarding/welcome/profile-step.tsx index 4230f510..65eb21c6 100644 --- a/apps/web/app/new/onboarding/welcome/memories-step.tsx +++ b/apps/web/components/new/onboarding/welcome/profile-step.tsx @@ -3,9 +3,15 @@ import { Button } from "@ui/components/button" import { useState } from "react" import { useRouter } from "next/navigation" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" +import { + parseXHandle, + parseLinkedInHandle, + toXProfileUrl, + toLinkedInProfileUrl, +} from "@/lib/url-helpers" -interface MemoriesStepProps { +interface ProfileStepProps { onSubmit: (data: { twitter: string linkedin: string @@ -19,7 +25,7 @@ type ValidationError = { linkedin: string | null } -export function MemoriesStep({ onSubmit }: MemoriesStepProps) { +export function ProfileStep({ onSubmit }: ProfileStepProps) { const router = useRouter() const [otherLinks, setOtherLinks] = useState([""]) const [twitterHandle, setTwitterHandle] = useState("") @@ -43,63 +49,43 @@ export function MemoriesStep({ onSubmit }: MemoriesStepProps) { setOtherLinks(updated) } - const validateTwitterLink = (value: string): string | null => { - if (!value.trim()) return null + const validateTwitterHandle = (handle: string): string | null => { + if (!handle.trim()) return null - const normalized = value.trim().toLowerCase() - const isXDomain = - normalized.includes("x.com") || normalized.includes("twitter.com") - - if (!isXDomain) { - return "share your X profile link" - } - - // Check if it's a profile link (not a status/tweet link) - const profilePattern = - /^(https?:\/\/)?(www\.)?(x\.com|twitter\.com)\/[^\/]+$/ - const statusPattern = /\/status\//i - - if (statusPattern.test(normalized) || !profilePattern.test(normalized)) { - return "share your X profile link" + // Basic validation: handle should be alphanumeric, underscore, or hyphen + // X/Twitter handles can contain letters, numbers, and underscores, max 15 chars + const handlePattern = /^[a-zA-Z0-9_]{1,15}$/ + if (!handlePattern.test(handle.trim())) { + return "Enter your handle or profile link" } - // Note: 404 validation would require a backend API endpoint - // Format validation is handled above return null } - const validateLinkedInLink = (value: string): string | null => { - if (!value.trim()) return null - - const normalized = value.trim().toLowerCase() - const isLinkedInDomain = normalized.includes("linkedin.com") - - if (!isLinkedInDomain) { - return "share your Linkedin profile link" - } - - // Check if it's a profile link (should have /in/ or /pub/) - const profilePattern = - /^(https?:\/\/)?(www\.)?linkedin\.com\/(in|pub)\/[^\/]+/ + const validateLinkedInHandle = (handle: string): string | null => { + if (!handle.trim()) return null - if (!profilePattern.test(normalized)) { - return "share your Linkedin profile link" + // Basic validation: LinkedIn handles are typically alphanumeric with hyphens + // They can be quite long, so we'll be lenient + const handlePattern = /^[a-zA-Z0-9-]+$/ + if (!handlePattern.test(handle.trim())) { + return "Enter your handle or profile link" } - // Note: 404 validation would require a backend API endpoint - // Format validation is handled above return null } const handleTwitterChange = (value: string) => { - setTwitterHandle(value) - const error = validateTwitterLink(value) + const parsedHandle = parseXHandle(value) + setTwitterHandle(parsedHandle) + const error = validateTwitterHandle(parsedHandle) setErrors((prev) => ({ ...prev, twitter: error })) } const handleLinkedInChange = (value: string) => { - setLinkedinProfile(value) - const error = validateLinkedInLink(value) + const parsedHandle = parseLinkedInHandle(value) + setLinkedinProfile(parsedHandle) + const error = validateLinkedInHandle(parsedHandle) setErrors((prev) => ({ ...prev, linkedin: error })) } @@ -123,24 +109,33 @@ export function MemoriesStep({ onSubmit }: MemoriesStepProps) { X/Twitter </label> <div className="relative flex items-center"> - <input - id="twitter-handle" - type="text" - placeholder="x.com/yourhandle" - value={twitterHandle} - onChange={(e) => handleTwitterChange(e.target.value)} - onBlur={() => { - if (twitterHandle.trim()) { - const error = validateTwitterLink(twitterHandle) - setErrors((prev) => ({ ...prev, twitter: error })) - } - }} - className={`w-full px-4 py-2 bg-[#070E1B] border rounded-xl text-white placeholder-onboarding focus:outline-none transition-colors h-[40px] ${ + <div + className={`flex items-center border rounded-xl overflow-hidden h-[40px] w-full ${ errors.twitter ? "border-[#52596633] bg-[#290F0A]" : "border-onboarding/20" }`} - /> + > + <div className="px-3 py-2 bg-[#070E1B] text-white/60 text-sm border-r border-onboarding/20 whitespace-nowrap"> + x.com/ + </div> + <input + id="twitter-handle" + type="text" + placeholder="handle" + value={twitterHandle} + onChange={(e) => handleTwitterChange(e.target.value)} + onBlur={() => { + if (twitterHandle.trim()) { + const error = validateTwitterHandle(twitterHandle) + setErrors((prev) => ({ ...prev, twitter: error })) + } + }} + className={`flex-1 px-4 py-2 bg-[#070E1B] text-white placeholder-onboarding focus:outline-none transition-colors ${ + errors.twitter ? "bg-[#290F0A]" : "" + }`} + /> + </div> {errors.twitter && ( <div className="absolute left-full ml-3"> <div @@ -165,24 +160,33 @@ export function MemoriesStep({ onSubmit }: MemoriesStepProps) { LinkedIn </label> <div className="relative flex items-center"> - <input - id="linkedin-profile" - type="text" - placeholder="linkedin.com/in/yourname" - value={linkedinProfile} - onChange={(e) => handleLinkedInChange(e.target.value)} - onBlur={() => { - if (linkedinProfile.trim()) { - const error = validateLinkedInLink(linkedinProfile) - setErrors((prev) => ({ ...prev, linkedin: error })) - } - }} - className={`w-full px-4 py-2 bg-[#070E1B] border rounded-xl text-white placeholder-onboarding focus:outline-none transition-colors h-[40px] ${ + <div + className={`flex items-center border rounded-xl overflow-hidden h-[40px] w-full ${ errors.linkedin ? "border-[#52596633] bg-[#290F0A]" : "border-onboarding/20" }`} - /> + > + <div className="px-3 py-2 bg-[#070E1B] text-white/60 text-sm border-r border-onboarding/20 whitespace-nowrap w-[140px]"> + linkedin.com/in/ + </div> + <input + id="linkedin-profile" + type="text" + placeholder="username" + value={linkedinProfile} + onChange={(e) => handleLinkedInChange(e.target.value)} + onBlur={() => { + if (linkedinProfile.trim()) { + const error = validateLinkedInHandle(linkedinProfile) + setErrors((prev) => ({ ...prev, linkedin: error })) + } + }} + className={`flex-1 px-4 py-2 bg-[#070E1B] text-white placeholder-onboarding focus:outline-none transition-colors ${ + errors.linkedin ? "bg-[#290F0A]" : "" + }`} + /> + </div> {errors.linkedin && ( <div className="absolute left-full ml-3"> <div @@ -282,8 +286,8 @@ export function MemoriesStep({ onSubmit }: MemoriesStepProps) { }} onClick={() => { const formData = { - twitter: twitterHandle, - linkedin: linkedinProfile, + twitter: toXProfileUrl(twitterHandle), + linkedin: toLinkedInProfileUrl(linkedinProfile), description: description, otherLinks: otherLinks.filter((l) => l.trim()), } diff --git a/apps/web/app/new/onboarding/welcome/welcome-step.tsx b/apps/web/components/new/onboarding/welcome/welcome-step.tsx index 5c0d998f..5c0d998f 100644 --- a/apps/web/app/new/onboarding/welcome/welcome-step.tsx +++ b/apps/web/components/new/onboarding/welcome/welcome-step.tsx diff --git a/apps/web/components/x-bookmarks-detail-view.tsx b/apps/web/components/new/onboarding/x-bookmarks-detail-view.tsx index 72befe96..acd4ebf8 100644 --- a/apps/web/components/x-bookmarks-detail-view.tsx +++ b/apps/web/components/new/onboarding/x-bookmarks-detail-view.tsx @@ -2,7 +2,7 @@ import { Button } from "@ui/components/button" import { cn } from "@lib/utils" -import { dmSansClassName } from "@/utils/fonts" +import { dmSansClassName } from "@/lib/fonts" import Image from "next/image" interface XBookmarksDetailViewProps { @@ -27,9 +27,7 @@ const steps = [ }, ] -export function XBookmarksDetailView({ - onBack, -}: XBookmarksDetailViewProps) { +export function XBookmarksDetailView({ onBack }: XBookmarksDetailViewProps) { const handleInstall = () => { window.open( "https://chromewebstore.google.com/detail/supermemory/afpgkkipfdpeaflnangednailhoegogi", diff --git a/apps/web/components/new/settings/account.tsx b/apps/web/components/new/settings/account.tsx index 10967947..3be01f9f 100644 --- a/apps/web/components/new/settings/account.tsx +++ b/apps/web/components/new/settings/account.tsx @@ -1,6 +1,6 @@ "use client" -import { dmSans125ClassName } from "@/utils/fonts" +import { dmSans125ClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { useAuth } from "@lib/auth-context" import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar" diff --git a/apps/web/components/new/settings/connections-mcp.tsx b/apps/web/components/new/settings/connections-mcp.tsx index 760dcf22..5a619f65 100644 --- a/apps/web/components/new/settings/connections-mcp.tsx +++ b/apps/web/components/new/settings/connections-mcp.tsx @@ -1,6 +1,6 @@ "use client" -import { dmSans125ClassName } from "@/utils/fonts" +import { dmSans125ClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { $fetch } from "@lib/api" import { fetchSubscriptionStatus } from "@lib/queries" diff --git a/apps/web/components/new/settings/integrations.tsx b/apps/web/components/new/settings/integrations.tsx index 0ce96bf7..deccf5e4 100644 --- a/apps/web/components/new/settings/integrations.tsx +++ b/apps/web/components/new/settings/integrations.tsx @@ -1,6 +1,6 @@ "use client" -import { dmSans125ClassName } from "@/utils/fonts" +import { dmSans125ClassName } from "@/lib/fonts" import { analytics } from "@/lib/analytics" import { cn } from "@lib/utils" import { authClient } from "@lib/auth" diff --git a/apps/web/components/new/settings/support.tsx b/apps/web/components/new/settings/support.tsx index 691f5c38..5868f356 100644 --- a/apps/web/components/new/settings/support.tsx +++ b/apps/web/components/new/settings/support.tsx @@ -1,6 +1,6 @@ "use client" -import { dmSans125ClassName } from "@/utils/fonts" +import { dmSans125ClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { ArrowUpRight } from "lucide-react" diff --git a/apps/web/components/new/text-editor/extensions.tsx b/apps/web/components/new/text-editor/extensions.tsx new file mode 100644 index 00000000..9db26c2b --- /dev/null +++ b/apps/web/components/new/text-editor/extensions.tsx @@ -0,0 +1,92 @@ +import StarterKit from "@tiptap/starter-kit" +import Placeholder from "@tiptap/extension-placeholder" +import Link from "@tiptap/extension-link" +import Image from "@tiptap/extension-image" +import TaskList from "@tiptap/extension-task-list" +import TaskItem from "@tiptap/extension-task-item" +import { cx } from "class-variance-authority" + +const placeholder = Placeholder.configure({ + placeholder: 'Write, paste anything or type "/" for commands...', +}) + +const taskList = TaskList.configure({ + HTMLAttributes: { + class: cx("not-prose pl-2"), + }, +}) + +const taskItem = TaskItem.configure({ + HTMLAttributes: { + class: cx("flex items-start my-4"), + }, + nested: true, +}) + +const link = Link.configure({ + HTMLAttributes: { + class: cx( + "text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer", + ), + }, + openOnClick: false, +}) + +const image = Image.configure({ + HTMLAttributes: { + class: cx("rounded-lg border border-muted"), + }, +}) + +const starterKit = StarterKit.configure({ + bulletList: { + HTMLAttributes: { + class: cx("list-disc list-outside leading-3 -mt-2"), + }, + }, + orderedList: { + HTMLAttributes: { + class: cx("list-decimal list-outside leading-3 -mt-2"), + }, + }, + listItem: { + HTMLAttributes: { + class: cx("leading-normal -mb-2"), + }, + }, + blockquote: { + HTMLAttributes: { + class: cx("border-l-4 border-primary"), + }, + }, + codeBlock: { + HTMLAttributes: { + class: cx("rounded-sm bg-muted border p-5 font-mono font-medium"), + }, + }, + code: { + HTMLAttributes: { + class: cx("rounded-md bg-muted px-1.5 py-1 font-mono font-medium"), + spellcheck: "false", + }, + }, + horizontalRule: { + HTMLAttributes: { + class: cx("mt-4 mb-6 border-t border-muted-foreground"), + }, + }, + dropcursor: { + color: "#DBEAFE", + width: 4, + }, + gapcursor: false, +}) + +export const defaultExtensions = [ + starterKit, + placeholder, + link, + image, + taskList, + taskItem, +] diff --git a/apps/web/components/new/text-editor/index.tsx b/apps/web/components/new/text-editor/index.tsx new file mode 100644 index 00000000..6446863d --- /dev/null +++ b/apps/web/components/new/text-editor/index.tsx @@ -0,0 +1,174 @@ +"use client" + +import { useEditor, EditorContent } from "@tiptap/react" +import { BubbleMenu } from "@tiptap/react/menus" +import type { Editor } from "@tiptap/core" +import { Markdown } from "@tiptap/markdown" +import { useRef, useEffect, useCallback } from "react" +import { defaultExtensions } from "./extensions" +import { slashCommand } from "./suggestions" +import { Bold, Italic, Code } from "lucide-react" +import { useDebouncedCallback } from "use-debounce" +import { cn } from "@lib/utils" + +const extensions = [...defaultExtensions, slashCommand, Markdown] + +export function TextEditor({ + content: initialContent, + onContentChange, + onSubmit, +}: { + content: string | undefined + onContentChange: (content: string) => void + onSubmit: () => void +}) { + const containerRef = useRef<HTMLDivElement>(null) + const editorRef = useRef<Editor | null>(null) + const onSubmitRef = useRef(onSubmit) + const hasUserEditedRef = useRef(false) + + useEffect(() => { + onSubmitRef.current = onSubmit + }, [onSubmit]) + + const debouncedUpdates = useDebouncedCallback((editor: Editor) => { + if (!hasUserEditedRef.current) return + const json = editor.getJSON() + const markdown = editor.storage.markdown?.manager?.serialize(json) ?? "" + onContentChange?.(markdown) + }, 500) + + const editor = useEditor({ + extensions, + content: initialContent, + contentType: "markdown", + immediatelyRender: true, + onCreate: ({ editor }) => { + editorRef.current = editor + }, + onUpdate: ({ editor }) => { + editorRef.current = editor + debouncedUpdates(editor) + }, + editorProps: { + handleKeyDown: (_view, event) => { + if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { + event.preventDefault() + onSubmitRef.current?.() + return true + } + hasUserEditedRef.current = true + return false + }, + handleTextInput: () => { + hasUserEditedRef.current = true + return false + }, + handlePaste: () => { + hasUserEditedRef.current = true + return false + }, + handleDrop: () => { + hasUserEditedRef.current = true + return false + }, + }, + }) + + useEffect(() => { + if (editor && initialContent) { + hasUserEditedRef.current = false + editor.commands.setContent(initialContent, { contentType: "markdown" }) + } + }, [editor, initialContent]) + + const handleClick = useCallback( + (e: React.MouseEvent<HTMLDivElement>) => { + const target = e.target as HTMLElement + if (target.closest(".ProseMirror")) { + return + } + if (target.closest("button, a")) { + return + } + + const proseMirror = containerRef.current?.querySelector( + ".ProseMirror", + ) as HTMLElement + if (proseMirror && editorRef.current) { + setTimeout(() => { + proseMirror.focus() + editorRef.current?.commands.focus("end") + }, 0) + } + }, + [], + ) + + useEffect(() => { + return () => { + editor?.destroy() + } + }, [editor]) + + return ( + <> + {/* biome-ignore lint/a11y/useSemanticElements: div is needed as container for editor, cannot use button */} + {/* biome-ignore lint/a11y/useKeyWithClickEvents: we need to use a div to get the focus on the editor */} + <div + role="button" + tabIndex={0} + ref={containerRef} + onClick={handleClick} + className="w-full h-full outline-none prose prose-invert max-w-none [&_.ProseMirror]:outline-none [&_.ProseMirror]:focus:outline-none [&_.ProseMirror-focused]:outline-none text-editor-prose cursor-text" + > + <EditorContent editor={editor} /> + </div> + {editor && ( + <BubbleMenu + editor={editor} + options={{ placement: "bottom-start", offset: 8 }} + > + <div className="flex items-center gap-1 rounded-[8px] bg-[#1b1f24] p-2 shadow-[0px_4px_20px_0px_rgba(0,0,0,0.25),inset_1px_1px_1px_0px_rgba(255,255,255,0.1)]"> + <button + type="button" + onClick={() => + editor.chain().focus().toggleBold().run() + } + className={cn( + "flex items-center justify-center rounded-[4px] p-1.5 hover:bg-[#2e353d] cursor-pointer text-[#fafafa]", + editor.isActive("bold") && "bg-[#2e353d]", + )} + > + <Bold size={16} /> + </button> + <button + type="button" + onClick={() => + editor.chain().focus().toggleItalic().run() + } + className={cn( + "flex items-center justify-center rounded-[4px] p-1.5 hover:bg-[#2e353d] cursor-pointer text-[#fafafa]", + editor.isActive("italic") && "bg-[#2e353d]", + )} + > + <Italic size={16} /> + </button> + <button + type="button" + onClick={() => + editor.chain().focus().toggleCode().run() + } + className={cn( + "flex items-center justify-center rounded-[4px] p-1.5 hover:bg-[#2e353d] cursor-pointer text-[#fafafa]", + editor.isActive("code") && "bg-[#2e353d]", + )} + > + <Code size={16} /> + </button> + </div> + </BubbleMenu> + )} + </> + ) +} diff --git a/apps/web/components/new/text-editor/slash-command.tsx b/apps/web/components/new/text-editor/slash-command.tsx new file mode 100644 index 00000000..31991093 --- /dev/null +++ b/apps/web/components/new/text-editor/slash-command.tsx @@ -0,0 +1,308 @@ +"use client" + +import { Extension, type Editor, type Range } from "@tiptap/core" +import Suggestion, { type SuggestionOptions } from "@tiptap/suggestion" +import { useEffect, useLayoutEffect, useState, useRef } from "react" +import { createPortal } from "react-dom" +import { createRoot, type Root } from "react-dom/client" +import { + useFloating, + offset, + flip, + shift, + autoUpdate, +} from "@floating-ui/react" +import { cn } from "@lib/utils" + +export interface SuggestionItem { + title: string + description: string + searchTerms?: string[] + icon: React.ReactNode + command: (props: { editor: Editor; range: Range }) => void +} + +interface CommandListProps { + items: SuggestionItem[] + command: (item: SuggestionItem) => void + selectedIndex: number +} + +function CommandList({ items, command, selectedIndex }: CommandListProps) { + const containerRef = useRef<HTMLDivElement>(null) + + useEffect(() => { + const selectedElement = containerRef.current?.querySelector( + `[data-index="${selectedIndex}"]`, + ) + selectedElement?.scrollIntoView({ block: "nearest" }) + }, [selectedIndex]) + + if (items.length === 0) { + return ( + <div className="z-50 h-auto max-h-[330px] overflow-y-auto rounded-[8px] bg-[#1b1f24] p-2 shadow-[0px_4px_20px_0px_rgba(0,0,0,0.25),inset_1px_1px_1px_0px_rgba(255,255,255,0.1)]"> + <div className="px-2 text-muted-foreground">No results</div> + </div> + ) + } + + return ( + <div + ref={containerRef} + className="z-50 h-auto max-h-[330px] overflow-y-auto rounded-[8px] bg-[#1b1f24] p-2 shadow-[0px_4px_20px_0px_rgba(0,0,0,0.25),inset_1px_1px_1px_0px_rgba(255,255,255,0.1)]" + > + {items.map((item, index) => ( + <button + type="button" + key={item.title} + data-index={index} + onClick={() => command(item)} + className={cn( + "flex w-full items-center gap-2 rounded-[4px] px-3 py-2 text-left hover:bg-[#2e353d]", + index === selectedIndex && "bg-[#2e353d]", + )} + > + <div className="flex size-[20px] shrink-0 items-center justify-center text-[#fafafa]"> + {item.icon} + </div> + <p className="font-medium text-[16px] leading-[1.35] tracking-[-0.16px] text-[#fafafa]"> + {item.title} + </p> + </button> + ))} + </div> + ) +} + +interface CommandMenuProps { + items: SuggestionItem[] + command: (item: SuggestionItem) => void + clientRect: (() => DOMRect | null) | null + selectedIndex: number +} + +function CommandMenu({ + items, + command, + clientRect, + selectedIndex, +}: CommandMenuProps) { + const [mounted, setMounted] = useState(false) + + const { refs, floatingStyles } = useFloating({ + placement: "bottom-start", + middleware: [offset(8), flip(), shift()], + whileElementsMounted: autoUpdate, + }) + + useLayoutEffect(() => { + setMounted(true) + }, []) + + useEffect(() => { + const rect = clientRect?.() + if (rect) { + refs.setReference({ + getBoundingClientRect: () => rect, + }) + } + }, [clientRect, refs]) + + if (!mounted) return null + + return createPortal( + <div ref={refs.setFloating} style={floatingStyles} className="z-50"> + <CommandList + items={items} + command={command} + selectedIndex={selectedIndex} + /> + </div>, + document.body, + ) +} + +export function createSlashCommand(items: SuggestionItem[]) { + let component: { + updateProps: (props: CommandMenuProps) => void + destroy: () => void + element: HTMLElement + } | null = null + let root: Root | null = null + let selectedIndex = 0 + let currentItems: SuggestionItem[] = [] + + const renderMenu = (props: { + items: SuggestionItem[] + command: (item: SuggestionItem) => void + clientRect: (() => DOMRect | null) | null + }) => { + root?.render( + <CommandMenu + items={props.items} + command={props.command} + clientRect={props.clientRect} + selectedIndex={selectedIndex} + />, + ) + } + + const suggestion: Omit<SuggestionOptions<SuggestionItem>, "editor"> = { + char: "/", + items: ({ query }) => { + return items.filter( + (item) => + item.title.toLowerCase().includes(query.toLowerCase()) || + item.searchTerms?.some((term) => + term.toLowerCase().includes(query.toLowerCase()), + ), + ) + }, + command: ({ editor, range, props }) => { + props.command({ editor, range }) + }, + render: () => { + let currentCommand: ((item: SuggestionItem) => void) | null = null + let currentClientRect: (() => DOMRect | null) | null = null + + return { + onStart: (props) => { + selectedIndex = 0 + currentItems = props.items as SuggestionItem[] + currentCommand = props.command as ( + item: SuggestionItem, + ) => void + currentClientRect = props.clientRect ?? null + + const element = document.createElement("div") + document.body.appendChild(element) + + root = createRoot(element) + if (currentCommand) { + renderMenu({ + items: currentItems, + command: currentCommand, + clientRect: currentClientRect, + }) + } + + component = { + element, + updateProps: (newProps: CommandMenuProps) => { + root?.render( + <CommandMenu + items={newProps.items} + command={newProps.command} + clientRect={newProps.clientRect} + selectedIndex={newProps.selectedIndex} + />, + ) + }, + destroy: () => { + root?.unmount() + element.remove() + root = null + }, + } + }, + + onUpdate: (props) => { + currentItems = props.items as SuggestionItem[] + currentCommand = props.command as ( + item: SuggestionItem, + ) => void + currentClientRect = props.clientRect ?? null + + if (selectedIndex >= currentItems.length) { + selectedIndex = Math.max(0, currentItems.length - 1) + } + + if (currentCommand) { + component?.updateProps({ + items: currentItems, + command: currentCommand, + clientRect: currentClientRect, + selectedIndex, + }) + } + }, + + onKeyDown: (props) => { + const { event } = props + + if (event.key === "Escape") { + component?.destroy() + component = null + return true + } + + if (event.key === "ArrowUp") { + selectedIndex = + selectedIndex <= 0 + ? currentItems.length - 1 + : selectedIndex - 1 + if (currentCommand) { + component?.updateProps({ + items: currentItems, + command: currentCommand, + clientRect: currentClientRect, + selectedIndex, + }) + } + return true + } + + if (event.key === "ArrowDown") { + selectedIndex = + selectedIndex >= currentItems.length - 1 + ? 0 + : selectedIndex + 1 + if (currentCommand) { + component?.updateProps({ + items: currentItems, + command: currentCommand, + clientRect: currentClientRect, + selectedIndex, + }) + } + return true + } + + if (event.key === "Enter") { + const item = currentItems[selectedIndex] + if (item && currentCommand) { + currentCommand(item) + } + return true + } + + return false + }, + + onExit: () => { + component?.destroy() + component = null + }, + } + }, + } + + return Extension.create({ + name: "slashCommand", + + addOptions() { + return { + suggestion, + } + }, + + addProseMirrorPlugins() { + return [ + Suggestion({ + editor: this.editor, + ...this.options.suggestion, + }), + ] + }, + }) +} diff --git a/apps/web/components/new/text-editor/suggestions.tsx b/apps/web/components/new/text-editor/suggestions.tsx new file mode 100644 index 00000000..d31f7732 --- /dev/null +++ b/apps/web/components/new/text-editor/suggestions.tsx @@ -0,0 +1,103 @@ +import { + Heading1, + Heading2, + Heading3, + List, + ListOrdered, + TextQuote, + Text, +} from "lucide-react" +import { createSlashCommand, type SuggestionItem } from "./slash-command" + +export const suggestionItems: SuggestionItem[] = [ + { + title: "Heading 1", + description: "Big section heading.", + searchTerms: ["title", "big", "large"], + icon: <Heading1 size={20} />, + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 1 }) + .run() + }, + }, + { + title: "Heading 2", + description: "Medium section heading.", + searchTerms: ["subtitle", "medium"], + icon: <Heading2 size={20} />, + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 2 }) + .run() + }, + }, + { + title: "Heading 3", + description: "Small section heading.", + searchTerms: ["subtitle", "small"], + icon: <Heading3 size={20} />, + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 3 }) + .run() + }, + }, + { + title: "Text", + description: "Just start typing with plain text.", + searchTerms: ["p", "paragraph"], + icon: <Text size={18} />, + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .toggleNode("paragraph", "paragraph") + .run() + }, + }, + { + title: "Bullet List", + description: "Create a simple bullet list.", + searchTerms: ["unordered", "point"], + icon: <List size={20} />, + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleBulletList().run() + }, + }, + { + title: "Numbered List", + description: "Create a list with numbering.", + searchTerms: ["ordered"], + icon: <ListOrdered size={20} />, + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleOrderedList().run() + }, + }, + { + title: "Block Quote", + description: "Capture a quote.", + searchTerms: ["blockquote"], + icon: <TextQuote size={20} />, + command: ({ editor, range }) => + editor + .chain() + .focus() + .deleteRange(range) + .toggleNode("paragraph", "paragraph") + .toggleBlockquote() + .run(), + }, +] + +export const slashCommand = createSlashCommand(suggestionItems) diff --git a/apps/web/globals.css b/apps/web/globals.css index 50f28e98..c9e5da27 100644 --- a/apps/web/globals.css +++ b/apps/web/globals.css @@ -88,3 +88,35 @@ inset 0 2px 4px rgba(0, 0, 0, 0.3), inset 0 1px 2px rgba(0, 0, 0, 0.1); } + +/* Disable ProseMirror focus styles */ +.ProseMirror:focus, +.ProseMirror-focused, +.ProseMirror:focus-visible { + outline: none; + box-shadow: none; + border: none; +} + +/* Override prose paragraph margins for text editor */ +.text-editor-prose.prose :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} + +/* Style placeholder for text editor */ +.text-editor-prose .ProseMirror p.is-editor-empty:first-child::before { + content: attr(data-placeholder); + float: left; + color: #525966; + pointer-events: none; + height: 0; +} + +.text-editor-prose .ProseMirror .is-empty::before { + content: attr(data-placeholder); + float: left; + color: #525966; + pointer-events: none; + height: 0; +} diff --git a/apps/web/hooks/use-document-mutations.ts b/apps/web/hooks/use-document-mutations.ts index 5abd7b56..f3931b5f 100644 --- a/apps/web/hooks/use-document-mutations.ts +++ b/apps/web/hooks/use-document-mutations.ts @@ -10,10 +10,10 @@ interface DocumentsQueryData { } interface UseDocumentMutationsOptions { - onClose: () => void + onClose?: () => void } -export function useDocumentMutations({ onClose }: UseDocumentMutationsOptions) { +export function useDocumentMutations({ onClose }: UseDocumentMutationsOptions = {}) { const queryClient = useQueryClient() const noteMutation = useMutation({ @@ -101,7 +101,7 @@ export function useDocumentMutations({ onClose }: UseDocumentMutationsOptions) { queryClient.invalidateQueries({ queryKey: ["documents-with-memories", variables.project], }) - onClose() + onClose?.() }, }) @@ -184,7 +184,7 @@ export function useDocumentMutations({ onClose }: UseDocumentMutationsOptions) { queryClient.invalidateQueries({ queryKey: ["documents-with-memories", variables.project], }) - onClose() + onClose?.() }, }) @@ -301,7 +301,43 @@ export function useDocumentMutations({ onClose }: UseDocumentMutationsOptions) { queryClient.invalidateQueries({ queryKey: ["documents-with-memories", variables.project], }) - onClose() + onClose?.() + }, + }) + + const updateMutation = useMutation({ + mutationFn: async ({ + documentId, + content, + }: { + documentId: string + content: string + }) => { + const response = await $fetch(`@patch/documents/${documentId}`, { + body: { + content, + metadata: { + sm_source: "consumer", + }, + }, + }) + + if (response.error) { + throw new Error(response.error?.message || "Failed to save document") + } + + return response.data + }, + onSuccess: () => { + toast.success("Document saved successfully!") + queryClient.invalidateQueries({ + queryKey: ["documents-with-memories"], + }) + }, + onError: (error) => { + toast.error("Failed to save document", { + description: error instanceof Error ? error.message : "Unknown error", + }) }, }) @@ -309,5 +345,6 @@ export function useDocumentMutations({ onClose }: UseDocumentMutationsOptions) { noteMutation, linkMutation, fileMutation, + updateMutation, } } diff --git a/apps/web/utils/fonts.ts b/apps/web/lib/fonts.ts index dd13c6b5..dd13c6b5 100644 --- a/apps/web/utils/fonts.ts +++ b/apps/web/lib/fonts.ts diff --git a/apps/web/lib/url-helpers.ts b/apps/web/lib/url-helpers.ts new file mode 100644 index 00000000..e4147a05 --- /dev/null +++ b/apps/web/lib/url-helpers.ts @@ -0,0 +1,190 @@ +/** + * Validates if a string is a valid URL. + */ +export const isValidUrl = (url: string): boolean => { + try { + new URL(url) + return true + } catch { + return false + } +} + +/** + * Normalizes a URL by adding https:// prefix if missing. + */ +export const normalizeUrl = (url: string): string => { + if (!url.trim()) return "" + if (url.startsWith("http://") || url.startsWith("https://")) { + return url + } + return `https://${url}` +} + +/** + * Checks if a URL is a Twitter/X URL. + */ +export const isTwitterUrl = (url: string): boolean => { + const normalizedUrl = url.toLowerCase() + return ( + normalizedUrl.includes("twitter.com") || normalizedUrl.includes("x.com") + ) +} + +/** + * Checks if a URL is a LinkedIn profile URL (not a company page). + */ +export const isLinkedInProfileUrl = (url: string): boolean => { + const normalizedUrl = url.toLowerCase() + return ( + normalizedUrl.includes("linkedin.com/in/") && + !normalizedUrl.includes("linkedin.com/company/") + ) +} + +/** + * Collects and validates URLs from LinkedIn profile and other links, excluding Twitter. + */ +export const collectValidUrls = ( + linkedinProfile: string, + otherLinks: string[], +): string[] => { + const urls: string[] = [] + + if (linkedinProfile.trim()) { + const normalizedLinkedIn = normalizeUrl(linkedinProfile.trim()) + if ( + isValidUrl(normalizedLinkedIn) && + isLinkedInProfileUrl(normalizedLinkedIn) + ) { + urls.push(normalizedLinkedIn) + } + } + + otherLinks + .filter((link) => link.trim()) + .forEach((link) => { + const normalizedLink = normalizeUrl(link.trim()) + if (isValidUrl(normalizedLink) && !isTwitterUrl(normalizedLink)) { + urls.push(normalizedLink) + } + }) + + return urls +} + +/** + * Extracts X/Twitter handle from various input formats (URLs, handles with @, etc.). + */ +export function parseXHandle(input: string): string { + if (!input.trim()) return "" + + let value = input.trim() + + if (value.startsWith("@")) { + value = value.slice(1) + } + + const lowerValue = value.toLowerCase() + if (lowerValue.includes("x.com") || lowerValue.includes("twitter.com")) { + try { + let url: URL + if (value.startsWith("http://") || value.startsWith("https://")) { + url = new URL(value) + } else { + url = new URL(`https://${value}`) + } + + const pathSegments = url.pathname.split("/").filter(Boolean) + if (pathSegments.length > 0) { + const firstSegment = pathSegments[0] + if (firstSegment && firstSegment !== "status" && firstSegment !== "i") { + return firstSegment + } + } + } catch { + const match = value.match(/(?:x\.com|twitter\.com)\/([^/\s?#]+)/i) + const handle = match?.[1] + if (handle && handle !== "status") { + return handle + } + } + } + + if ( + value.includes("/") && + !lowerValue.includes("x.com") && + !lowerValue.includes("twitter.com") + ) { + const parts = value.split("/").filter(Boolean) + const firstPart = parts[0] + if (firstPart) { + return firstPart + } + } + + return value +} + +/** + * Extracts LinkedIn handle from various input formats (URLs, handles with @, etc.). + */ +export function parseLinkedInHandle(input: string): string { + if (!input.trim()) return "" + + let value = input.trim() + + if (value.startsWith("@")) { + value = value.slice(1) + } + + const lowerValue = value.toLowerCase() + if (lowerValue.includes("linkedin.com")) { + try { + let url: URL + if (value.startsWith("http://") || value.startsWith("https://")) { + url = new URL(value) + } else { + url = new URL(`https://${value}`) + } + + const pathMatch = url.pathname.match(/\/(in|pub)\/([^/\s?#]+)/i) + const handle = pathMatch?.[2] + if (handle) { + return handle + } + } catch { + const match = value.match(/linkedin\.com\/(?:in|pub)\/([^/\s?#]+)/i) + const handle = match?.[1] + if (handle) { + return handle + } + } + } + + if (value.includes("/in/") || value.includes("/pub/")) { + const match = value.match(/\/(?:in|pub)\/([^/\s?#]+)/i) + const handle = match?.[1] + if (handle) { + return handle + } + } + + return value +} + +/** + * Converts X/Twitter handle to full profile URL. + */ +export function toXProfileUrl(handle: string): string { + if (!handle.trim()) return "" + return `https://x.com/${handle.trim()}` +} + +/** + * Converts LinkedIn handle to full profile URL. + */ +export function toLinkedInProfileUrl(handle: string): string { + if (!handle.trim()) return "" + return `https://linkedin.com/in/${handle.trim()}` +} diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 139923e8..17f8dc4c 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -2,6 +2,19 @@ import { withSentryConfig } from "@sentry/nextjs" import type { NextConfig } from "next" const nextConfig: NextConfig = { + transpilePackages: [ + "@tiptap/core", + "@tiptap/react", + "@tiptap/pm", + "@tiptap/starter-kit", + "@tiptap/extension-placeholder", + "@tiptap/extension-link", + "@tiptap/extension-image", + "@tiptap/extension-task-list", + "@tiptap/extension-task-item", + "@tiptap/suggestion", + "@tiptap/markdown", + ], experimental: { viewTransition: true, turbopackFileSystemCacheForDev: true, diff --git a/apps/web/package.json b/apps/web/package.json index eb744d8e..27ca7aa4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,13 +15,15 @@ "postdeploy": "bun run sentry:sourcemaps" }, "dependencies": { - "@ai-sdk/google": "^2.0.0-beta.13", - "@ai-sdk/react": "2.0.0-beta.24", + "@ai-sdk/google": "^3.0.9", + "@ai-sdk/react": "^3.0.39", + "@ai-sdk/xai": "^3.0.23", "@better-fetch/fetch": "^1.1.18", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@floating-ui/react": "^0.27.0", "@opennextjs/cloudflare": "^1.12.0", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", @@ -48,8 +50,19 @@ "@tanstack/react-query-devtools": "^5.84.2", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.13.12", + "@tiptap/core": "^3.15.3", + "@tiptap/extension-image": "^3.15.3", + "@tiptap/extension-link": "^3.15.3", + "@tiptap/extension-placeholder": "^3.15.3", + "@tiptap/extension-task-item": "^3.15.3", + "@tiptap/extension-task-list": "^3.15.3", + "@tiptap/markdown": "^3.15.3", + "@tiptap/pm": "^3.15.3", + "@tiptap/react": "^3.15.3", + "@tiptap/starter-kit": "^3.15.3", + "@tiptap/suggestion": "^3.15.3", "@types/dompurify": "^3.2.0", - "ai": "5.0.0-beta.24", + "ai": "^6.0.35", "autumn-js": "0.0.116", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -85,6 +98,7 @@ "streamdown": "^1.1.6", "tailwind-merge": "^3.3.1", "tw-animate-css": "^1.3.4", + "use-debounce": "^10.1.0", "vaul": "^1.1.2", "zustand": "^5.0.7" }, diff --git a/apps/web/utils/url-helpers.ts b/apps/web/utils/url-helpers.ts deleted file mode 100644 index 3a9ef5de..00000000 --- a/apps/web/utils/url-helpers.ts +++ /dev/null @@ -1,59 +0,0 @@ -export const isValidUrl = (url: string): boolean => { - try { - new URL(url) - return true - } catch { - return false - } -} - -export const normalizeUrl = (url: string): string => { - if (!url.trim()) return "" - if (url.startsWith("http://") || url.startsWith("https://")) { - return url - } - return `https://${url}` -} - -export const isTwitterUrl = (url: string): boolean => { - const normalizedUrl = url.toLowerCase() - return ( - normalizedUrl.includes("twitter.com") || normalizedUrl.includes("x.com") - ) -} - -export const isLinkedInProfileUrl = (url: string): boolean => { - const normalizedUrl = url.toLowerCase() - return ( - normalizedUrl.includes("linkedin.com/in/") && - !normalizedUrl.includes("linkedin.com/company/") - ) -} - -export const collectValidUrls = ( - linkedinProfile: string, - otherLinks: string[], -): string[] => { - const urls: string[] = [] - - if (linkedinProfile.trim()) { - const normalizedLinkedIn = normalizeUrl(linkedinProfile.trim()) - if ( - isValidUrl(normalizedLinkedIn) && - isLinkedInProfileUrl(normalizedLinkedIn) - ) { - urls.push(normalizedLinkedIn) - } - } - - otherLinks - .filter((link) => link.trim()) - .forEach((link) => { - const normalizedLink = normalizeUrl(link.trim()) - if (isValidUrl(normalizedLink) && !isTwitterUrl(normalizedLink)) { - urls.push(normalizedLink) - } - }) - - return urls -} @@ -138,13 +138,15 @@ "name": "@repo/web", "version": "0.1.0", "dependencies": { - "@ai-sdk/google": "^2.0.0-beta.13", - "@ai-sdk/react": "2.0.0-beta.24", + "@ai-sdk/google": "^3.0.9", + "@ai-sdk/react": "^3.0.39", + "@ai-sdk/xai": "^3.0.23", "@better-fetch/fetch": "^1.1.18", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@floating-ui/react": "^0.27.0", "@opennextjs/cloudflare": "^1.12.0", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", @@ -171,8 +173,19 @@ "@tanstack/react-query-devtools": "^5.84.2", "@tanstack/react-table": "^8.21.3", "@tanstack/react-virtual": "^3.13.12", + "@tiptap/core": "^3.15.3", + "@tiptap/extension-image": "^3.15.3", + "@tiptap/extension-link": "^3.15.3", + "@tiptap/extension-placeholder": "^3.15.3", + "@tiptap/extension-task-item": "^3.15.3", + "@tiptap/extension-task-list": "^3.15.3", + "@tiptap/markdown": "^3.15.3", + "@tiptap/pm": "^3.15.3", + "@tiptap/react": "^3.15.3", + "@tiptap/starter-kit": "^3.15.3", + "@tiptap/suggestion": "^3.15.3", "@types/dompurify": "^3.2.0", - "ai": "5.0.0-beta.24", + "ai": "^6.0.35", "autumn-js": "0.0.116", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -208,6 +221,7 @@ "streamdown": "^1.1.6", "tailwind-merge": "^3.3.1", "tw-animate-css": "^1.3.4", + "use-debounce": "^10.1.0", "vaul": "^1.1.2", "zustand": "^5.0.7", }, @@ -372,10 +386,12 @@ "@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="], - "@ai-sdk/react": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider-utils": "3.0.0-beta.5", "ai": "5.0.0-beta.24", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.25.76 || ^4" }, "optionalPeers": ["zod"] }, "sha512-Hfgb+z7MteHMaSdQ3G8hhTDridmUk2fkuRa17OaFlsfySYaFQd/exC0EBJlT+05ljcJPInyurdrxbgZLtsoPpg=="], + "@ai-sdk/react": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.7", "ai": "6.0.37", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-Q/39hAazwxItCbqEDWC4pa3+HXLVvTzeu/zu7ghANqRNCxzmqBb/GYEIU/wiYNRS05hP6R1l9bA9S4U6sx0iTA=="], "@ai-sdk/ui-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="], + "@ai-sdk/xai": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.10", "@ai-sdk/provider": "3.0.3", "@ai-sdk/provider-utils": "4.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-sWWJK0lGNFWfGTzgFkgTRZUeXycaXY3QufMfPwev2uRYvRNF6QFGZn2Ut3PM70OVl8NP6DHMnYl0loM4VyC7ug=="], + "@aklinker1/rollup-plugin-visualizer": ["@aklinker1/[email protected]", "", { "dependencies": { "open": "^8.4.0", "picomatch": "^2.3.1", "source-map": "^0.7.4", "yargs": "^17.5.1" }, "peerDependencies": { "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ=="], "@alcalzone/ansi-tokenize": ["@alcalzone/[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-jsElTJ0sQ4wHRz+C45tfect76BwbTbgkgKByOzpCN9xG61N5V6u/glvg1CsNJhq2xJIFpKHSwG3D2wPPuEYOrQ=="], @@ -782,6 +798,8 @@ "@floating-ui/dom": ["@floating-ui/[email protected]", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], + "@floating-ui/react": ["@floating-ui/[email protected]", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g=="], + "@floating-ui/react-dom": ["@floating-ui/[email protected]", "", { "dependencies": { "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw=="], "@floating-ui/utils": ["@floating-ui/[email protected]", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], @@ -1306,6 +1324,8 @@ "@react-router/serve": ["@react-router/[email protected]", "", { "dependencies": { "@mjackson/node-fetch-server": "^0.2.0", "@react-router/express": "7.12.0", "@react-router/node": "7.12.0", "compression": "^1.8.1", "express": "^4.19.2", "get-port": "5.1.1", "morgan": "^1.10.1", "source-map-support": "^0.5.21" }, "peerDependencies": { "react-router": "7.12.0" }, "bin": { "react-router-serve": "bin.js" } }, "sha512-j1ltgU7s3wAwOosZ5oxgHSsmVyK706gY/yIs8qVmC239wQ3zr3eqaXk3TVVLMeRy+eDgPNmgc6oNJv2o328VgA=="], + "@remirror/core-constants": ["@remirror/[email protected]", "", {}, "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg=="], + "@remix-run/node-fetch-server": ["@remix-run/[email protected]", "", {}, "sha512-SoLMv7dbH+njWzXnOY6fI08dFMI5+/dQ+vY3n8RnnbdG7MdJEgiP28Xj/xWlnRnED/aB6SFw56Zop+LbmaaKqA=="], "@repo/docs": ["@repo/docs@workspace:apps/docs"], @@ -1698,6 +1718,74 @@ "@tanstack/virtual-core": ["@tanstack/[email protected]", "", {}, "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg=="], + "@tiptap/core": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/pm": "^3.15.3" } }, "sha512-bmXydIHfm2rEtGju39FiQNfzkFx9CDvJe+xem1dgEZ2P6Dj7nQX9LnA1ZscW7TuzbBRkL5p3dwuBIi3f62A66A=="], + + "@tiptap/extension-blockquote": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-13x5UsQXtttFpoS/n1q173OeurNxppsdWgP3JfsshzyxIghhC141uL3H6SGYQLPU31AizgDs2OEzt6cSUevaZg=="], + + "@tiptap/extension-bold": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-I8JYbkkUTNUXbHd/wCse2bR0QhQtJD7+0/lgrKOmGfv5ioLxcki079Nzuqqay3PjgYoJLIJQvm3RAGxT+4X91w=="], + + "@tiptap/extension-bubble-menu": ["@tiptap/[email protected]", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-e88DG1bTy6hKxrt7iPVQhJnH5/EOrnKpIyp09dfRDgWrrW88fE0Qjys7a/eT8W+sXyXM3z10Ye7zpERWsrLZDg=="], + + "@tiptap/extension-bullet-list": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/extension-list": "^3.15.3" } }, "sha512-MGwEkNT7ltst6XaWf0ObNgpKQ4PvuuV3igkBrdYnQS+qaAx9IF4isygVPqUc9DvjYC306jpyKsNqNrENIXcosA=="], + + "@tiptap/extension-code": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-x6LFt3Og6MFINYpsMzrJnz7vaT9Yk1t4oXkbJsJRSavdIWBEBcoRudKZ4sSe/AnsYlRJs8FY2uR76mt9e+7xAQ=="], + + "@tiptap/extension-code-block": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-q1UB9icNfdJppTqMIUWfoRKkx5SSdWIpwZoL2NeOI5Ah3E20/dQKVttIgLhsE521chyvxCYCRaHD5tMNGKfhyw=="], + + "@tiptap/extension-document": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-AC72nI2gnogBuETCKbZekn+h6t5FGGcZG2abPGKbz/x9rwpb6qV2hcbAQ30t6M7H6cTOh2/Ut8bEV2MtMB15sw=="], + + "@tiptap/extension-dropcursor": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/extensions": "^3.15.3" } }, "sha512-jGI5XZpdo8GSYQFj7HY15/oEwC2m2TqZz0/Fln5qIhY32XlZhWrsMuMI6WbUJrTH16es7xO6jmRlDsc6g+vJWg=="], + + "@tiptap/extension-floating-menu": ["@tiptap/[email protected]", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-+3DVBleKKffadEJEdLYxmYAJOjHjLSqtiSFUE3RABT4V2ka1ODy2NIpyKX0o1SvQ5N1jViYT9Q+yUbNa6zCcDw=="], + + "@tiptap/extension-gapcursor": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/extensions": "^3.15.3" } }, "sha512-Kaw0sNzP0bQI/xEAMSfIpja6xhsu9WqqAK/puzOIS1RKWO47Wps/tzqdSJ9gfslPIb5uY5mKCfy8UR8Xgiia8w=="], + + "@tiptap/extension-hard-break": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-8HjxmeRbBiXW+7JKemAJtZtHlmXQ9iji398CPQ0yYde68WbIvUhHXjmbJE5pxFvvQTJ/zJv1aISeEOZN2bKBaw=="], + + "@tiptap/extension-heading": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-G1GG6iN1YXPS+75arDpo+bYRzhr3dNDw99c7D7na3aDawa9Qp7sZ/bVrzFUUcVEce0cD6h83yY7AooBxEc67hA=="], + + "@tiptap/extension-horizontal-rule": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-FYkN7L6JsfwwNEntmLklCVKvgL0B0N47OXMacRk6kYKQmVQ4Nvc7q/VJLpD9sk4wh4KT1aiCBfhKEBTu5pv1fg=="], + + "@tiptap/extension-image": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-Tjq9BHlC/0bGR9/uySA0tv6I1Ua1Q5t5P/mdbWyZi4JdUpKHRfgenzfXF5DYnklJ01QJ7uOPSp9sAGgPzBixtQ=="], + + "@tiptap/extension-italic": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-6XeuPjcWy7OBxpkgOV7bD6PATO5jhIxc8SEK4m8xn8nelGTBIbHGqK37evRv+QkC7E0MUryLtzwnmmiaxcKL0Q=="], + + "@tiptap/extension-link": ["@tiptap/[email protected]", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-PdDXyBF9Wco9U1x6e+b7tKBWG+kqBDXDmaYXHkFm/gYuQCQafVJ5mdrDdKgkHDWVnJzMWZXBcZjT9r57qtlLWg=="], + + "@tiptap/extension-list": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-n7y/MF9lAM5qlpuH5IR4/uq+kJPEJpe9NrEiH+NmkO/5KJ6cXzpJ6F4U17sMLf2SNCq+TWN9QK8QzoKxIn50VQ=="], + + "@tiptap/extension-list-item": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/extension-list": "^3.15.3" } }, "sha512-CCxL5ek1p0lO5e8aqhnPzIySldXRSigBFk2fP9OLgdl5qKFLs2MGc19jFlx5+/kjXnEsdQTFbGY1Sizzt0TVDw=="], + + "@tiptap/extension-list-keymap": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/extension-list": "^3.15.3" } }, "sha512-UxqnTEEAKrL+wFQeSyC9z0mgyUUVRS2WTcVFoLZCE6/Xus9F53S4bl7VKFadjmqI4GpDk5Oe2IOUc72o129jWg=="], + + "@tiptap/extension-ordered-list": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/extension-list": "^3.15.3" } }, "sha512-/8uhw528Iy0c9wF6tHCiIn0ToM0Ml6Ll2c/3iPRnKr4IjXwx2Lr994stUFihb+oqGZwV1J8CPcZJ4Ufpdqi4Dw=="], + + "@tiptap/extension-paragraph": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-lc0Qu/1AgzcEfS67NJMj5tSHHhH6NtA6uUpvppEKGsvJwgE2wKG1onE4isrVXmcGRdxSMiCtyTDemPNMu6/ozQ=="], + + "@tiptap/extension-placeholder": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/extensions": "^3.15.3" } }, "sha512-XcHHnojT186hKIoOgcPBesXk89+caNGVUdMtc171Vcr/5s0dpnr4q5LfE+YRC+S85CpCxCRRnh84Ou+XRtOqrw=="], + + "@tiptap/extension-strike": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-Y1P3eGNY7RxQs2BcR6NfLo9VfEOplXXHAqkOM88oowWWOE7dMNeFFZM9H8HNxoQgXJ7H0aWW9B7ZTWM9hWli2Q=="], + + "@tiptap/extension-task-item": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/extension-list": "^3.15.3" } }, "sha512-bkrmouc1rE5n9ONw2G7+zCGfBRoF2HJWq8REThPMzg/6+L5GJJ5YTN4UmncaP48U9jHX8xeihjgg9Ypenjl4lw=="], + + "@tiptap/extension-task-list": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/extension-list": "^3.15.3" } }, "sha512-nh8iBk1LHVIoqxphLoqZlLAN9fF2i9ZeK+2TjGSS35lfh7sYzRoSjNW0E81Uy48YuCzM1NQYghYR5Qfc7vm4jA=="], + + "@tiptap/extension-text": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-MhkBz8ZvrqOKtKNp+ZWISKkLUlTrDR7tbKZc2OnNcUTttL9dz0HwT+cg91GGz19fuo7ttDcfsPV6eVmflvGToA=="], + + "@tiptap/extension-underline": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3" } }, "sha512-r/IwcNN0W366jGu4Y0n2MiFq9jGa4aopOwtfWO4d+J0DyeS2m7Go3+KwoUqi0wQTiVU74yfi4DF6eRsMQ9/iHQ=="], + + "@tiptap/extensions": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-ycx/BgxR4rc9tf3ZyTdI98Z19yKLFfqM3UN+v42ChuIwkzyr9zyp7kG8dB9xN2lNqrD+5y/HyJobz/VJ7T90gA=="], + + "@tiptap/markdown": ["@tiptap/[email protected]", "", { "dependencies": { "marked": "^15.0.12" }, "peerDependencies": { "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-JjjZ/X7H2+/Jeapk8GurbncJVyG9ai5YD/eJLBKDyWqsoQwsrHbDlYBx26q9J5VwWXzxe9G6fmHNlAfw0Pokow=="], + + "@tiptap/pm": ["@tiptap/[email protected]", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-Zm1BaU1TwFi3CQiisxjgnzzIus+q40bBKWLqXf6WEaus8Z6+vo1MT2pU52dBCMIRaW9XNDq3E5cmGtMc1AlveA=="], + + "@tiptap/react": ["@tiptap/[email protected]", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "fast-equals": "^5.3.3", "use-sync-external-store": "^1.4.0" }, "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.15.3", "@tiptap/extension-floating-menu": "^3.15.3" }, "peerDependencies": { "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XvouB+Hrqw8yFmZLPEh+HWlMeRSjZfHSfWfWuw5d8LSwnxnPeu3Bg/rjHrRrdwb+7FumtzOnNWMorpb/PSOttQ=="], + + "@tiptap/starter-kit": ["@tiptap/[email protected]", "", { "dependencies": { "@tiptap/core": "^3.15.3", "@tiptap/extension-blockquote": "^3.15.3", "@tiptap/extension-bold": "^3.15.3", "@tiptap/extension-bullet-list": "^3.15.3", "@tiptap/extension-code": "^3.15.3", "@tiptap/extension-code-block": "^3.15.3", "@tiptap/extension-document": "^3.15.3", "@tiptap/extension-dropcursor": "^3.15.3", "@tiptap/extension-gapcursor": "^3.15.3", "@tiptap/extension-hard-break": "^3.15.3", "@tiptap/extension-heading": "^3.15.3", "@tiptap/extension-horizontal-rule": "^3.15.3", "@tiptap/extension-italic": "^3.15.3", "@tiptap/extension-link": "^3.15.3", "@tiptap/extension-list": "^3.15.3", "@tiptap/extension-list-item": "^3.15.3", "@tiptap/extension-list-keymap": "^3.15.3", "@tiptap/extension-ordered-list": "^3.15.3", "@tiptap/extension-paragraph": "^3.15.3", "@tiptap/extension-strike": "^3.15.3", "@tiptap/extension-text": "^3.15.3", "@tiptap/extension-underline": "^3.15.3", "@tiptap/extensions": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-ia+eQr9Mt1ln2UO+kK4kFTJOrZK4GhvZXFjpCCYuHtco3rhr2fZAIxEEY4cl/vo5VO5WWyPqxhkFeLcoWmNjSw=="], + + "@tiptap/suggestion": ["@tiptap/[email protected]", "", { "peerDependencies": { "@tiptap/core": "^3.15.3", "@tiptap/pm": "^3.15.3" } }, "sha512-+CbaHhPfKUe+fNpUIQaOPhh6xI+xL5jbK1zw++U+CZIRrVAAmHRhO+D0O2HdiE1RK7596y8bRqMiB2CRHF7emA=="], + "@tokenizer/inflate": ["@tokenizer/[email protected]", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], "@tokenizer/token": ["@tokenizer/[email protected]", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], @@ -1836,10 +1924,16 @@ "@types/katex": ["@types/[email protected]", "", {}, "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg=="], + "@types/linkify-it": ["@types/[email protected]", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], + "@types/lodash": ["@types/[email protected]", "", {}, "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA=="], + "@types/markdown-it": ["@types/[email protected]", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], + "@types/mdast": ["@types/[email protected]", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + "@types/mdurl": ["@types/[email protected]", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], + "@types/mdx": ["@types/[email protected]", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], "@types/minimatch": ["@types/[email protected]", "", {}, "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="], @@ -1874,6 +1968,8 @@ "@types/urijs": ["@types/[email protected]", "", {}, "sha512-wkXrVzX5yoqLnndOwFsieJA7oKM8cNkOKJtf/3vVGSUFkWDKZvFHpIl9Pvqb/T9UsawBBFMTTD8xu7sK5MWuvg=="], + "@types/use-sync-external-store": ["@types/[email protected]", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], + "@types/yauzl": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/[email protected]", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.0", "@typescript-eslint/type-utils": "8.53.0", "@typescript-eslint/utils": "8.53.0", "@typescript-eslint/visitor-keys": "8.53.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.53.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg=="], @@ -2336,6 +2432,8 @@ "cosmiconfig": ["[email protected]", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], + "crelt": ["[email protected]", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], + "cron-schedule": ["[email protected]", "", {}, "sha512-BoZaseYGXOo5j5HUwTaegIog3JJbuH4BbrY9A1ArLjXpy+RWb3mV28F/9Gv1dDA7E2L8kngWva4NWisnLTyfgQ=="], "cross-spawn": ["[email protected]", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -3266,6 +3364,10 @@ "linkedom": ["[email protected]", "", { "dependencies": { "css-select": "^5.1.0", "cssom": "^0.5.0", "html-escaper": "^3.0.3", "htmlparser2": "^10.0.0", "uhyphen": "^0.2.0" }, "peerDependencies": { "canvas": ">= 2" }, "optionalPeers": ["canvas"] }, "sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q=="], + "linkify-it": ["[email protected]", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + + "linkifyjs": ["[email protected]", "", {}, "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA=="], + "listr2": ["[email protected]", "", { "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ=="], "loader-runner": ["[email protected]", "", {}, "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q=="], @@ -3316,9 +3418,11 @@ "markdown-extensions": ["[email protected]", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], + "markdown-it": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="], + "markdown-table": ["[email protected]", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], - "marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], + "marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], "marky": ["[email protected]", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="], @@ -3362,6 +3466,8 @@ "mdast-util-to-string": ["[email protected]", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + "mdurl": ["[email protected]", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], + "media-query-parser": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.12.5" } }, "sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w=="], "media-typer": ["[email protected]", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], @@ -3614,6 +3720,8 @@ "ora": ["[email protected]", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + "orderedmap": ["[email protected]", "", {}, "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="], + "os-shim": ["[email protected]", "", {}, "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A=="], "own-keys": ["[email protected]", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], @@ -3792,6 +3900,42 @@ "property-information": ["[email protected]", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + "prosemirror-changeset": ["[email protected]", "", { "dependencies": { "prosemirror-transform": "^1.0.0" } }, "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ=="], + + "prosemirror-collab": ["[email protected]", "", { "dependencies": { "prosemirror-state": "^1.0.0" } }, "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ=="], + + "prosemirror-commands": ["[email protected]", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.10.2" } }, "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w=="], + + "prosemirror-dropcursor": ["[email protected]", "", { "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0", "prosemirror-view": "^1.1.0" } }, "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw=="], + + "prosemirror-gapcursor": ["[email protected]", "", { "dependencies": { "prosemirror-keymap": "^1.0.0", "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-view": "^1.0.0" } }, "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ=="], + + "prosemirror-history": ["[email protected]", "", { "dependencies": { "prosemirror-state": "^1.2.2", "prosemirror-transform": "^1.0.0", "prosemirror-view": "^1.31.0", "rope-sequence": "^1.3.0" } }, "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg=="], + + "prosemirror-inputrules": ["[email protected]", "", { "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.0.0" } }, "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw=="], + + "prosemirror-keymap": ["[email protected]", "", { "dependencies": { "prosemirror-state": "^1.0.0", "w3c-keyname": "^2.2.0" } }, "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw=="], + + "prosemirror-markdown": ["[email protected]", "", { "dependencies": { "@types/markdown-it": "^14.0.0", "markdown-it": "^14.0.0", "prosemirror-model": "^1.25.0" } }, "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g=="], + + "prosemirror-menu": ["[email protected]", "", { "dependencies": { "crelt": "^1.0.0", "prosemirror-commands": "^1.0.0", "prosemirror-history": "^1.0.0", "prosemirror-state": "^1.0.0" } }, "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ=="], + + "prosemirror-model": ["[email protected]", "", { "dependencies": { "orderedmap": "^2.0.0" } }, "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA=="], + + "prosemirror-schema-basic": ["[email protected]", "", { "dependencies": { "prosemirror-model": "^1.25.0" } }, "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ=="], + + "prosemirror-schema-list": ["[email protected]", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.7.3" } }, "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q=="], + + "prosemirror-state": ["[email protected]", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", "prosemirror-view": "^1.27.0" } }, "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw=="], + + "prosemirror-tables": ["[email protected]", "", { "dependencies": { "prosemirror-keymap": "^1.2.3", "prosemirror-model": "^1.25.4", "prosemirror-state": "^1.4.4", "prosemirror-transform": "^1.10.5", "prosemirror-view": "^1.41.4" } }, "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw=="], + + "prosemirror-trailing-node": ["[email protected]", "", { "dependencies": { "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { "prosemirror-model": "^1.22.1", "prosemirror-state": "^1.4.2", "prosemirror-view": "^1.33.8" } }, "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ=="], + + "prosemirror-transform": ["[email protected]", "", { "dependencies": { "prosemirror-model": "^1.21.0" } }, "sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw=="], + + "prosemirror-view": ["[email protected]", "", { "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } }, "sha512-UDQbIPnDrjE8tqUBbPmCOZgtd75htE6W3r0JCmY9bL6W1iemDM37MZEKC49d+tdQ0v/CKx4gjxLoLsfkD2NiZA=="], + "proto-list": ["[email protected]", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="], "protobufjs": ["[email protected]", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], @@ -3810,6 +3954,8 @@ "punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "punycode.js": ["[email protected]", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + "pupa": ["[email protected]", "", { "dependencies": { "escape-goat": "^4.0.0" } }, "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA=="], "puppeteer": ["[email protected]", "", { "dependencies": { "@puppeteer/browsers": "2.3.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1312386", "puppeteer-core": "22.14.0" }, "bin": { "puppeteer": "lib/esm/puppeteer/node/cli.js" } }, "sha512-MGTR6/pM8zmWbTdazb6FKnwIihzsSEXBPH49mFFU96DNZpQOevCAZMnjBZGlZRGRzRK6aADCavR6SQtrbv5dQw=="], @@ -3998,6 +4144,8 @@ "rollup": ["[email protected]", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.1", "@rollup/rollup-android-arm64": "4.55.1", "@rollup/rollup-darwin-arm64": "4.55.1", "@rollup/rollup-darwin-x64": "4.55.1", "@rollup/rollup-freebsd-arm64": "4.55.1", "@rollup/rollup-freebsd-x64": "4.55.1", "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", "@rollup/rollup-linux-arm-musleabihf": "4.55.1", "@rollup/rollup-linux-arm64-gnu": "4.55.1", "@rollup/rollup-linux-arm64-musl": "4.55.1", "@rollup/rollup-linux-loong64-gnu": "4.55.1", "@rollup/rollup-linux-loong64-musl": "4.55.1", "@rollup/rollup-linux-ppc64-gnu": "4.55.1", "@rollup/rollup-linux-ppc64-musl": "4.55.1", "@rollup/rollup-linux-riscv64-gnu": "4.55.1", "@rollup/rollup-linux-riscv64-musl": "4.55.1", "@rollup/rollup-linux-s390x-gnu": "4.55.1", "@rollup/rollup-linux-x64-gnu": "4.55.1", "@rollup/rollup-linux-x64-musl": "4.55.1", "@rollup/rollup-openbsd-x64": "4.55.1", "@rollup/rollup-openharmony-arm64": "4.55.1", "@rollup/rollup-win32-arm64-msvc": "4.55.1", "@rollup/rollup-win32-ia32-msvc": "4.55.1", "@rollup/rollup-win32-x64-gnu": "4.55.1", "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A=="], + "rope-sequence": ["[email protected]", "", {}, "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="], + "rou3": ["[email protected]", "", {}, "sha512-1HSG1ENTj7Kkm5muMnXuzzfdDOf7CFnbSYFA+H3Fp/rB9lOCxCPgy1jlZxTKyFoC5jJay8Mmc+VbPLYRjzYLrA=="], "roughjs": ["[email protected]", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], @@ -4218,6 +4366,8 @@ "swr": ["[email protected]", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w=="], + "tabbable": ["[email protected]", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], + "tagged-tag": ["[email protected]", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], "tailwind-merge": ["[email protected]", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], @@ -4346,6 +4496,8 @@ "typescript-eslint": ["[email protected]", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.53.0", "@typescript-eslint/parser": "8.53.0", "@typescript-eslint/typescript-estree": "8.53.0", "@typescript-eslint/utils": "8.53.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw=="], + "uc.micro": ["[email protected]", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + "ufo": ["[email protected]", "", {}, "sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q=="], "uhyphen": ["[email protected]", "", {}, "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA=="], @@ -4420,6 +4572,8 @@ "use-callback-ref": ["[email protected]", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], + "use-debounce": ["[email protected]", "", { "peerDependencies": { "react": "*" } }, "sha512-lu87Za35V3n/MyMoEpD5zJv0k7hCn0p+V/fK2kWD+3k2u3kOCwO593UArbczg1fhfs2rqPEnHpULJ3KmGdDzvg=="], + "use-sidecar": ["[email protected]", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], "use-sync-external-store": ["[email protected]", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], @@ -4466,6 +4620,8 @@ "vscode-uri": ["[email protected]", "", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="], + "w3c-keyname": ["[email protected]", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + "warning": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w=="], "watchpack": ["[email protected]", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA=="], @@ -4598,12 +4754,18 @@ "@ai-sdk/provider-utils/nanoid": ["[email protected]", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "@ai-sdk/react/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-4Dv/wiGZrvO6fI7P0yMLa4XZru0XW8LPibTObbkHBdweLUVGIze7aCfxxQeY44Uqcbl/h6/yBTkx2XmPtwf/Ow=="], + "@ai-sdk/react/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.3", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ItzTdBxRLieGz1GHPwl9X3+HKfwTfFd9MdIa91aXRnOjUVRw68ENjAGKm3FcXGsBLkXDLaFWgjbTVdXe2igs2w=="], - "@ai-sdk/react/ai": ["[email protected]", "", { "dependencies": { "@ai-sdk/gateway": "1.0.0-beta.10", "@ai-sdk/provider": "2.0.0-beta.1", "@ai-sdk/provider-utils": "3.0.0-beta.5", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" }, "bin": { "ai": "dist/bin/ai.min.js" } }, "sha512-glQIA+PGEP+UEPB+thdqNZi9Ot4Yjiqsl071S1KPaRTGHmBIg/c8OYb2mXCRM+3cNCFGVnCTudZoYUVNwBpFxg=="], + "@ai-sdk/react/ai": ["[email protected]", "", { "dependencies": { "@ai-sdk/gateway": "3.0.15", "@ai-sdk/provider": "3.0.3", "@ai-sdk/provider-utils": "4.0.7", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-GUMBYx0TXKxXRcWy6DY3ryHJZ7cATxc99WPT7WCD8hGCYkdhFVItKPTtINeTlK+FlUomDqzjPGtiDcVftcw//w=="], "@ai-sdk/ui-utils/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], + "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.3", "@ai-sdk/provider-utils": "4.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dM+CXjW3+QAZVl2NKhch9T5gCa8p7CMJR16KtaRS9HzY8H/ASD8hiAssnDGz00XyOKYpDkd9Mj2mwK25u5LxnQ=="], + + "@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qGPYdoAuECaUXPrrz0BPX1SacZQuJ6zky0aakxpW89QW1hrY0eF4gcFm/3L9Pk8C5Fwe+RvBf2z7ZjDhaPjnlg=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.3", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-o/SP1GQOrpXAzHjMosPHI0Pu+YkwxIMndSjSLrEXtcVixdrjqrGaA9I7xJcWf+XpRFJ9byPHrKYnprwS+36gMg=="], + "@aklinker1/rollup-plugin-visualizer/open": ["[email protected]", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], "@aklinker1/rollup-plugin-visualizer/picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -5040,9 +5202,9 @@ "@repo/docs/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "@repo/web/@ai-sdk/google": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2XUnGi3f7TV4ujoAhA+Fg3idUoG/+Y2xjCRg70a1/m0DH1KSQqYaCboJ1C19y6ZHGdf5KNT20eJdswP6TvrY2g=="], + "@repo/web/@ai-sdk/google": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.3", "@ai-sdk/provider-utils": "4.0.7" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-whRdK0gCZL92UbEHdmQ6edm3cp5ZZSqwE79AIvTEaJR+BeaMUJchTxz/I5w9l3EHGOiDxrKYssklqO3z45KGXg=="], - "@repo/web/ai": ["[email protected]", "", { "dependencies": { "@ai-sdk/gateway": "1.0.0-beta.10", "@ai-sdk/provider": "2.0.0-beta.1", "@ai-sdk/provider-utils": "3.0.0-beta.5", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" }, "bin": { "ai": "dist/bin/ai.min.js" } }, "sha512-glQIA+PGEP+UEPB+thdqNZi9Ot4Yjiqsl071S1KPaRTGHmBIg/c8OYb2mXCRM+3cNCFGVnCTudZoYUVNwBpFxg=="], + "@repo/web/ai": ["[email protected]", "", { "dependencies": { "@ai-sdk/gateway": "3.0.14", "@ai-sdk/provider": "3.0.3", "@ai-sdk/provider-utils": "4.0.6", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MxgtU6CjnegH1rhRfomM0gptKxP6r+9sxbLvYq36C1l85+o0LacqbXLdNVYzqab+lHN4q7ZP3QS8wZq4YkZahA=="], "@repo/web/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -5354,6 +5516,8 @@ "memory-graph-playground/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "mermaid/marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], + "mermaid/uuid": ["[email protected]", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], "micromatch/picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -5474,6 +5638,8 @@ "streamdown/lucide-react": ["[email protected]", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw=="], + "streamdown/marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], + "string-width-cjs/emoji-regex": ["[email protected]", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -5564,11 +5730,15 @@ "wxt/vite-node": ["[email protected]", "", { "dependencies": { "cac": "^6.7.14", "es-module-lexer": "^1.7.0", "obug": "^2.0.0", "pathe": "^2.0.3", "vite": "^7.2.2" }, "bin": { "vite-node": "dist/cli.mjs" } }, "sha512-7UT39YxUukIA97zWPXUGb0SGSiLexEGlavMwU3HDE6+d/HJhKLjLqu4eX2qv6SQiocdhKLRcusroDwXHQ6CnRQ=="], - "@ai-sdk/react/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Z8SPncMtS3RsoXITmT7NVwrAq6M44dmw0DoUOYJqNNtCu8iMWuxB8Nxsoqpa0uEEy9R1V1ZThJAXTYgjTUxl3w=="], + "@ai-sdk/react/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qGPYdoAuECaUXPrrz0BPX1SacZQuJ6zky0aakxpW89QW1hrY0eF4gcFm/3L9Pk8C5Fwe+RvBf2z7ZjDhaPjnlg=="], - "@ai-sdk/react/ai/@ai-sdk/gateway": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.1", "@ai-sdk/provider-utils": "3.0.0-beta.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-v+LXXm8INLYAdxHnNMVAJ/B7k+Nejn5dCQMg/F8SRetB5dEQ4sbfimE+b6rawILJznnsy2fugUO1oFFXlUS5Yg=="], + "@ai-sdk/react/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@ai-sdk/react/ai/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Z8SPncMtS3RsoXITmT7NVwrAq6M44dmw0DoUOYJqNNtCu8iMWuxB8Nxsoqpa0uEEy9R1V1ZThJAXTYgjTUxl3w=="], + "@ai-sdk/react/ai/@ai-sdk/gateway": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.3", "@ai-sdk/provider-utils": "4.0.7", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OsWcXMfkF9U38YhU7926rYt4IAtJuZlnM1e8STN8hHb4qbiTaORBSXcFaJuOEZ1kYOf3DqqP7CxbmM543ePzIg=="], + + "@ai-sdk/react/ai/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qGPYdoAuECaUXPrrz0BPX1SacZQuJ6zky0aakxpW89QW1hrY0eF4gcFm/3L9Pk8C5Fwe+RvBf2z7ZjDhaPjnlg=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@aklinker1/rollup-plugin-visualizer/open/define-lazy-prop": ["[email protected]", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], @@ -6076,13 +6246,15 @@ "@raycast/api/esbuild/@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "@repo/web/@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], + "@repo/web/@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qGPYdoAuECaUXPrrz0BPX1SacZQuJ6zky0aakxpW89QW1hrY0eF4gcFm/3L9Pk8C5Fwe+RvBf2z7ZjDhaPjnlg=="], + + "@repo/web/@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.3", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ItzTdBxRLieGz1GHPwl9X3+HKfwTfFd9MdIa91aXRnOjUVRw68ENjAGKm3FcXGsBLkXDLaFWgjbTVdXe2igs2w=="], - "@repo/web/ai/@ai-sdk/gateway": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.1", "@ai-sdk/provider-utils": "3.0.0-beta.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-v+LXXm8INLYAdxHnNMVAJ/B7k+Nejn5dCQMg/F8SRetB5dEQ4sbfimE+b6rawILJznnsy2fugUO1oFFXlUS5Yg=="], + "@repo/web/ai/@ai-sdk/gateway": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.3", "@ai-sdk/provider-utils": "4.0.6", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-udVpkDaQ00jMcBvtGGvmkEBU31XidsHB4E8HIF9l7/H7lyjOS/EtXzN2adoupDg5j1/VjjSI3Ny5P1zHUvLyMA=="], - "@repo/web/ai/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Z8SPncMtS3RsoXITmT7NVwrAq6M44dmw0DoUOYJqNNtCu8iMWuxB8Nxsoqpa0uEEy9R1V1ZThJAXTYgjTUxl3w=="], + "@repo/web/ai/@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qGPYdoAuECaUXPrrz0BPX1SacZQuJ6zky0aakxpW89QW1hrY0eF4gcFm/3L9Pk8C5Fwe+RvBf2z7ZjDhaPjnlg=="], - "@repo/web/ai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-4Dv/wiGZrvO6fI7P0yMLa4XZru0XW8LPibTObbkHBdweLUVGIze7aCfxxQeY44Uqcbl/h6/yBTkx2XmPtwf/Ow=="], + "@repo/web/ai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.3", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-o/SP1GQOrpXAzHjMosPHI0Pu+YkwxIMndSjSLrEXtcVixdrjqrGaA9I7xJcWf+XpRFJ9byPHrKYnprwS+36gMg=="], "@stoplight/better-ajv-errors/ajv/json-schema-traverse": ["[email protected]", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], @@ -6608,6 +6780,10 @@ "@puppeteer/browsers/yargs/string-width/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@repo/web/@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@repo/web/ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "agents/@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["[email protected]", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "agents/@modelcontextprotocol/sdk/express/accepts": ["[email protected]", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], diff --git a/packages/ui/components/button.tsx b/packages/ui/components/button.tsx index 9d0d6070..ae77aa5d 100644 --- a/packages/ui/components/button.tsx +++ b/packages/ui/components/button.tsx @@ -27,7 +27,7 @@ const buttonVariants = cva( "rounded-xl !px-6 !py-3 bg-black border border-[#161F2C] hover:bg-[#161F2C] !h-[40px] cursor-pointer text-white", linkPreview: "rounded-xl !px-3 !py-1 bg-black border border-[#161F2C] hover:bg-[#161F2C] cursor-pointer text-white border border-[#161F2C]", - insideOut: "shadow-inside-out rounded-full bg-[#0D121A]", + insideOut: "shadow-inside-out rounded-full bg-[#0D121A] cursor-pointer", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", |