diff --git a/apps/web/app/api/onboarding/extract-content/route.ts b/apps/web/app/api/onboarding/extract-content/route.ts deleted file mode 100644 index 9322f3242..000000000 --- a/apps/web/app/api/onboarding/extract-content/route.ts +++ /dev/null @@ -1,75 +0,0 @@ -export interface ExaContentResult { - url: string - text: string - title: string - author?: string -} - -interface ExaApiResponse { - results: ExaContentResult[] -} - -const exaApiKey = process.env.EXA_API_KEY -if (!exaApiKey) { - console.error( - "EXA_API_KEY is not configured; /api/onboarding/extract-content will return 503", - ) -} - -export async function POST(request: Request) { - try { - if (!exaApiKey) { - return Response.json( - { error: "Content extraction is unavailable" }, - { status: 503 }, - ) - } - - 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": exaApiKey, - "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 deleted file mode 100644 index 1bac648c6..000000000 --- a/apps/web/app/api/onboarding/research/route.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { xai } from "@ai-sdk/xai" -import { generateText } from "ai" - -interface ResearchRequest { - xUrl: string - name?: string - email?: string -} - -const ALLOWED_X_HOSTS: ReadonlySet = new Set([ - "x.com", - "www.x.com", - "twitter.com", - "www.twitter.com", - "mobile.twitter.com", -]) - -const X_URL_FALLBACK_REGEX = - /^(?:https?:\/\/)?(?:x\.com|www\.x\.com|twitter\.com|www\.twitter\.com|mobile\.twitter\.com)\/([^/\s?#]+)/i - -function isXHost(hostname: string): boolean { - return ALLOWED_X_HOSTS.has(hostname.toLowerCase()) -} - -function extractHandle(input: string): string { - const trimmed = input.trim() - if (!trimmed) return "" - - let handle = trimmed.replace(/^@+/, "") - const lower = handle.toLowerCase() - - if (lower.includes("x.com") || lower.includes("twitter.com")) { - try { - const parsed = new URL( - handle.startsWith("http://") || handle.startsWith("https://") - ? handle - : `https://${handle}`, - ) - handle = isXHost(parsed.hostname) - ? (parsed.pathname.split("/").filter(Boolean)[0] ?? "") - : "" - } catch { - handle = handle.match(X_URL_FALLBACK_REGEX)?.[1] ?? "" - } - } - - return handle.replace(/^@+/, "").split(/[/?#]/)[0]?.toLowerCase() ?? "" -} - -function finalPrompt(handle: string, userContext: string) { - return `You are researching a user based on their X/Twitter profile to help personalize their experience. - -X Handle: @${handle}${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 or handle is required" }, - { status: 400 }, - ) - } - - const handle = extractHandle(xUrl) - - if (!/^[A-Za-z0-9_]{1,15}$/.test(handle)) { - return Response.json( - { error: "Could not parse a valid X/Twitter handle from the input" }, - { 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.responses("grok-4-fast"), - prompt: finalPrompt(handle, userContext), - tools: { - web_search: xai.tools.webSearch(), - x_search: xai.tools.xSearch({ - allowedXHandles: [handle], - }), - }, - }) - - return Response.json({ text }) - } catch (error) { - console.error("Research API error:", error) - return Response.json({ error: "Internal server error" }, { status: 500 }) - } -}