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 /apps/web/app/api/onboarding | |
| 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
Diffstat (limited to 'apps/web/app/api/onboarding')
| -rw-r--r-- | apps/web/app/api/onboarding/extract-content/route.ts | 61 | ||||
| -rw-r--r-- | apps/web/app/api/onboarding/research/route.ts | 81 |
2 files changed, 142 insertions, 0 deletions
diff --git a/apps/web/app/api/onboarding/extract-content/route.ts b/apps/web/app/api/onboarding/extract-content/route.ts new file mode 100644 index 00000000..6cdb40d5 --- /dev/null +++ b/apps/web/app/api/onboarding/extract-content/route.ts @@ -0,0 +1,61 @@ +export interface ExaContentResult { + url: string + text: string + title: string + author?: string +} + +interface ExaApiResponse { + results: ExaContentResult[] +} + +export async function POST(request: Request) { + try { + const { urls } = await request.json() + + if (!Array.isArray(urls) || urls.length === 0) { + return Response.json( + { error: "Invalid input: urls must be a non-empty array" }, + { status: 400 }, + ) + } + + if (!urls.every((url) => typeof url === "string" && url.trim())) { + return Response.json( + { error: "Invalid input: all urls must be non-empty strings" }, + { status: 400 }, + ) + } + + const response = await fetch("https://api.exa.ai/contents", { + method: "POST", + headers: { + "x-api-key": process.env.EXA_API_KEY ?? "", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + urls, + text: true, + livecrawl: "fallback", + }), + }) + + if (!response.ok) { + console.error( + "Exa API request failed:", + response.status, + response.statusText, + ) + return Response.json( + { error: "Failed to fetch content from Exa API" }, + { status: 500 }, + ) + } + + const data: ExaApiResponse = await response.json() + return Response.json({ results: data.results }) + } catch (error) { + console.error("Exa API request error:", error) + return Response.json({ error: "Internal server error" }, { status: 500 }) + } +} 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 }) + } +} |