a
This commit is contained in:
87
app/api/analysis/reprompt/route.ts
Normal file
87
app/api/analysis/reprompt/route.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { convexAuthNextjsToken, isAuthenticatedNextjs } from "@convex-dev/auth/nextjs/server";
|
||||
import { fetchMutation, fetchQuery } from "convex/nextjs";
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import { scrapeWebsite, analyzeFromText } from "@/lib/scraper";
|
||||
import { repromptSection } from "@/lib/analysis-pipeline";
|
||||
|
||||
const bodySchema = z.object({
|
||||
analysisId: z.string().min(1),
|
||||
sectionKey: z.enum([
|
||||
"profile",
|
||||
"features",
|
||||
"competitors",
|
||||
"keywords",
|
||||
"problems",
|
||||
"personas",
|
||||
"useCases",
|
||||
"dorkQueries",
|
||||
]),
|
||||
prompt: z.string().optional(),
|
||||
});
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
if (!(await isAuthenticatedNextjs())) {
|
||||
const redirectUrl = new URL("/auth", request.url);
|
||||
const referer = request.headers.get("referer");
|
||||
const nextPath = referer ? new URL(referer).pathname + new URL(referer).search : "/";
|
||||
redirectUrl.searchParams.set("next", nextPath);
|
||||
return NextResponse.redirect(redirectUrl);
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const parsed = bodySchema.parse(body);
|
||||
const token = await convexAuthNextjsToken();
|
||||
|
||||
const analysis = await fetchQuery(
|
||||
api.analyses.getById,
|
||||
{ analysisId: parsed.analysisId as any },
|
||||
{ token }
|
||||
);
|
||||
|
||||
if (!analysis) {
|
||||
return NextResponse.json({ error: "Analysis not found." }, { status: 404 });
|
||||
}
|
||||
|
||||
const dataSource = await fetchQuery(
|
||||
api.dataSources.getById,
|
||||
{ dataSourceId: analysis.dataSourceId as any },
|
||||
{ token }
|
||||
);
|
||||
|
||||
if (!dataSource) {
|
||||
return NextResponse.json({ error: "Data source not found." }, { status: 404 });
|
||||
}
|
||||
|
||||
const isManual = dataSource.url.startsWith("manual:") || dataSource.url === "manual-input";
|
||||
const featureText = (analysis.features || []).map((f: any) => f.name).join("\n");
|
||||
const content = isManual
|
||||
? await analyzeFromText(
|
||||
analysis.productName,
|
||||
analysis.description || "",
|
||||
featureText
|
||||
)
|
||||
: await scrapeWebsite(dataSource.url);
|
||||
|
||||
const items = await repromptSection(
|
||||
parsed.sectionKey,
|
||||
content,
|
||||
analysis as any,
|
||||
parsed.prompt
|
||||
);
|
||||
|
||||
await fetchMutation(
|
||||
api.analysisSections.replaceSection,
|
||||
{
|
||||
analysisId: parsed.analysisId as any,
|
||||
sectionKey: parsed.sectionKey,
|
||||
items,
|
||||
lastPrompt: parsed.prompt,
|
||||
source: "ai",
|
||||
},
|
||||
{ token }
|
||||
);
|
||||
|
||||
return NextResponse.json({ success: true, items });
|
||||
}
|
||||
21
app/api/checkout/route.ts
Normal file
21
app/api/checkout/route.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Checkout } from "@polar-sh/nextjs";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export const GET = async () => {
|
||||
if (!process.env.POLAR_ACCESS_TOKEN || !process.env.POLAR_SUCCESS_URL) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
"Missing POLAR_ACCESS_TOKEN or POLAR_SUCCESS_URL environment variables.",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const handler = Checkout({
|
||||
accessToken: process.env.POLAR_ACCESS_TOKEN,
|
||||
successUrl: process.env.POLAR_SUCCESS_URL,
|
||||
});
|
||||
|
||||
return handler();
|
||||
};
|
||||
@@ -17,7 +17,9 @@ const searchSchema = z.object({
|
||||
icon: z.string().optional(),
|
||||
enabled: z.boolean(),
|
||||
searchTemplate: z.string().optional(),
|
||||
rateLimit: z.number()
|
||||
rateLimit: z.number(),
|
||||
site: z.string().optional(),
|
||||
custom: z.boolean().optional()
|
||||
})),
|
||||
strategies: z.array(z.string()),
|
||||
maxResults: z.number().default(50)
|
||||
@@ -115,9 +117,21 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
const resultUrls = Array.from(
|
||||
new Set(searchResults.map((result) => result.url).filter(Boolean))
|
||||
)
|
||||
const existingUrls = await fetchQuery(
|
||||
api.seenUrls.listExisting,
|
||||
{ projectId: projectId as any, urls: resultUrls },
|
||||
{ token }
|
||||
)
|
||||
const existingSet = new Set(existingUrls)
|
||||
const newUrls = resultUrls.filter((url) => !existingSet.has(url))
|
||||
const filteredResults = searchResults.filter((result) => !existingSet.has(result.url))
|
||||
|
||||
// Score and rank
|
||||
console.log(' Scoring opportunities...')
|
||||
const opportunities = scoreOpportunities(searchResults, analysis as EnhancedProductAnalysis)
|
||||
const opportunities = scoreOpportunities(filteredResults, analysis as EnhancedProductAnalysis)
|
||||
console.log(` ✓ Scored ${opportunities.length} opportunities`)
|
||||
if (jobId) {
|
||||
await fetchMutation(
|
||||
@@ -135,18 +149,22 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
if (newUrls.length > 0) {
|
||||
await fetchMutation(
|
||||
api.seenUrls.markSeenBatch,
|
||||
{ projectId: projectId as any, urls: newUrls, source: "search" },
|
||||
{ token }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
opportunities: opportunities.slice(0, 50),
|
||||
stats: {
|
||||
queriesGenerated: queries.length,
|
||||
rawResults: searchResults.length,
|
||||
opportunitiesFound: opportunities.length,
|
||||
highRelevance: opportunities.filter(o => o.relevanceScore >= 0.7).length,
|
||||
averageScore: opportunities.length > 0
|
||||
? opportunities.reduce((a, o) => a + o.relevanceScore, 0) / opportunities.length
|
||||
: 0
|
||||
rawResults: filteredResults.length,
|
||||
opportunitiesFound: opportunities.length
|
||||
},
|
||||
queries: queries.map(q => ({
|
||||
query: q.query,
|
||||
|
||||
Reference in New Issue
Block a user