feat: Refine keyword generation logic, enforce Serper API usage, and enhance search query construction with platform-specific templates.
This commit is contained in:
@@ -95,7 +95,6 @@ const GOAL_PRESETS: {
|
||||
title: string
|
||||
description: string
|
||||
strategies: SearchStrategy[]
|
||||
intensity: 'broad' | 'balanced' | 'targeted'
|
||||
maxQueries: number
|
||||
}[] = [
|
||||
{
|
||||
@@ -103,7 +102,6 @@ const GOAL_PRESETS: {
|
||||
title: "High-intent leads",
|
||||
description: "Shortlist people actively searching to buy or switch.",
|
||||
strategies: ["direct-keywords", "competitor-alternative", "comparison", "recommendation"],
|
||||
intensity: "targeted",
|
||||
maxQueries: 30,
|
||||
},
|
||||
{
|
||||
@@ -111,7 +109,6 @@ const GOAL_PRESETS: {
|
||||
title: "Problem pain",
|
||||
description: "Find people expressing frustration or blockers.",
|
||||
strategies: ["problem-pain", "emotional-frustrated"],
|
||||
intensity: "balanced",
|
||||
maxQueries: 40,
|
||||
},
|
||||
{
|
||||
@@ -119,7 +116,6 @@ const GOAL_PRESETS: {
|
||||
title: "Market scan",
|
||||
description: "Broader sweep to map demand and platforms.",
|
||||
strategies: ["direct-keywords", "problem-pain", "how-to", "recommendation"],
|
||||
intensity: "broad",
|
||||
maxQueries: 50,
|
||||
},
|
||||
]
|
||||
@@ -137,7 +133,6 @@ export default function OpportunitiesPage() {
|
||||
'problem-pain',
|
||||
'competitor-alternative'
|
||||
])
|
||||
const [intensity, setIntensity] = useState<'broad' | 'balanced' | 'targeted'>('balanced')
|
||||
const [maxQueries, setMaxQueries] = useState(50)
|
||||
const [goalPreset, setGoalPreset] = useState<string>('high-intent')
|
||||
const [isSearching, setIsSearching] = useState(false)
|
||||
@@ -253,9 +248,6 @@ export default function OpportunitiesPage() {
|
||||
if (Array.isArray(parsed.strategies)) {
|
||||
setStrategies(parsed.strategies)
|
||||
}
|
||||
if (parsed.intensity === 'broad' || parsed.intensity === 'balanced' || parsed.intensity === 'targeted') {
|
||||
setIntensity(parsed.intensity)
|
||||
}
|
||||
if (typeof parsed.maxQueries === 'number') {
|
||||
setMaxQueries(Math.min(Math.max(parsed.maxQueries, 10), 50))
|
||||
}
|
||||
@@ -275,7 +267,6 @@ export default function OpportunitiesPage() {
|
||||
}
|
||||
} else if (defaultPlatformsRef.current) {
|
||||
setStrategies(['direct-keywords', 'problem-pain', 'competitor-alternative'])
|
||||
setIntensity('balanced')
|
||||
setMaxQueries(50)
|
||||
setGoalPreset('high-intent')
|
||||
setPlatforms(defaultPlatformsRef.current)
|
||||
@@ -298,12 +289,11 @@ export default function OpportunitiesPage() {
|
||||
const payload = {
|
||||
goalPreset,
|
||||
strategies,
|
||||
intensity,
|
||||
maxQueries,
|
||||
platformIds: platforms.filter((platform) => platform.enabled).map((platform) => platform.id),
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(payload))
|
||||
}, [selectedProjectId, goalPreset, strategies, intensity, maxQueries, platforms])
|
||||
}, [selectedProjectId, goalPreset, strategies, maxQueries, platforms])
|
||||
|
||||
useEffect(() => {
|
||||
if (!analysis && latestAnalysis === null) {
|
||||
@@ -330,7 +320,6 @@ export default function OpportunitiesPage() {
|
||||
if (!preset) return
|
||||
setGoalPreset(preset.id)
|
||||
setStrategies(preset.strategies)
|
||||
setIntensity(preset.intensity)
|
||||
setMaxQueries(preset.maxQueries)
|
||||
}
|
||||
|
||||
@@ -350,7 +339,6 @@ export default function OpportunitiesPage() {
|
||||
searchTemplate: platform.searchTemplate ?? "",
|
||||
})),
|
||||
strategies,
|
||||
intensity,
|
||||
maxResults: Math.min(maxQueries, 50)
|
||||
}
|
||||
setLastSearchConfig(config as SearchConfig)
|
||||
@@ -576,24 +564,12 @@ export default function OpportunitiesPage() {
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Depth + Queries */}
|
||||
<div className="space-y-4">
|
||||
<Label className="text-sm font-medium uppercase text-muted-foreground">Depth</Label>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>Broad</span>
|
||||
<span>Targeted</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[intensity === 'broad' ? 0 : intensity === 'balanced' ? 50 : 100]}
|
||||
onValueChange={([v]) => setIntensity(v < 33 ? 'broad' : v < 66 ? 'balanced' : 'targeted')}
|
||||
max={100}
|
||||
step={50}
|
||||
/>
|
||||
</div>
|
||||
{/* Queries */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium uppercase text-muted-foreground">Max Queries</Label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-medium uppercase text-muted-foreground">Max Queries</Label>
|
||||
<span className="text-xs text-muted-foreground">Max Queries</span>
|
||||
<span className="text-xs text-muted-foreground">{maxQueries}</span>
|
||||
</div>
|
||||
<Slider
|
||||
@@ -662,7 +638,6 @@ export default function OpportunitiesPage() {
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
|
||||
<Badge variant="outline">Goal: {GOAL_PRESETS.find((preset) => preset.id === goalPreset)?.title || "Custom"}</Badge>
|
||||
<Badge variant="outline">Intensity: {intensity}</Badge>
|
||||
<Badge variant="outline">Max queries: {maxQueries}</Badge>
|
||||
</div>
|
||||
{latestJob && (latestJob.status === "running" || latestJob.status === "pending") && (
|
||||
@@ -698,7 +673,14 @@ export default function OpportunitiesPage() {
|
||||
)}
|
||||
{searchError && (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>{searchError}</AlertDescription>
|
||||
<AlertDescription>
|
||||
{searchError}
|
||||
{searchError.includes('SERPER_API_KEY') && (
|
||||
<span className="block mt-2 text-xs text-muted-foreground">
|
||||
Add `SERPER_API_KEY` to your environment and restart the app to enable search.
|
||||
</span>
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user