From 186efa4244846bf761c7cf4f9cc5d1087b8f95df Mon Sep 17 00:00:00 2001 From: Dhravya Shah Date: Fri, 14 Feb 2025 12:43:55 -0800 Subject: delete spaces --- apps/backend/src/index.tsx | 29 +- apps/backend/src/routes/actions.ts | 17 +- apps/backend/src/routes/spaces.ts | 38 +- apps/extension/manifest.json | 2 +- apps/extension/src/background.ts | 2 +- apps/web/app/components/Landing.tsx | 430 ------------------ apps/web/app/components/Landing/Feature.tsx | 157 +++++++ apps/web/app/components/Landing/Footer.tsx | 142 ++++++ apps/web/app/components/Landing/Hero.tsx | 290 ++++++++++++ apps/web/app/components/Landing/Note.tsx | 73 +++ apps/web/app/components/Landing/Private.tsx | 62 +++ apps/web/app/components/Landing/index.tsx | 17 + apps/web/app/components/Landing/plus-grid.tsx | 88 ++++ apps/web/app/components/editor/use-chat.tsx | 492 ++++++++++----------- .../app/components/editor/use-create-editor.tsx | 285 ++++++------ .../app/components/editor/writing-playground.tsx | 242 ++++++++++ apps/web/app/components/icons/IntegrationIcons.tsx | 46 ++ apps/web/app/components/memories/SharedCard.tsx | 2 +- apps/web/app/lib/hooks/use-chat-stream.ts | 2 +- apps/web/app/lib/hooks/use-live-transcript.tsx | 132 ++++++ apps/web/app/lib/hooks/use-spaces.tsx | 1 + apps/web/app/routes/_index.tsx | 20 +- apps/web/app/routes/editor.tsx | 18 +- apps/web/app/routes/onboarding.privacy.tsx | 20 +- apps/web/app/routes/privacy.tsx | 8 +- apps/web/app/routes/space.$spaceId.tsx | 22 +- apps/web/public/product-of-the-day.png | Bin 0 -> 79406 bytes apps/web/server/index.ts | 114 ++++- package.json | 4 + 29 files changed, 1817 insertions(+), 938 deletions(-) delete mode 100644 apps/web/app/components/Landing.tsx create mode 100644 apps/web/app/components/Landing/Feature.tsx create mode 100644 apps/web/app/components/Landing/Footer.tsx create mode 100644 apps/web/app/components/Landing/Hero.tsx create mode 100644 apps/web/app/components/Landing/Note.tsx create mode 100644 apps/web/app/components/Landing/Private.tsx create mode 100644 apps/web/app/components/Landing/index.tsx create mode 100644 apps/web/app/components/Landing/plus-grid.tsx create mode 100644 apps/web/app/components/editor/writing-playground.tsx create mode 100644 apps/web/app/lib/hooks/use-live-transcript.tsx create mode 100644 apps/web/public/product-of-the-day.png diff --git a/apps/backend/src/index.tsx b/apps/backend/src/index.tsx index 55aa58c7..1d62b543 100644 --- a/apps/backend/src/index.tsx +++ b/apps/backend/src/index.tsx @@ -128,28 +128,31 @@ export const app = new Hono<{ Variables: Variables; Bindings: Env }>() .all("/api/*", async (c) => { // Get the full URL and path const url = new URL(c.req.url); - const path = url.pathname.replace("/api", "/v1"); + const path = url.pathname; + const newPath = path.replace("/api", "/v1"); // Preserve query parameters and build target URL - const redirectUrl = path + url.search; - - // Forward the request with same method, headers and body - const response = await fetch(redirectUrl, { - method: c.req.method, - headers: c.req.raw.headers, - body: - c.req.method !== "GET" && c.req.method !== "HEAD" - ? await c.req.blob() - : undefined, - }); + const redirectUrl = "https://api.supermemory.ai" + newPath + url.search; - return response; + // Use c.redirect() for a proper redirect + return c.redirect(redirectUrl); }) .route("/v1/user", user) .route("/v1/spaces", spacesRoute) .route("/v1", actions) .route("/v1/integrations", integrations) .route("/v1/memories", memories) + .get("/v1/session", (c) => { + const user = c.get("user"); + + if (!user) { + return c.json({ error: "Unauthorized" }, 401); + } + + return c.json({ + user, + }); + }) .post( "/waitlist", zValidator( diff --git a/apps/backend/src/routes/actions.ts b/apps/backend/src/routes/actions.ts index bcddec43..c0801ada 100644 --- a/apps/backend/src/routes/actions.ts +++ b/apps/backend/src/routes/actions.ts @@ -88,7 +88,7 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() apiKey: c.env.BRAINTRUST_API_KEY, }); - const googleClient = wrapAISDKModel(openai(c.env).chat("gpt-4o")); + const googleClient = wrapAISDKModel(openai(c.env).chat("gpt-4o-mini-2024-07-18")); // Get last user message and generate embedding in parallel with thread creation let lastUserMessage = coreMessages.findLast((i) => i.role === "user"); @@ -170,8 +170,17 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() try { const data = new StreamData(); + // De-duplicate chunks by URL to avoid showing duplicate content + const uniqueResults = finalResults.reduce((acc, current) => { + const existingResult = acc.find(item => item.id === current.id); + if (!existingResult) { + acc.push(current); + } + return acc; + }, [] as typeof finalResults); + data.appendMessageAnnotation( - finalResults.map((r) => ({ + uniqueResults.map((r) => ({ id: r.id, content: r.content, type: r.type, @@ -414,7 +423,7 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() .orderBy(sql`RANDOM()`) .limit(7); - if (recentLearnings.length === 0) { + if (recentLearnings.length === 0 || recentLearnings.length < 3) { return c.json({ suggestedLearnings: [] }); } @@ -425,7 +434,7 @@ const actions = new Hono<{ Variables: Variables; Bindings: Env }>() const model = openai(c.env).chat("gpt-4o-mini-2024-07-18"); const prompt = `Generate a concise topic recall card for this document. The card should: - Have a clear title that captures the main topic - - Include a brief "Last week, you saved notes on..." intro (do something different every time.) + - based on when the document was saved, include a brief "Last (week/month/...), you saved notes on..." intro (do something different every time.) - List 2-3 key points from the content in simple bullet points - Keep the total length under 280 characters - Focus on the core concepts worth remembering diff --git a/apps/backend/src/routes/spaces.ts b/apps/backend/src/routes/spaces.ts index aa9c905d..2ca2e461 100644 --- a/apps/backend/src/routes/spaces.ts +++ b/apps/backend/src/routes/spaces.ts @@ -118,6 +118,7 @@ const spacesRoute = new Hono<{ Variables: Variables; Bindings: Env }>() canRead: true, canEdit, isOwner: space[0].ownerId === user?.id, + isPublic: space[0].isPublic, }, }); } @@ -156,6 +157,7 @@ const spacesRoute = new Hono<{ Variables: Variables; Bindings: Env }>() canRead: true, canEdit, isOwner: space[0].ownerId === user.id, + isPublic: space[0].isPublic, }, }); }) @@ -254,16 +256,8 @@ const spacesRoute = new Hono<{ Variables: Variables; Bindings: Env }>() error instanceof Error && error.message.includes("saved_spaces_user_space_idx") ) { - // Space is already favorited - remove it - await db - .delete(savedSpaces) - .where( - and( - eq(savedSpaces.userId, user.id), - eq(savedSpaces.spaceId, space[0].id) - ) - ); - return c.json({ message: "Space unfavorited successfully" }); + // Space is already favorited + return c.json({ message: "Space already favorited" }); } throw error; } @@ -524,6 +518,28 @@ const spacesRoute = new Hono<{ Variables: Variables; Bindings: Env }>() return c.json({ success: true }); } - ); + ).delete("/:spaceId", async (c) => { + const user = c.get("user"); + if (!user) { + return c.json({ error: "Unauthorized" }, 401); + } + + const { spaceId } = c.req.param(); + const db = database(c.env.HYPERDRIVE.connectionString); + + const space = await db + .select() + .from(spaces) + .where(eq(spaces.uuid, spaceId)) + .limit(1); + + if (space.length === 0) { + return c.json({ error: "Space not found" }, 404); + } + + await db.delete(spaces).where(eq(spaces.uuid, spaceId)); + + return c.json({ success: true }); + }); export default spacesRoute; diff --git a/apps/extension/manifest.json b/apps/extension/manifest.json index d51d27b4..98d42318 100644 --- a/apps/extension/manifest.json +++ b/apps/extension/manifest.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/chrome-manifest.json", "manifest_version": 3, - "version": "5.0.12", + "version": "5.0.135", "homepage_url": "https://supermemory.ai", "name": "Supermemory", "description": "An extension for https://supermemory.ai - an AI hub for all your knowledge.", diff --git a/apps/extension/src/background.ts b/apps/extension/src/background.ts index 7d96feff..b02d027f 100644 --- a/apps/extension/src/background.ts +++ b/apps/extension/src/background.ts @@ -14,7 +14,7 @@ const tabStates = new Map(); const checkIfLoggedIn = async () => { const baseURL = await getBaseURL(); - const response = await fetch(`${baseURL}/v1/session`); + const response = await fetch(`${baseURL}/backend/v1/session`); return response.status == 200; }; diff --git a/apps/web/app/components/Landing.tsx b/apps/web/app/components/Landing.tsx deleted file mode 100644 index 3ed8fa7c..00000000 --- a/apps/web/app/components/Landing.tsx +++ /dev/null @@ -1,430 +0,0 @@ -import { useEffect, useRef } from "react"; - -import { Logo } from "./icons/Logo"; - -import { motion, useMotionTemplate, useScroll, useSpring, useTransform } from "framer-motion"; - -// Interfaces -interface Memory { - x: number; - y: number; - size: number; - type: "bookmark" | "note" | "tweet" | "doc"; - color: string; - orbitRadius: number; - orbitSpeed: number; - orbitOffset: number; - opacity: number; -} - -// Components -const ProductHuntBadge = () => ( - - Supermemory - #1 Product of the Day on Product Hunt - -); - -const SupermemoryBackground = () => { - const canvasRef = useRef(null); - const memoriesRef = useRef([]); - const rafRef = useRef(); - const centerRef = useRef({ x: 0, y: 0 }); - const circleRadiusRef = useRef(150); - const targetRadiusRef = useRef(0); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - const resize = () => { - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - centerRef.current = { - x: canvas.width / 2, - y: canvas.height / 2, - }; - targetRadiusRef.current = - Math.sqrt(Math.pow(canvas.width, 2) + Math.pow(canvas.height, 2)) / 1.5; - }; - - const createMemories = () => { - const memories: Memory[] = []; - const types: ("bookmark" | "note" | "tweet" | "doc")[] = ["bookmark", "note", "tweet", "doc"]; - const colors = { - bookmark: "#3B82F6", - note: "#10B981", - tweet: "#60A5FA", - doc: "#818CF8", - }; - - const orbits = [250, 350, 450]; - orbits.forEach((orbitRadius, orbitIndex) => { - const memoriesInOrbit = 8 + orbitIndex * 4; - for (let i = 0; i < memoriesInOrbit; i++) { - const type = types[i % 4]; - const angle = (Math.PI * 2 * i) / memoriesInOrbit; - memories.push({ - x: centerRef.current.x + Math.cos(angle) * orbitRadius, - y: centerRef.current.y + Math.sin(angle) * orbitRadius, - size: 3 + Math.random() * 2, - type, - color: colors[type], - orbitRadius, - orbitSpeed: (0.1 + Math.random() * 0.05) * (1 - orbitIndex * 0.2), - orbitOffset: angle, - opacity: 0.15 + Math.random() * 0.15, - }); - } - }); - - return memories; - }; - - const drawMemory = (ctx: CanvasRenderingContext2D, memory: Memory, time: number) => { - const angle = memory.orbitOffset + time * memory.orbitSpeed; - memory.x = centerRef.current.x + Math.cos(angle) * memory.orbitRadius; - memory.y = centerRef.current.y + Math.sin(angle) * memory.orbitRadius; - - const dx = memory.x - centerRef.current.x; - const dy = memory.y - centerRef.current.y; - const distanceFromCenter = Math.sqrt(dx * dx + dy * dy); - - if (distanceFromCenter <= circleRadiusRef.current) { - // Draw connections - memoriesRef.current.forEach((otherMemory) => { - if (memory === otherMemory) return; - const connectionDx = memory.x - otherMemory.x; - const connectionDy = memory.y - otherMemory.y; - const connectionDistance = Math.sqrt( - connectionDx * connectionDx + connectionDy * connectionDy, - ); - - const otherDx = otherMemory.x - centerRef.current.x; - const otherDy = otherMemory.y - centerRef.current.y; - const otherDistanceFromCenter = Math.sqrt(otherDx * otherDx + otherDy * otherDy); - - if (connectionDistance < 80 && otherDistanceFromCenter <= circleRadiusRef.current) { - const opacity = (1 - connectionDistance / 80) * 0.04; - ctx.beginPath(); - ctx.moveTo(memory.x, memory.y); - ctx.lineTo(otherMemory.x, otherMemory.y); - ctx.strokeStyle = `rgba(59, 130, 246, ${opacity})`; - ctx.lineWidth = 0.5; - ctx.stroke(); - } - }); - - // Draw node - const gradient = ctx.createRadialGradient( - memory.x, - memory.y, - 0, - memory.x, - memory.y, - memory.size * 2, - ); - gradient.addColorStop(0, memory.color.replace(")", `,${memory.opacity})`)); - gradient.addColorStop(1, memory.color.replace(")", ",0)")); - - ctx.beginPath(); - ctx.arc(memory.x, memory.y, memory.size, 0, Math.PI * 2); - ctx.fillStyle = gradient; - ctx.fill(); - } - }; - - memoriesRef.current = createMemories(); - - const animate = () => { - if (!ctx || !canvas) return; - - ctx.fillStyle = "rgba(17, 24, 39, 1)"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - const time = Date.now() * 0.001; - - // Grow circle with easing - const radiusDiff = targetRadiusRef.current - circleRadiusRef.current; - if (Math.abs(radiusDiff) > 1) { - circleRadiusRef.current += radiusDiff * 0.02; - } - - // Create clipping region - ctx.save(); - ctx.beginPath(); - ctx.arc(centerRef.current.x, centerRef.current.y, circleRadiusRef.current, 0, Math.PI * 2); - ctx.clip(); - - // Draw orbit paths - [250, 350, 450].forEach((radius) => { - ctx.beginPath(); - ctx.arc(centerRef.current.x, centerRef.current.y, radius, 0, Math.PI * 2); - ctx.strokeStyle = "rgba(255, 255, 255, 0.02)"; - ctx.stroke(); - }); - - memoriesRef.current.forEach((memory) => drawMemory(ctx, memory, time)); - - ctx.restore(); - rafRef.current = requestAnimationFrame(animate); - }; - - resize(); - window.addEventListener("resize", resize); - animate(); - - return () => { - if (rafRef.current) cancelAnimationFrame(rafRef.current); - window.removeEventListener("resize", resize); - }; - }, []); - - return ( - - ); -}; - -export default function Landing() { - const { scrollYProgress } = useScroll(); - const scrollProgress = useSpring(scrollYProgress); - const boxOpacity = useTransform(scrollYProgress, [0.3, 0.6], [0, 1]); - const boxScale = useTransform(scrollYProgress, [0.3, 0.6], [0.8, 1]); - - return ( -
- - -
- -
- - - supermemory.ai - -
-
- - - Follow us - - - Get Started - -
-
- -
-
-
- -
- - - - - -

- - Your second brain for all -
- your saved content -
-

- - Save anything from anywhere. Supermemory connects your bookmarks, notes, and research - into a powerful, searchable knowledge base. - - {/* -
- - - - Chrome extension for one-click saving -
-
- - - - AI-powered search across all your content -
-
- - - - Integrates with Notion, Twitter, and more -
-
*/} - - - Try it free - - - - - - - - - Star on GitHub - - - - Medium - Notion - Reddit - Twitter - -
-
-
- -
- -
-

- All your knowledge in one place -

-

- Supermemory intelligently organizes and connects your saved content, making it easy to - find and use when you need it. -

- -
-
- ); -} diff --git a/apps/web/app/components/Landing/Feature.tsx b/apps/web/app/components/Landing/Feature.tsx new file mode 100644 index 00000000..e46fa41e --- /dev/null +++ b/apps/web/app/components/Landing/Feature.tsx @@ -0,0 +1,157 @@ +"use client"; +export default function Feature2() { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Meet Supermemory.

+

Your second brain for knowledge.

+

+ Save the things you like, and over time, build the knowledge base of your dreams. +
+ Go down rabbit holes, make connections, search what's important to you. +

+
+
+ ); +} diff --git a/apps/web/app/components/Landing/Footer.tsx b/apps/web/app/components/Landing/Footer.tsx new file mode 100644 index 00000000..3f0b3f3e --- /dev/null +++ b/apps/web/app/components/Landing/Footer.tsx @@ -0,0 +1,142 @@ +import { PlusGrid, PlusGridItem, PlusGridRow } from "./plus-grid"; + +function SitemapHeading({ children }: { children: React.ReactNode }) { + return

{children}

; +} + +function SitemapLinks({ children }: { children: React.ReactNode }) { + return
    {children}
; +} + +function SitemapLink(props: React.ComponentPropsWithoutRef<"a">) { + return ( +
  • + +
  • + ); +} + +function Sitemap() { + return ( + <> +
    + Product + + Documentation + Chrome Extension + iOS Shortcut + +
    +
    + Community + + Discord + + Report Issue + + Get Help + +
    +
    + Legal + + Terms of Service + Privacy Policy + +
    + + ); +} + +function SocialIconX(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + + + ); +} + +function SocialIconGitHub(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + + + ); +} + +function SocialLinks() { + return ( + <> +
    + + + + + + + ); +} + +function Copyright() { + return ( +
    + © {new Date().getFullYear()} Supermemory, Inc. +
    + ); +} + +export default function Footer() { + return ( +
    +
    +
    +
    + + +
    +
    + +

    + Supermemory +

    +

    + Supermemory is a free, open-source AI knowlege platform. +

    +
    +
    +
    + +
    +
    +
    + +
    + + + +
    +
    + + + +
    +
    +
    +
    +
    +
    + ); +} diff --git a/apps/web/app/components/Landing/Hero.tsx b/apps/web/app/components/Landing/Hero.tsx new file mode 100644 index 00000000..06d82dfb --- /dev/null +++ b/apps/web/app/components/Landing/Hero.tsx @@ -0,0 +1,290 @@ +"use client"; + +import { DiscordIcon, GithubIcon } from "../icons/IntegrationIcons"; +import { Logo } from "../icons/Logo"; + +interface PlusPatternBackgroundProps { + plusSize?: number; + plusColor?: string; + backgroundColor?: string; + className?: string; + style?: React.CSSProperties; + fade?: boolean; + [key: string]: any; +} + +export const BackgroundPlus: React.FC = ({ + plusColor = "#CCE5FF", + backgroundColor = "transparent", + plusSize = 60, + className, + fade = true, + style, + ...props +}) => { + const encodedPlusColor = encodeURIComponent(plusColor); + + const maskStyle: React.CSSProperties = fade + ? { + maskImage: "radial-gradient(circle, white 10%, transparent 90%)", + WebkitMaskImage: "radial-gradient(circle, white 10%, transparent 90%)", + } + : {}; + + const backgroundStyle: React.CSSProperties = { + backgroundColor, + backgroundImage: `url("data:image/svg+xml,%3Csvg width='${plusSize}' height='${plusSize}' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='${encodedPlusColor}' fill-opacity='0.5'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`, + ...maskStyle, + ...style, + }; + + return ( +
    + ); +}; + +export default function Hero() { + return ( +
    +
    +
    +
    + + + {/* Header */} +
    +
    +
    + {/* Left section */} +
    +
    + + + supermemory.ai + +
    + +
    + + {/* Right section */} + +
    +
    +
    + + {/* Hero Section */} +
    +
    +
    + {/* Hero Content */} +
    + {/* Announcement Banner */} +
    +
    + + NEW + + Top OSS Repository in 2024 + + Read more → + +
    +
    +

    + AI for all your knowledge. +

    +

    + Supermemory helps you collect, organize, and recall all your knowledge. + {/* list of notable features */} +

      +
    • + + + + Connect with your existing tools and bookmarks +
    • +
    • + + + + Chat and find with AI & actually use your knowledge +
    • +
    • + + + + Share your knowledge with your friends and colleagues +
    • +
    +

    + +
    + + {/* Video Section */} +
    +
    + +
    +
    +
    + + {/* Integration Tags */} +
    +
    + Integrate with your favorite tools +
    +
    + {[ + "Notion", + "Twitter", + "Obsidian", + "Reddit", + "LinkedIn", + "Chrome Extension", + "iOS App", + "Slack", + // "Google Drive", + // "Microsoft Teams" + ].map((tool) => ( +
    + {tool} +
    + ))} +
    +
    +
    +
    +
    + ); +} diff --git a/apps/web/app/components/Landing/Note.tsx b/apps/web/app/components/Landing/Note.tsx new file mode 100644 index 00000000..8fe2d4e1 --- /dev/null +++ b/apps/web/app/components/Landing/Note.tsx @@ -0,0 +1,73 @@ +export default function Note() { + return ( +
    +
    +
    +
    +
    +
    +
    Today
    +
    + +
    +
    + {/* Profile Image */} +
    + Dhravya Shah +
    + + {/* Message Bubble */} +
    +

    + 👋 Hey there! I'm Dhravya + + + I'm a college student who built Supermemory as a weekend project. What started + as a simple idea has grown into something I'm really proud of, thanks to + amazing support from the open-source community! 🚀 + + + + When you see "we" on the website - that's actually just me! 😅 I maintain and + build everything myself, supported by wonderful donors and grants that help + keep this project free and open source. + + + + In this AI-driven world, I believe in augmenting human knowledge rather than + replacing it. My goal is simple: build something that genuinely helps people + learn and grow. 💡 + + + + If you'd like to follow my journey, you can find me on{" "} + + Twitter + {" "} + and{" "} + + GitHub + + . And if you believe in what we're building, consider{" "} + + supporting Supermemory's development + {" "} + ❤️ + +

    +
    +
    +
    +
    +
    +
    +
    + ); +} diff --git a/apps/web/app/components/Landing/Private.tsx b/apps/web/app/components/Landing/Private.tsx new file mode 100644 index 00000000..7e944887 --- /dev/null +++ b/apps/web/app/components/Landing/Private.tsx @@ -0,0 +1,62 @@ +import { Eye, Lock, ShieldCheck } from "lucide-react"; + +export default function Private() { + const privacyFeatures = [ + { icon: ShieldCheck, text: "End-to-end encryption" }, + { icon: Lock, text: "Self-hosted option available" }, + { icon: Eye, text: "Zero knowledge architecture" }, + ]; + + return ( +
    +
    +
    +

    + Your knowledge stays +
    + private and secure +

    + +

    + We take privacy seriously. Your data is fully encrypted, never shared with third + parties. Even on the hosted version, we securely store your data in our own servers. +

    + +
    + {privacyFeatures.map((feature, index) => ( +
    +
    + +
    + {feature.text} +
    + ))} +
    + + +
    +
    +
    + ); +} diff --git a/apps/web/app/components/Landing/index.tsx b/apps/web/app/components/Landing/index.tsx new file mode 100644 index 00000000..0a432ec2 --- /dev/null +++ b/apps/web/app/components/Landing/index.tsx @@ -0,0 +1,17 @@ +import Feature2 from "./Feature"; +import Footer from "./Footer"; +import Hero from "./Hero"; +import Note from "./Note"; +import Private from "./Private"; + +export default function Landing() { + return ( +
    + + + + +
    +
    + ); +} diff --git a/apps/web/app/components/Landing/plus-grid.tsx b/apps/web/app/components/Landing/plus-grid.tsx new file mode 100644 index 00000000..752ccefc --- /dev/null +++ b/apps/web/app/components/Landing/plus-grid.tsx @@ -0,0 +1,88 @@ +import { clsx } from "clsx"; + +export function PlusGrid({ + className = "", + children, +}: { + className?: string; + children: React.ReactNode; +}) { + return
    {children}
    ; +} + +export function PlusGridRow({ + className = "", + children, +}: { + className?: string; + children: React.ReactNode; +}) { + return ( +
    + + {children} +
    + ); +} + +export function PlusGridItem({ + className = "", + children, +}: { + className?: string; + children: React.ReactNode; +}) { + return ( +
    + + + + + {children} +
    + ); +} + +export function PlusGridIcon({ + className = "", + placement, +}: { + className?: string; + placement: `${"top" | "bottom"} ${"right" | "left"}`; +}) { + let [yAxis, xAxis] = placement.split(" "); + + let yClass = yAxis === "top" ? "-top-2" : "-bottom-2"; + let xClass = xAxis === "left" ? "-left-2" : "-right-2"; + + return ( + + ); +} diff --git a/apps/web/app/components/editor/use-chat.tsx b/apps/web/app/components/editor/use-chat.tsx index 8c60f88d..11b6ee6e 100644 --- a/apps/web/app/components/editor/use-chat.tsx +++ b/apps/web/app/components/editor/use-chat.tsx @@ -1,314 +1,270 @@ -'use client'; +"use client"; -import { type ReactNode, createContext, useContext, useState } from 'react'; +import { type ReactNode, createContext, useContext, useState } from "react"; -import { faker } from '@faker-js/faker'; -import { cn } from '@udecode/cn'; -import { CopilotPlugin } from '@udecode/plate-ai/react'; -import { useEditorPlugin } from '@udecode/plate-common/react'; -import { useChat as useBaseChat } from 'ai/react'; +import { faker } from "@faker-js/faker"; +import { cn } from "@udecode/cn"; +import { CopilotPlugin } from "@udecode/plate-ai/react"; +import { useEditorPlugin } from "@udecode/plate-common/react"; +import { useChat as useBaseChat } from "ai/react"; +import { ArrowUpRight, Check, ChevronsUpDown, Eye, EyeOff, Settings } from "lucide-react"; +import { Button } from "~/components/plate-ui/button"; import { - ArrowUpRight, - Check, - ChevronsUpDown, - Eye, - EyeOff, - Settings, -} from 'lucide-react'; - -import { Button } from '~/components/plate-ui/button'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from '~/components/plate-ui/command'; + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "~/components/plate-ui/command"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '~/components/plate-ui/dialog'; -import { Input } from '~/components/plate-ui/input'; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '~/components/plate-ui/popover'; - -export const useChat = () => { - return useBaseChat({ - id: 'editor', - api: '/api/ai/command', - body: { - apiKey: useOpenAI().apiKey, - model: useOpenAI().model.value, - }, - fetch: async (input, init) => { - const res = await fetch(input, init); - - if (!res.ok) { - // Mock the API response. Remove it when you implement the route /api/ai/command - await new Promise((resolve) => setTimeout(resolve, 400)); - - const stream = fakeStreamText(); - - return new Response(stream, { - headers: { - Connection: 'keep-alive', - 'Content-Type': 'text/plain', - }, - }); - } + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "~/components/plate-ui/dialog"; +import { Input } from "~/components/plate-ui/input"; +import { Popover, PopoverContent, PopoverTrigger } from "~/components/plate-ui/popover"; - return res; - }, - }); +export const useChat = (props?: Parameters[0]) => { + return useBaseChat({ + id: "editor", + api: "/api/ai/command", + ...props, + }); }; // Used for testing. Remove it after implementing useChat api. const fakeStreamText = ({ - chunkCount = 10, - streamProtocol = 'data', + chunkCount = 10, + streamProtocol = "data", }: { - chunkCount?: number; - streamProtocol?: 'data' | 'text'; + chunkCount?: number; + streamProtocol?: "data" | "text"; } = {}) => { - const chunks = Array.from({ length: chunkCount }, () => ({ - delay: faker.number.int({ max: 150, min: 50 }), - texts: faker.lorem.words({ max: 3, min: 1 }) + ' ', - })); - const encoder = new TextEncoder(); + const chunks = Array.from({ length: chunkCount }, () => ({ + delay: faker.number.int({ max: 150, min: 50 }), + texts: faker.lorem.words({ max: 3, min: 1 }) + " ", + })); + const encoder = new TextEncoder(); - return new ReadableStream({ - async start(controller) { - for (const chunk of chunks) { - await new Promise((resolve) => setTimeout(resolve, chunk.delay)); + return new ReadableStream({ + async start(controller) { + for (const chunk of chunks) { + await new Promise((resolve) => setTimeout(resolve, chunk.delay)); - if (streamProtocol === 'text') { - controller.enqueue(encoder.encode(chunk.texts)); - } else { - controller.enqueue( - encoder.encode(`0:${JSON.stringify(chunk.texts)}\n`) - ); - } - } + if (streamProtocol === "text") { + controller.enqueue(encoder.encode(chunk.texts)); + } else { + controller.enqueue(encoder.encode(`0:${JSON.stringify(chunk.texts)}\n`)); + } + } - if (streamProtocol === 'data') { - controller.enqueue( - `d:{"finishReason":"stop","usage":{"promptTokens":0,"completionTokens":${chunks.length}}}\n` - ); - } + if (streamProtocol === "data") { + controller.enqueue( + `d:{"finishReason":"stop","usage":{"promptTokens":0,"completionTokens":${chunks.length}}}\n`, + ); + } - controller.close(); - }, - }); + controller.close(); + }, + }); }; interface Model { - label: string; - value: string; + label: string; + value: string; } interface OpenAIContextType { - apiKey: string; - model: Model; - setApiKey: (key: string) => void; - setModel: (model: Model) => void; + apiKey: string; + model: Model; + setApiKey: (key: string) => void; + setModel: (model: Model) => void; } export const models: Model[] = [ - { label: 'gpt-4o-mini', value: 'gpt-4o-mini' }, - { label: 'gpt-4o', value: 'gpt-4o' }, - { label: 'gpt-4-turbo', value: 'gpt-4-turbo' }, - { label: 'gpt-4', value: 'gpt-4' }, - { label: 'gpt-3.5-turbo', value: 'gpt-3.5-turbo' }, - { label: 'gpt-3.5-turbo-instruct', value: 'gpt-3.5-turbo-instruct' }, + { label: "gpt-4o-mini", value: "gpt-4o-mini" }, + { label: "gpt-4o", value: "gpt-4o" }, + { label: "gpt-4-turbo", value: "gpt-4-turbo" }, + { label: "gpt-4", value: "gpt-4" }, + { label: "gpt-3.5-turbo", value: "gpt-3.5-turbo" }, + { label: "gpt-3.5-turbo-instruct", value: "gpt-3.5-turbo-instruct" }, ]; const OpenAIContext = createContext(undefined); export function OpenAIProvider({ children }: { children: ReactNode }) { - const [apiKey, setApiKey] = useState(''); - const [model, setModel] = useState(models[0]); + const [apiKey, setApiKey] = useState(""); + const [model, setModel] = useState(models[0]); - return ( - - {children} - - ); + return ( + + {children} + + ); } export function useOpenAI() { - const context = useContext(OpenAIContext); + const context = useContext(OpenAIContext); - return ( - context ?? - ({ - apiKey: '', - model: models[0], - setApiKey: () => {}, - setModel: () => {}, - } as OpenAIContextType) - ); + return ( + context ?? + ({ + apiKey: "", + model: models[0], + setApiKey: () => {}, + setModel: () => {}, + } as OpenAIContextType) + ); } export function SettingsDialog() { - const { apiKey, model, setApiKey, setModel } = useOpenAI(); - const [tempKey, setTempKey] = useState(apiKey); - const [showKey, setShowKey] = useState(false); - const [open, setOpen] = useState(false); - const [openModel, setOpenModel] = useState(false); + const { apiKey, model, setApiKey, setModel } = useOpenAI(); + const [tempKey, setTempKey] = useState(apiKey); + const [showKey, setShowKey] = useState(false); + const [open, setOpen] = useState(false); + const [openModel, setOpenModel] = useState(false); - const { getOptions, setOption } = useEditorPlugin(CopilotPlugin); + const { getOptions, setOption } = useEditorPlugin(CopilotPlugin); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - setApiKey(tempKey); - setOpen(false); + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setApiKey(tempKey); + setOpen(false); - const completeOptions = getOptions().completeOptions ?? {}; + const completeOptions = getOptions().completeOptions ?? {}; - setOption('completeOptions', { - ...completeOptions, - body: { - ...completeOptions.body, - apiKey: tempKey, - model: model.value, - }, - }); - }; + setOption("completeOptions", { + ...completeOptions, + body: { + ...completeOptions.body, + apiKey: tempKey, + model: model.value, + }, + }); + }; - return ( - - - - - - - AI Settings - - Enter your{' '} - - OpenAI API key - - {' '} - to use AI features. - - -
    -
    - setTempKey(e.target.value)} - placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - data-1p-ignore - type={showKey ? 'text' : 'password'} - /> - -
    + return ( + + + + + + + AI Settings + + Enter your{" "} + + OpenAI API key + + {" "} + to use AI features. + + + +
    + setTempKey(e.target.value)} + placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + data-1p-ignore + type={showKey ? "text" : "password"} + /> + +
    - - - - - - - - No model found. + + + + + + + + No model found. - - - {models.map((m) => ( - { - setModel(m); - setOpenModel(false); - }} - > - - {m.label} - - ))} - - - - - + + + {models.map((m) => ( + { + setModel(m); + setOpenModel(false); + }} + > + + {m.label} + + ))} + + + + + - - -

    - Not stored anywhere. Used only for current session requests. -

    -
    -
    - ); + + +

    + Not stored anywhere. Used only for current session requests. +

    +
    +
    + ); } diff --git a/apps/web/app/components/editor/use-create-editor.tsx b/apps/web/app/components/editor/use-create-editor.tsx index 598af916..bae45a74 100644 --- a/apps/web/app/components/editor/use-create-editor.tsx +++ b/apps/web/app/components/editor/use-create-editor.tsx @@ -1,156 +1,137 @@ -import { withProps } from '@udecode/cn'; -import { AIPlugin } from '@udecode/plate-ai/react'; +import { withProps } from "@udecode/cn"; +import { AIPlugin } from "@udecode/plate-ai/react"; import { - BoldPlugin, - CodePlugin, - ItalicPlugin, - StrikethroughPlugin, - SubscriptPlugin, - SuperscriptPlugin, - UnderlinePlugin, -} from '@udecode/plate-basic-marks/react'; -import { BlockquotePlugin } from '@udecode/plate-block-quote/react'; + BoldPlugin, + CodePlugin, + ItalicPlugin, + StrikethroughPlugin, + SubscriptPlugin, + SuperscriptPlugin, + UnderlinePlugin, +} from "@udecode/plate-basic-marks/react"; +import { BlockquotePlugin } from "@udecode/plate-block-quote/react"; +import { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from "@udecode/plate-code-block/react"; +import { CommentsPlugin } from "@udecode/plate-comments/react"; +import { Value } from "@udecode/plate-common"; +import { ParagraphPlugin, PlateLeaf, usePlateEditor } from "@udecode/plate-common/react"; +import { DatePlugin } from "@udecode/plate-date/react"; +import { ExcalidrawPlugin } from "@udecode/plate-excalidraw/react"; +import { HEADING_KEYS } from "@udecode/plate-heading"; +import { TocPlugin } from "@udecode/plate-heading/react"; +import { HighlightPlugin } from "@udecode/plate-highlight/react"; +import { HorizontalRulePlugin } from "@udecode/plate-horizontal-rule/react"; +import { KbdPlugin } from "@udecode/plate-kbd/react"; +import { ColumnItemPlugin, ColumnPlugin } from "@udecode/plate-layout/react"; +import { LinkPlugin } from "@udecode/plate-link/react"; +import { MarkdownPlugin } from "@udecode/plate-markdown"; +import { ImagePlugin, MediaEmbedPlugin } from "@udecode/plate-media/react"; +import { MentionInputPlugin, MentionPlugin } from "@udecode/plate-mention/react"; +import { SlashInputPlugin } from "@udecode/plate-slash-command/react"; import { - CodeBlockPlugin, - CodeLinePlugin, - CodeSyntaxPlugin, -} from '@udecode/plate-code-block/react'; -import { CommentsPlugin } from '@udecode/plate-comments/react'; -import { - ParagraphPlugin, - PlateLeaf, - usePlateEditor, -} from '@udecode/plate-common/react'; -import { DatePlugin } from '@udecode/plate-date/react'; - -import { ExcalidrawPlugin } from '@udecode/plate-excalidraw/react'; -import { HEADING_KEYS } from '@udecode/plate-heading'; -import { TocPlugin } from '@udecode/plate-heading/react'; -import { HighlightPlugin } from '@udecode/plate-highlight/react'; -import { HorizontalRulePlugin } from '@udecode/plate-horizontal-rule/react'; -import { KbdPlugin } from '@udecode/plate-kbd/react'; -import { ColumnItemPlugin, ColumnPlugin } from '@udecode/plate-layout/react'; -import { LinkPlugin } from '@udecode/plate-link/react'; -import { ImagePlugin, MediaEmbedPlugin } from '@udecode/plate-media/react'; -import { - MentionInputPlugin, - MentionPlugin, -} from '@udecode/plate-mention/react'; -import { SlashInputPlugin } from '@udecode/plate-slash-command/react'; -import { - TableCellHeaderPlugin, - TableCellPlugin, - TablePlugin, - TableRowPlugin, -} from '@udecode/plate-table/react'; -import { TogglePlugin } from '@udecode/plate-toggle/react'; - -import { copilotPlugins } from '~/components/editor/plugins/copilot-plugins'; -import { editorPlugins } from '~/components/editor/plugins/editor-plugins'; -import { FixedToolbarPlugin } from '~/components/editor/plugins/fixed-toolbar-plugin'; -import { FloatingToolbarPlugin } from '~/components/editor/plugins/floating-toolbar-plugin'; -import { AILeaf } from '~/components/plate-ui/ai-leaf'; -import { BlockquoteElement } from '~/components/plate-ui/blockquote-element'; -import { CodeBlockElement } from '~/components/plate-ui/code-block-element'; -import { CodeLeaf } from '~/components/plate-ui/code-leaf'; -import { CodeLineElement } from '~/components/plate-ui/code-line-element'; -import { CodeSyntaxLeaf } from '~/components/plate-ui/code-syntax-leaf'; -import { ColumnElement } from '~/components/plate-ui/column-element'; -import { ColumnGroupElement } from '~/components/plate-ui/column-group-element'; -import { CommentLeaf } from '~/components/plate-ui/comment-leaf'; -import { DateElement } from '~/components/plate-ui/date-element'; -import { ExcalidrawElement } from '~/components/plate-ui/excalidraw-element'; -import { HeadingElement } from '~/components/plate-ui/heading-element'; -import { HighlightLeaf } from '~/components/plate-ui/highlight-leaf'; -import { HrElement } from '~/components/plate-ui/hr-element'; -import { ImageElement } from '~/components/plate-ui/image-element'; -import { KbdLeaf } from '~/components/plate-ui/kbd-leaf'; -import { LinkElement } from '~/components/plate-ui/link-element'; -import { MediaEmbedElement } from '~/components/plate-ui/media-embed-element'; -import { MentionElement } from '~/components/plate-ui/mention-element'; -import { MentionInputElement } from '~/components/plate-ui/mention-input-element'; -import { ParagraphElement } from '~/components/plate-ui/paragraph-element'; -import { withPlaceholders } from '~/components/plate-ui/placeholder'; -import { SlashInputElement } from '~/components/plate-ui/slash-input-element'; -import { - TableCellElement, - TableCellHeaderElement, -} from '~/components/plate-ui/table-cell-element'; -import { TableElement } from '~/components/plate-ui/table-element'; -import { TableRowElement } from '~/components/plate-ui/table-row-element'; -import { TocElement } from '~/components/plate-ui/toc-element'; -import { ToggleElement } from '~/components/plate-ui/toggle-element'; -import { withDraggables } from '~/components/plate-ui/with-draggables'; + TableCellHeaderPlugin, + TableCellPlugin, + TablePlugin, + TableRowPlugin, +} from "@udecode/plate-table/react"; +import { TogglePlugin } from "@udecode/plate-toggle/react"; +import { copilotPlugins } from "~/components/editor/plugins/copilot-plugins"; +import { editorPlugins } from "~/components/editor/plugins/editor-plugins"; +import { FixedToolbarPlugin } from "~/components/editor/plugins/fixed-toolbar-plugin"; +import { FloatingToolbarPlugin } from "~/components/editor/plugins/floating-toolbar-plugin"; +import { AILeaf } from "~/components/plate-ui/ai-leaf"; +import { BlockquoteElement } from "~/components/plate-ui/blockquote-element"; +import { CodeBlockElement } from "~/components/plate-ui/code-block-element"; +import { CodeLeaf } from "~/components/plate-ui/code-leaf"; +import { CodeLineElement } from "~/components/plate-ui/code-line-element"; +import { CodeSyntaxLeaf } from "~/components/plate-ui/code-syntax-leaf"; +import { ColumnElement } from "~/components/plate-ui/column-element"; +import { ColumnGroupElement } from "~/components/plate-ui/column-group-element"; +import { CommentLeaf } from "~/components/plate-ui/comment-leaf"; +import { DateElement } from "~/components/plate-ui/date-element"; +import { ExcalidrawElement } from "~/components/plate-ui/excalidraw-element"; +import { HeadingElement } from "~/components/plate-ui/heading-element"; +import { HighlightLeaf } from "~/components/plate-ui/highlight-leaf"; +import { HrElement } from "~/components/plate-ui/hr-element"; +import { ImageElement } from "~/components/plate-ui/image-element"; +import { KbdLeaf } from "~/components/plate-ui/kbd-leaf"; +import { LinkElement } from "~/components/plate-ui/link-element"; +import { MediaEmbedElement } from "~/components/plate-ui/media-embed-element"; +import { MentionElement } from "~/components/plate-ui/mention-element"; +import { MentionInputElement } from "~/components/plate-ui/mention-input-element"; +import { ParagraphElement } from "~/components/plate-ui/paragraph-element"; +import { withPlaceholders } from "~/components/plate-ui/placeholder"; +import { SlashInputElement } from "~/components/plate-ui/slash-input-element"; +import { TableCellElement, TableCellHeaderElement } from "~/components/plate-ui/table-cell-element"; +import { TableElement } from "~/components/plate-ui/table-element"; +import { TableRowElement } from "~/components/plate-ui/table-row-element"; +import { TocElement } from "~/components/plate-ui/toc-element"; +import { ToggleElement } from "~/components/plate-ui/toggle-element"; +import { withDraggables } from "~/components/plate-ui/with-draggables"; -export const useCreateEditor = () => { - return usePlateEditor({ - override: { - components: withDraggables( - withPlaceholders({ - [AIPlugin.key]: AILeaf, - [BlockquotePlugin.key]: BlockquoteElement, - [BoldPlugin.key]: withProps(PlateLeaf, { as: 'strong' }), - [CodeBlockPlugin.key]: CodeBlockElement, - [CodeLinePlugin.key]: CodeLineElement, - [CodePlugin.key]: CodeLeaf, - [CodeSyntaxPlugin.key]: CodeSyntaxLeaf, - [ColumnItemPlugin.key]: ColumnElement, - [ColumnPlugin.key]: ColumnGroupElement, - [CommentsPlugin.key]: CommentLeaf, - [DatePlugin.key]: DateElement, - // [EmojiInputPlugin.key]: EmojiInputElement, - [ExcalidrawPlugin.key]: ExcalidrawElement, - [HEADING_KEYS.h1]: withProps(HeadingElement, { variant: 'h1' }), - [HEADING_KEYS.h2]: withProps(HeadingElement, { variant: 'h2' }), - [HEADING_KEYS.h3]: withProps(HeadingElement, { variant: 'h3' }), - [HEADING_KEYS.h4]: withProps(HeadingElement, { variant: 'h4' }), - [HEADING_KEYS.h5]: withProps(HeadingElement, { variant: 'h5' }), - [HEADING_KEYS.h6]: withProps(HeadingElement, { variant: 'h6' }), - [HighlightPlugin.key]: HighlightLeaf, - [HorizontalRulePlugin.key]: HrElement, - [ImagePlugin.key]: ImageElement, - [ItalicPlugin.key]: withProps(PlateLeaf, { as: 'em' }), - [KbdPlugin.key]: KbdLeaf, - [LinkPlugin.key]: LinkElement, - [MediaEmbedPlugin.key]: MediaEmbedElement, - [MentionInputPlugin.key]: MentionInputElement, - [MentionPlugin.key]: MentionElement, - [ParagraphPlugin.key]: ParagraphElement, - [SlashInputPlugin.key]: SlashInputElement, - [StrikethroughPlugin.key]: withProps(PlateLeaf, { as: 's' }), - [SubscriptPlugin.key]: withProps(PlateLeaf, { as: 'sub' }), - [SuperscriptPlugin.key]: withProps(PlateLeaf, { as: 'sup' }), - [TableCellHeaderPlugin.key]: TableCellHeaderElement, - [TableCellPlugin.key]: TableCellElement, - [TablePlugin.key]: TableElement, - [TableRowPlugin.key]: TableRowElement, - [TocPlugin.key]: TocElement, - [TogglePlugin.key]: ToggleElement, - [UnderlinePlugin.key]: withProps(PlateLeaf, { as: 'u' }), - }) - ), - }, - plugins: [ - ...copilotPlugins, - ...editorPlugins, - FixedToolbarPlugin, - FloatingToolbarPlugin, - ], - value: [ - { - children: [{ text: 'Playground' }], - type: 'h1', - }, - { - children: [ - { text: 'A rich-text editor with AI capabilities. Try the ' }, - { bold: true, text: 'AI commands' }, - { text: ' or use ' }, - { kbd: true, text: 'Cmd+J' }, - { text: ' to open the AI menu.' }, - ], - type: ParagraphPlugin.key, - }, - ], - }); +export const useCreateEditor = ({ initialValue }: { initialValue?: Value }) => { + return usePlateEditor({ + override: { + components: withDraggables( + withPlaceholders({ + [AIPlugin.key]: AILeaf, + [BlockquotePlugin.key]: BlockquoteElement, + [BoldPlugin.key]: withProps(PlateLeaf, { as: "strong" }), + [CodeBlockPlugin.key]: CodeBlockElement, + [CodeLinePlugin.key]: CodeLineElement, + [CodePlugin.key]: CodeLeaf, + [CodeSyntaxPlugin.key]: CodeSyntaxLeaf, + [ColumnItemPlugin.key]: ColumnElement, + [ColumnPlugin.key]: ColumnGroupElement, + [CommentsPlugin.key]: CommentLeaf, + [DatePlugin.key]: DateElement, + // [EmojiInputPlugin.key]: EmojiInputElement, + [ExcalidrawPlugin.key]: ExcalidrawElement, + [HEADING_KEYS.h1]: withProps(HeadingElement, { variant: "h1" }), + [HEADING_KEYS.h2]: withProps(HeadingElement, { variant: "h2" }), + [HEADING_KEYS.h3]: withProps(HeadingElement, { variant: "h3" }), + [HEADING_KEYS.h4]: withProps(HeadingElement, { variant: "h4" }), + [HEADING_KEYS.h5]: withProps(HeadingElement, { variant: "h5" }), + [HEADING_KEYS.h6]: withProps(HeadingElement, { variant: "h6" }), + [HighlightPlugin.key]: HighlightLeaf, + [HorizontalRulePlugin.key]: HrElement, + [ImagePlugin.key]: ImageElement, + [ItalicPlugin.key]: withProps(PlateLeaf, { as: "em" }), + [KbdPlugin.key]: KbdLeaf, + [LinkPlugin.key]: LinkElement, + [MediaEmbedPlugin.key]: MediaEmbedElement, + [MentionInputPlugin.key]: MentionInputElement, + [MentionPlugin.key]: MentionElement, + [ParagraphPlugin.key]: ParagraphElement, + [SlashInputPlugin.key]: SlashInputElement, + [StrikethroughPlugin.key]: withProps(PlateLeaf, { as: "s" }), + [SubscriptPlugin.key]: withProps(PlateLeaf, { as: "sub" }), + [SuperscriptPlugin.key]: withProps(PlateLeaf, { as: "sup" }), + [TableCellHeaderPlugin.key]: TableCellHeaderElement, + [TableCellPlugin.key]: TableCellElement, + [TablePlugin.key]: TableElement, + [TableRowPlugin.key]: TableRowElement, + [TocPlugin.key]: TocElement, + [TogglePlugin.key]: ToggleElement, + [UnderlinePlugin.key]: withProps(PlateLeaf, { as: "u" }), + }), + ), + }, + plugins: [ + ...copilotPlugins, + ...editorPlugins, + FixedToolbarPlugin, + FloatingToolbarPlugin, + MarkdownPlugin, + ], + value: initialValue ?? [ + { + children: [{ text: "Supermemory docs" }], + type: "h1", + }, + { + children: [{ text: "Auto-updating AI-powered docs" }], + type: ParagraphPlugin.key, + }, + ], + }); }; diff --git a/apps/web/app/components/editor/writing-playground.tsx b/apps/web/app/components/editor/writing-playground.tsx new file mode 100644 index 00000000..b303ffc8 --- /dev/null +++ b/apps/web/app/components/editor/writing-playground.tsx @@ -0,0 +1,242 @@ +import { lazy, memo, useEffect, useRef, useState } from "react"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; + +import { useChat } from "@ai-sdk/react"; +import { Message } from "ai"; +import { OpenAIProvider } from "~/components/editor/use-chat"; +import { useCreateEditor } from "~/components/editor/use-create-editor"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; +import { useLiveTranscript } from "~/lib/hooks/use-live-transcript"; +import { Theme, useTheme } from "~/lib/theme-provider"; + +const PlateEditorImport = lazy(() => + import("@udecode/plate-common/react").then((mod) => ({ default: mod.Plate })), +); + +const EditorContainerImport = lazy(() => + import("~/components/plate-ui/editor").then((mod) => ({ default: mod.EditorContainer })), +); + +const EditorImport = lazy(() => + import("~/components/plate-ui/editor").then((mod) => ({ default: mod.Editor })), +); + +const Plate = memo(PlateEditorImport); +const EditorContainer = memo(EditorContainerImport); +const Editor = memo(EditorImport); + +export function WritingPlayground() { + if (typeof window === "undefined") { + return
    Loading...
    ; + } + const localValue = localStorage.getItem("editorContent"); + const editor = useCreateEditor({ initialValue: localValue ? JSON.parse(localValue) : undefined }); + + const [theme, setTheme] = useTheme(); + + useEffect(() => { + setTheme(Theme.LIGHT); + }, [theme, setTheme]); + + const { toggleMicrophone, caption, status, isListening, isLoading } = useLiveTranscript(); + + const { messages, input, handleInputChange, handleSubmit } = useChat({ + id: "editor", + api: "/api/ai/command", + initialMessages: [ + { + id: "1", + content: "Hi! I am here to help you quickly find what you're looking for.", + role: "assistant", + }, + { + id: "2", + content: "Just drop a question when you need me, ok?", + role: "assistant", + }, + ], + keepLastMessageOnError: true, + // @ts-expect-error + experimental_prepareRequestBody: (request) => { + // messages with the documentation content + // @ts-expect-error + const markdown = editor.api.markdown.serialize(); + console.log(JSON.stringify(editor.children)); + console.log(markdown); + return { + messages: [ + ...request.messages, + { + id: "3", + content: `Here is the documentation for the company: ${markdown}`, + role: "user", + } satisfies Message, + ], + }; + }, + }); + + const [lastProcessedLength, setLastProcessedLength] = useState(0); + const [isProcessing, setIsProcessing] = useState(false); + const updateTimeoutRef = useRef(); + + useEffect(() => { + if (!isListening) { + return; + } + // Clear existing timeout + if (updateTimeoutRef.current) { + clearTimeout(updateTimeoutRef.current); + } + + const currentSentences = caption.split(".").length; + + // Only process if we have new sentences and aren't currently processing + if (currentSentences > lastProcessedLength && !isProcessing) { + // Debounce the update for 2 seconds + updateTimeoutRef.current = setTimeout(async () => { + setIsProcessing(true); + try { + const result = await fetch("/api/ai/update", { + method: "POST", + body: JSON.stringify({ caption, document: editor.children }), + }); + const data = (await result.json()) as { + action: "edit" | "delete" | "append" | "ignore"; + blockId?: string; + content?: string; + reason: string; + }; + if (data.action === "ignore") { + return; + } + if (data.action === "edit") { + // Make a copy of the editor children + const newChildren = [...editor.children]; + // Find and update the block in the copy + const blockIndex = newChildren.findIndex((block) => block.id === data.blockId); + if (blockIndex !== -1) { + newChildren[blockIndex] = { + ...newChildren[blockIndex], + children: [{ text: data.content ?? "" }], + }; + } + editor.tf.setValue(newChildren); + } else if (data.action === "delete") { + // Make a copy of the editor children and filter out the block + const newChildren = editor.children.filter((block) => block.id !== data.blockId); + editor.tf.setValue(newChildren); + } else if (data.action === "append") { + editor.tf.setValue([ + ...editor.children, + { + type: "paragraph", + children: [{ text: data.content ?? "" }], + }, + ]); + } + setLastProcessedLength(currentSentences); + } catch (error) { + console.error("Error updating editor:", error); + } finally { + setIsProcessing(false); + } + }, 2000); + } + + return () => { + if (updateTimeoutRef.current) { + clearTimeout(updateTimeoutRef.current); + } + }; + }, [caption, editor, lastProcessedLength, isProcessing]); + + return ( +
    + + + Docs + Transcript + +
    + + {status} +
    +
    +
    + + + + { + // For performance, debounce your saving logic + localStorage.setItem("editorContent", JSON.stringify(value)); + }} + editor={editor} + > + + + + + + + + +
    {caption}
    +
    +
    +
    +
    +
    + {messages.map((message) => ( +
    +
    + {message.content} +
    +
    + ))} +
    +
    +
    + + +
    +
    +
    +
    + ); +} \ No newline at end of file diff --git a/apps/web/app/components/icons/IntegrationIcons.tsx b/apps/web/app/components/icons/IntegrationIcons.tsx index d818678f..1a8998b5 100644 --- a/apps/web/app/components/icons/IntegrationIcons.tsx +++ b/apps/web/app/components/icons/IntegrationIcons.tsx @@ -22,3 +22,49 @@ export const GoogleCalendarIcon = (props: SVGProps) => ( ); + +export const TwitterIcon = (props: SVGProps) => ( + + + +); + +export const GithubIcon = (props: SVGProps) => ( + + + +); + +export const DiscordIcon = (props: SVGProps) => ( + + + +); diff --git a/apps/web/app/components/memories/SharedCard.tsx b/apps/web/app/components/memories/SharedCard.tsx index 7bd2cc6a..8bf00361 100644 --- a/apps/web/app/components/memories/SharedCard.tsx +++ b/apps/web/app/components/memories/SharedCard.tsx @@ -226,7 +226,7 @@ const renderContent = {
    - {data.isSuccessfullyProcessed ? ( + {data.permissions?.isPublic ? (
    { - alert(`Error in chat: ${e}`); + console.error(e); }, body: { threadId: threadUuid, diff --git a/apps/web/app/lib/hooks/use-live-transcript.tsx b/apps/web/app/lib/hooks/use-live-transcript.tsx new file mode 100644 index 00000000..05c3436a --- /dev/null +++ b/apps/web/app/lib/hooks/use-live-transcript.tsx @@ -0,0 +1,132 @@ + +import { useState, useEffect, useCallback } from "react"; +import { useQueue } from "@uidotdev/usehooks"; +import { LiveClient, LiveTranscriptionEvents, createClient } from "@deepgram/sdk"; + +export function useLiveTranscript() { + + const { add, remove, first, size, queue } = useQueue([]); + const [apiKey, _] = useState(""); + const [connection, setConnection] = useState(null); + const [isListening, setIsListening] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [isProcessing, setIsProcessing] = useState(false); + const [micOpen, setMicOpen] = useState(false); + const [microphone, setMicrophone] = useState(null); + const [userMedia, setUserMedia] = useState(null); + const [caption, setCaption] = useState(""); + const [status, setStatus] = useState("Not Connected"); + // Initialize Deepgram connection + const initializeConnection = useCallback(() => { + if (!apiKey) return null; + + const deepgram = createClient(apiKey); + const connection = deepgram.listen.live({ + model: "nova-3", + language: "en", + smart_format: true, + interim_results: true, + punctuate: true, + diarize: true, + utterances: true, + }); + + connection.on(LiveTranscriptionEvents.Open, () => { + setStatus("Connected"); + setIsListening(true); + }); + + connection.on(LiveTranscriptionEvents.Close, () => { + setStatus("Not Connected"); + setIsListening(false); + }); + + connection.on(LiveTranscriptionEvents.Error, (error) => { + console.error("Deepgram error:", error); + setStatus("Error occurred"); + }); + + connection.on(LiveTranscriptionEvents.Transcript, (data) => { + const transcript = data.channel.alternatives[0].transcript; + if (data.is_final) { + if (transcript && transcript.trim() !== "") { + setCaption((prev) => prev + " " + transcript); + } + } + }); + + return connection; + }, [apiKey]); + + const toggleMicrophone = useCallback(async () => { + if (microphone && userMedia) { + setUserMedia(null); + setMicrophone(null); + microphone.stop(); + if (connection) { + connection.finish(); + setConnection(null); + } + } else { + const userMedia = await navigator.mediaDevices.getUserMedia({ + audio: true, + }); + + const microphone = new MediaRecorder(userMedia); + microphone.start(250); + + microphone.onstart = () => { + setMicOpen(true); + // Create new connection when starting microphone + const newConnection = initializeConnection(); + if (newConnection) { + setConnection(newConnection); + } + }; + + microphone.onstop = () => { + setMicOpen(false); + }; + + microphone.ondataavailable = (e) => { + add(e.data); + }; + + setUserMedia(userMedia); + setMicrophone(microphone); + } + }, [add, microphone, userMedia, connection, initializeConnection]); + + useEffect(() => { + setIsLoading(false); + }, []); + + useEffect(() => { + const processQueue = async () => { + if (size > 0 && !isProcessing && isListening && connection) { + setIsProcessing(true); + try { + const blob = first; + if (blob) { + connection.send(blob); + } + remove(); + } catch (error) { + console.error("Error processing audio:", error); + } + setIsProcessing(false); + } + }; + + const interval = setInterval(processQueue, 100); + return () => clearInterval(interval); + }, [connection, queue, remove, first, size, isProcessing, isListening]); + + return { + toggleMicrophone, + caption, + status, + isListening, + isLoading, + }; +} \ No newline at end of file diff --git a/apps/web/app/lib/hooks/use-spaces.tsx b/apps/web/app/lib/hooks/use-spaces.tsx index 827c7c79..14ffb56c 100644 --- a/apps/web/app/lib/hooks/use-spaces.tsx +++ b/apps/web/app/lib/hooks/use-spaces.tsx @@ -7,6 +7,7 @@ export type ExtraSpaceMetaData = { canRead: boolean; canEdit: boolean; isOwner: boolean; + isPublic: boolean; }; owner: { id: string; diff --git a/apps/web/app/routes/_index.tsx b/apps/web/app/routes/_index.tsx index 8512cd69..b8c69a39 100644 --- a/apps/web/app/routes/_index.tsx +++ b/apps/web/app/routes/_index.tsx @@ -14,6 +14,7 @@ import Navbar from "~/components/Navbar"; import Suggestions from "~/components/Suggestions"; import { IntegrationModals } from "~/components/memories/Integrations"; import SuggestionsSkeleton from "~/components/skeletons/SuggestionsSkeleton"; +import { Theme, useTheme } from "~/lib/theme-provider"; const MemoriesPage = lazy(() => import("~/components/memories/MemoriesPage")); const Reminders = lazy(() => import("~/components/Reminders")); @@ -40,7 +41,14 @@ export const loader = async ({ request, context }: LoaderFunctionArgs) => { const integration = searchParams.get("integration"); if (!user) { - return redirect("/signin"); + return { + user: null, + signinUrl, + greeting, + recommendedQuestions: null, + success, + integration, + }; } try { @@ -80,15 +88,21 @@ const HomePage = memo(function HomePage() { const [input, setInput] = useState(""); const fetcher = useFetcher(); const [scrollOpacity, setScrollOpacity] = useState(1); + const [theme, setTheme] = useTheme(); useEffect(() => { + if (!user) { + setTheme(Theme.LIGHT); + return; + } + const handleScroll = () => { const scrollPosition = window.scrollY; const opacity = Math.max(0, 1 - scrollPosition / 200); // Adjust 200 to control fade speed setScrollOpacity(opacity); }; - if (!recommendedQuestions || recommendedQuestions === null) { + if ((!recommendedQuestions || recommendedQuestions === null) && !user) { toast.error("Something went wrong. Please try again later."); alert("Something went wrong. Please try again later."); } @@ -126,7 +140,7 @@ const HomePage = memo(function HomePage() { }, []); if (!user) { - return
    Loading...
    ; + return ; } return ( diff --git a/apps/web/app/routes/editor.tsx b/apps/web/app/routes/editor.tsx index 02cd277e..264db784 100644 --- a/apps/web/app/routes/editor.tsx +++ b/apps/web/app/routes/editor.tsx @@ -1,19 +1,13 @@ import { lazy, memo } from "react"; -import { OpenAIProvider } from "~/components/editor/use-chat"; - -const PlateEditorImport = lazy(() => - import("~/components/editor/plate-editor").then((mod) => ({ default: mod.PlateEditor })), +const WritingPlaygroundImport = lazy(() => + import("~/components/editor/writing-playground").then((mod) => ({ + default: mod.WritingPlayground, + })), ); -const PlateEditor = memo(PlateEditorImport); +const WritingPlayground = memo(WritingPlaygroundImport); export default function Page() { - return ( -
    - - - -
    - ); + return ; } diff --git a/apps/web/app/routes/onboarding.privacy.tsx b/apps/web/app/routes/onboarding.privacy.tsx index 49055095..5a03c998 100644 --- a/apps/web/app/routes/onboarding.privacy.tsx +++ b/apps/web/app/routes/onboarding.privacy.tsx @@ -28,7 +28,7 @@ export default function Onboarding() { className="flex flex-col min-h-screen items-center pt-20 relative overflow-hidden bg-gradient-to-b from-gray-900 to-gray-800 bg-opacity-40" > {/* Neural network background pattern */} -
    +
    {/* Subtle gradient orbs */} {[...Array(4)].map((_, i) => ( supermemory @@ -119,29 +119,29 @@ export default function Onboarding() { initial={{ y: 20, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.8, delay: 0.2 }} - className="flex flex-col gap-2 items-center mt-8 text-white" + className="flex flex-col gap-2 items-center mt-8 text-white relative z-10" >

    Your privacy is our priority

    -

    +

    We use Cloudflare's "bindings" architecture, meaning encryption keys are never exposed - not even to our developers. Your data remains encrypted and inaccessible without your authorization.

    -

    +

    Our entire codebase is open source and available for security review at{" "} - + git.new/memory . We believe transparency builds trust.

    -

    +

    Your data is encrypted at rest and in transit, and we use industry-standard encryption. You maintain full control over your data, including the right to export or delete it at any time.

    -

    - +

    + Read our detailed privacy policy →

    @@ -153,7 +153,7 @@ export default function Onboarding() { initial={{ y: 20, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.8, delay: 0.4 }} - className="mt-16 relative border-none bg-transparent p-0 cursor-pointer outline-offset-4 transition-[filter] duration-250 select-none" + className="mt-16 relative border-none bg-transparent p-0 cursor-pointer outline-offset-4 transition-[filter] duration-250 select-none z-10" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} href="/onboarding/add" diff --git a/apps/web/app/routes/privacy.tsx b/apps/web/app/routes/privacy.tsx index d480834e..84676609 100644 --- a/apps/web/app/routes/privacy.tsx +++ b/apps/web/app/routes/privacy.tsx @@ -3,7 +3,7 @@ import Markdown from "react-markdown"; function Page() { return (
    -
    +
    {` Privacy Policy for Supermemory.ai @@ -34,7 +34,7 @@ The extension has the capability to see all websites that users visit. However, - **Current page data**: Upon activation (click) by the user, Supermemory.ai stores data from the current HTML page. This data is used to provide relevant contextual information based on the content of the page you are viewing. ## Data Storage and Security -All collected data is securely stored in a SQLite database hosted on [Cloudflare D1](https://developers.cloudflare.com/d1/), [Cloudflare vectorize](https://developers.cloudflare.com/vectorize/), and [Cloudflare KV](https://developers.cloudflare.com/kv). +All collected data is securely stored in a Database hosted on [Hetzner](https://www.hetzner.com/). We employ industry-standard security measures to protect your information from unauthorized access, alteration, disclosure, or destruction. Despite our efforts, no method of transmission over the Internet or method of electronic storage is 100% secure. Therefore, while we strive to use commercially acceptable means to protect your personal information, we cannot guarantee its absolute security. When you chat with the app, your queries may be sent to OpenAI GPT-4 API, Google Gemini API or other third-party services to provide you with relevant information. These services may store your queries and responses for training purposes. @@ -58,8 +58,8 @@ Supermemory.ai reserves the right to update this privacy policy at any time. Whe # Contact Us If you have any questions about this Privacy Policy, the practices of this site, or your dealings with this site, please contact us at: -Email: dhravyashah@gmail.com -This document was last updated on July 4, 2024. +Email: privacy@supermemory.ai +This document was last updated on February 14, 2025. `} diff --git a/apps/web/app/routes/space.$spaceId.tsx b/apps/web/app/routes/space.$spaceId.tsx index 65ac6e3f..88f817c9 100644 --- a/apps/web/app/routes/space.$spaceId.tsx +++ b/apps/web/app/routes/space.$spaceId.tsx @@ -4,7 +4,7 @@ import { LoaderFunctionArgs, json } from "@remix-run/cloudflare"; import { useLoaderData, useNavigate } from "@remix-run/react"; import { getSessionFromRequest } from "@supermemory/authkit-remix-cloudflare/src/session"; -import { Clipboard, Share, Star, UserPlus } from "lucide-react"; +import { Clipboard, DeleteIcon, Share, Star, Trash, UserPlus } from "lucide-react"; import { proxy } from "server/proxy"; import { toast } from "sonner"; import Navbar from "~/components/Navbar"; @@ -155,11 +155,9 @@ export default function SpacePage() { } catch (err) { // Fallback to clipboard if share fails or is cancelled await navigator.clipboard.writeText(shareUrl); - toast.success("Link copied to clipboard!"); } } else { await navigator.clipboard.writeText(shareUrl); - toast.success("Link copied to clipboard!"); } }; @@ -169,6 +167,20 @@ export default function SpacePage() { toast.success("Link copied to clipboard!"); }; + const handleDelete = async () => { + const response = await fetch(`/backend/v1/spaces/${space.uuid}`, { + method: "DELETE", + credentials: "include", + }); + + if (!response.ok) { + toast.error("Failed to delete space"); + } else { + navigate("/"); + toast.success("Space deleted successfully"); + } + }; + return (
    @@ -241,6 +253,10 @@ export default function SpacePage() { + + {space.isPublic && user && !space.permissions.isOwner && diff --git a/apps/web/public/product-of-the-day.png b/apps/web/public/product-of-the-day.png new file mode 100644 index 00000000..9381c6a6 Binary files /dev/null and b/apps/web/public/product-of-the-day.png differ diff --git a/apps/web/server/index.ts b/apps/web/server/index.ts index 1343be53..09cd6af0 100644 --- a/apps/web/server/index.ts +++ b/apps/web/server/index.ts @@ -4,7 +4,7 @@ import * as cheerio from "cheerio"; import { createOpenAI } from "@ai-sdk/openai"; import { zValidator } from "@hono/zod-validator"; import { getSessionFromRequest } from "@supermemory/authkit-remix-cloudflare/src/session"; -import { convertToCoreMessages, generateText, streamText } from "ai"; +import { convertToCoreMessages, generateObject, generateText, streamText } from "ai"; import { putEncryptedKV } from "encrypt-workers-kv"; import { Hono } from "hono"; import { z } from "zod"; @@ -97,7 +97,7 @@ app.all("/backend/*", async (c) => { try { // Use tee() to create a copy of the body stream const [stream1, stream2] = c.req.raw.body.tee(); - + const reader = stream2.getReader(); const chunks = []; let done = false; @@ -125,21 +125,27 @@ app.all("/backend/*", async (c) => { body = JSON.stringify(parsedBody); } catch (e) { console.error("Invalid JSON in request body:", bodyText); - return c.json({ - error: "Invalid JSON in request body", - details: bodyText.substring(0, 100) + "..." // Show partial body for debugging - }, 400); + return c.json( + { + error: "Invalid JSON in request body", + details: bodyText.substring(0, 100) + "...", // Show partial body for debugging + }, + 400, + ); } } else { body = bodyText; } } catch (error) { console.error("Error reading request body:", error); - return c.json({ - error: "Failed to process request body", - details: error instanceof Error ? error.message : "Unknown error", - path: path - }, 400); + return c.json( + { + error: "Failed to process request body", + details: error instanceof Error ? error.message : "Unknown error", + path: path, + }, + 400, + ); } } @@ -197,11 +203,14 @@ app.all("/backend/*", async (c) => { } } catch (error) { console.error("Error reading response:", error); - return c.json({ - error: "Failed to read backend response", - details: error instanceof Error ? error.message : "Unknown error", - path: path - }, 502); + return c.json( + { + error: "Failed to read backend response", + details: error instanceof Error ? error.message : "Unknown error", + path: path, + }, + 502, + ); } return new Response(JSON.stringify(responseBody), { @@ -214,19 +223,22 @@ app.all("/backend/*", async (c) => { }); } catch (error) { console.error("Error proxying request:", error); - return c.json({ - error: "Failed to proxy request to backend", - details: error instanceof Error ? error.message : "Unknown error", - path: path, - url: url - }, 502); + return c.json( + { + error: "Failed to proxy request to backend", + details: error instanceof Error ? error.message : "Unknown error", + path: path, + url: url, + }, + 502, + ); } }); app.post("/api/ai/command", async (c) => { - const { apiKey: key, messages, model = "gpt-4o-mini", system } = await c.req.json(); + const { messages, model = "gpt-4o-mini", system } = await c.req.json(); - const apiKey = key || c.env.OPENAI_API_KEY; + const apiKey = c.env.OPENAI_API_KEY; if (!apiKey) { return c.json({ error: "Missing OpenAI API key." }, 401); @@ -280,6 +292,60 @@ app.post("/api/ai/copilot", async (c) => { } }); +app.post("/api/ai/update", async (c) => { + const { caption, document } = await c.req.json(); + + const apiKey = c.env.OPENAI_API_KEY; + + if (!apiKey) { + return c.json({ error: "Missing OpenAI API key." }, 401); + } + + const openai = createOpenAI({ apiKey }); + + const result = await generateObject({ + model: openai("gpt-4o-mini"), + schema: z.object({ + action: z.enum(["edit", "delete", "append", "ignore"]), + blockId: z.string().optional(), + content: z.string().optional(), + }), + prompt: `You are a professional technical document editor. + +You are given a document and a new caption that was transcribed from a recording. + +Your job is to analyze how to update the document to reflect the new caption. + +Given this document structure: +${JSON.stringify(document)} + +And this new caption that was transcribed: +${caption} + +Analyze how to update the document. Choose one: +1. Edit an existing block (provide blockId and new content) +2. Delete a block (provide blockId) +3. Append new content (provide content) +4. Ignore the new content if it doesn't meaningfully improve the document + +You can be strict in ignoring. if the transcript is not related to the content of the document, you should ignore it. + +Sometimes you may need to edit things below the headers, or move things around. Do what you think is best. That's all right. + +For eg, if I am talking about the features, add it to the block ID BELOW the features header. Don't change the heading blogs + +I will first talk about "what supermemory is", you need to add it to the block id that is right below the heading "what is supermemory?". +Then, I will talk about our charter. I will say somethuing like "we want to help companies write and read better documentation". now, you need to add it to the block id that is right below the heading "our charter". +I will talk about features like "you can actually edit the docs along with AI, seamlessly integrate it into your workflow". now, you need to add it to the block id that is right below the heading "features". +I will also talk about connections with existing documentation platforms. now, you need to add it to the block id that is right below the heading "how we will do it". + +there should not be any repetitive stuff. it should be professional. +Make sure that the document you write is a good, accurate, and up-to-date document.`, + }); + + return c.json(result.object); +}); + app.all("/auth/notion/callback", zValidator("query", z.object({ code: z.string() })), async (c) => { const { code } = c.req.valid("query"); diff --git a/package.json b/package.json index ef25819d..4a97e761 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ ], "dependencies": { "@cloudflare/workers-types": "^4.20241112.0", + "@deepgram/sdk": "^3.10.1", + "@heroicons/react": "^2.2.0", "@hono-rate-limiter/cloudflare": "^0.2.2", "@hono/zod-validator": "^0.4.1", "@llm-ui/code": "^0.13.3", @@ -48,6 +50,8 @@ "@types/postlight__mercury-parser": "^2.2.7", "@types/showdown": "^2.0.6", "@types/turndown": "^5.0.5", + "@udecode/plate": "^44.0.1", + "@uidotdev/usehooks": "^2.4.1", "ai": "4.0.18", "autoevals": "^0.0.106", "aws4fetch": "^1.0.20", -- cgit v1.2.3