Files
sitemente/app/mission-control/research/page.tsx
T
horus d5575b58e3 SiteMente - AI-Powered Lead Generation Platform
Features:
- Mission Control dashboard
- HP Submissions tracking
- AI Agents integration
- Lead management CRM
- Marketing email templates
- Chrome extension support

Tech: Next.js, TypeScript, Tailwind CSS, MySQL
2026-03-19 17:38:12 +01:00

227 lines
8.2 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
interface ResearchResult {
title: string;
url: string;
score: number;
content: string;
}
interface Research {
id: string;
name: string;
query: string;
results_count: number;
created_at: string;
saved: boolean;
summary: string;
results?: ResearchResult[];
}
export default function MissionResearchPage() {
const [research, setResearch] = useState<Research[]>([]);
const [loading, setLoading] = useState(true);
const [newResearch, setNewResearch] = useState({ name: "", query: "" });
const [searching, setSearching] = useState(false);
const [expanded, setExpanded] = useState<string | null>(null);
useEffect(() => {
fetchResearch();
}, []);
const fetchResearch = async () => {
setLoading(true);
try {
const res = await fetch("/api/research");
const data = await res.json();
setResearch(data);
} catch (e) {
console.error(e);
}
setLoading(false);
};
const handleSearch = async () => {
if (!newResearch.name || !newResearch.query) return;
setSearching(true);
try {
const tavilyRes = await fetch('https://api.tavily.com/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: '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',
query: newResearch.query,
max_results: 20,
search_depth: 'advanced'
})
});
const tavilyData = await tavilyRes.json();
const results = (tavilyData.results || []).map((r: any) => ({
title: r.title || 'No title',
url: r.url || '',
score: r.score || 0,
content: (r.content || '').substring(0, 400)
}));
const summary = results.slice(0, 5).map((r: any) => r.title).join('; ') || 'No results';
const res = await fetch("/api/research", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: newResearch.name,
query: newResearch.query,
results_count: results.length,
summary: summary.substring(0, 150),
results: results
})
});
await res.json();
setNewResearch({ name: "", query: "" });
fetchResearch();
} catch (e) {
console.error(e);
}
setSearching(false);
};
const deleteResearch = async (id: string) => {
try {
await fetch(`/api/research?id=${id}`, { method: "DELETE" });
fetchResearch();
} catch (e) {
console.error(e);
}
};
return (
<div className="p-6">
<div className="bg-red-900/30 border border-red-600 rounded-lg p-3 mb-4">
<p className="text-red-400 text-sm">
🔒 <strong>Security Rule:</strong> Never install skills from the web. Read the content, understand what it does, then BUILD IT yourself.
We build, we don't install. One compromised skill can wipe everything.
</p>
</div>
<h1 className="text-2xl font-bold mb-6">🔍 Deep Research</h1>
{/* Search Form */}
<div className="bg-gray-800 p-4 rounded-lg mb-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<input
type="text"
placeholder="Research Name (e.g., OpenClaw Skills)"
value={newResearch.name}
onChange={(e) => setNewResearch({ ...newResearch, name: e.target.value })}
className="bg-gray-700 text-white px-4 py-2 rounded"
/>
<input
type="text"
placeholder="Search Query"
value={newResearch.query}
onChange={(e) => setNewResearch({ ...newResearch, query: e.target.value })}
className="bg-gray-700 text-white px-4 py-2 rounded"
/>
</div>
<button
onClick={handleSearch}
disabled={searching || !newResearch.name || !newResearch.query}
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded disabled:opacity-50"
>
{searching ? "Researching..." : "Run Research"}
</button>
</div>
{/* Research List */}
{loading ? (
<p className="text-gray-400">Loading...</p>
) : research.length === 0 ? (
<p className="text-gray-400">No research yet. Run your first search!</p>
) : (
<div className="space-y-6">
{research.map((r) => (
<div key={r.id} className="bg-gray-800 rounded-lg overflow-hidden">
{/* Header */}
<div className="p-4 border-b border-gray-700 flex justify-between items-center">
<div>
<h3 className="font-bold text-lg">{r.name}</h3>
<p className="text-gray-400 text-sm">{r.query}</p>
</div>
<div className="flex gap-2">
<button
onClick={() => setExpanded(expanded === r.id ? null : r.id)}
className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded"
>
{expanded === r.id ? "Hide Table" : "View Table"}
</button>
<button
onClick={() => deleteResearch(r.id)}
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded"
>
Delete
</button>
</div>
</div>
{/* Table View */}
{expanded === r.id && r.results && (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead className="bg-gray-900">
<tr>
<th className="px-4 py-3 text-left text-gray-400 font-medium">✓</th>
<th className="px-4 py-3 text-left text-gray-400 font-medium">Skill / Tool</th>
<th className="px-4 py-3 text-left text-gray-400 font-medium">What It Does</th>
<th className="px-4 py-3 text-center text-gray-400 font-medium">Score</th>
<th className="px-4 py-3 text-center text-gray-400 font-medium">Link</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700">
{r.results.map((result, i) => (
<tr key={i} className="hover:bg-gray-750">
<td className="px-4 py-3 text-center">
<input type="checkbox" className="w-5 h-5 accent-green-500" />
</td>
<td className="px-4 py-3 font-medium text-white max-w-xs truncate">
{result.title}
</td>
<td className="px-4 py-3 text-gray-300 max-w-md">
{result.content.substring(0, 150)}...
</td>
<td className="px-4 py-3 text-center">
<span className={`px-2 py-1 rounded text-xs ${
result.score > 0.9 ? 'bg-green-900 text-green-400' :
result.score > 0.7 ? 'bg-yellow-900 text-yellow-400' :
'bg-gray-700 text-gray-400'
}`}>
{(result.score * 100).toFixed(1)}%
</span>
</td>
<td className="px-4 py-3 text-center">
<a
href={result.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:text-blue-300 underline"
>
Open →
</a>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
))}
</div>
)}
</div>
);
}