quanthedge / frontend /src /pages /ResearchInsights.tsx
jashdoshi77's picture
QuantHedge: Full deployment with Docker + nginx + uvicorn
9d29748
import { useState, useEffect } from 'react';
import { researchAPI } from '../api/client';
export default function ResearchInsights() {
const [reports, setReports] = useState<any[]>([]);
const [selectedReport, setSelectedReport] = useState<any>(null);
const [form, setForm] = useState({ insight_type: 'market_summary', tickers: 'AAPL, MSFT, GOOGL, SPY', context: '', include_signals: true });
const [generating, setGenerating] = useState(false);
useEffect(() => {
researchAPI.listReports().then(({ data }) => setReports(data.reports || [])).catch(console.error);
}, []);
const generate = async () => {
setGenerating(true);
try {
const tickers = form.tickers.split(',').map(t => t.trim()).filter(Boolean);
const { data } = await researchAPI.generate({ insight_type: form.insight_type, tickers, context: form.context || undefined });
setSelectedReport(data);
const r = await researchAPI.listReports();
setReports(r.data.reports || []);
} catch (e) { console.error(e); } finally { setGenerating(false); }
};
const exportReport = async (format: 'pdf' | 'docx') => {
if (!selectedReport?.id) return;
try {
const res = format === 'pdf'
? await researchAPI.exportPdf(selectedReport.id)
: await researchAPI.exportDocx(selectedReport.id);
const blob = new Blob([res.data], {
type: format === 'pdf' ? 'application/pdf' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
});
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `quanthedge_report_${selectedReport.id}.${format}`;
a.click();
window.URL.revokeObjectURL(url);
} catch (e) { console.error(e); }
};
return (
<div className="page animate-fade-in">
<div className="page-header"><h1>AI Research <span className="text-gradient">Insights</span></h1><p>Generate market summaries, signal analysis, and strategy reports with AI</p></div>
<div className="grid-2" style={{gridTemplateColumns:'1fr 300px'}}>
<div style={{display:'flex',flexDirection:'column',gap:'1.5rem'}}>
{/* Generate form */}
<div className="card">
<div className="card-header"><h3>Generate Insight</h3></div>
<div style={{display:'flex',gap:'0.75rem',flexWrap:'wrap',alignItems:'flex-end'}}>
<div>
<label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',display:'block',marginBottom:'0.375rem'}}>Type</label>
<select className="input" style={{width:200}} value={form.insight_type} onChange={e => setForm(f => ({...f,insight_type:e.target.value}))}>
<option value="market_summary">Market Summary</option>
<option value="signal_analysis">Signal Analysis</option>
<option value="strategy_report">Strategy Report</option>
</select>
</div>
<div style={{flex:1,minWidth:200}}>
<label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',display:'block',marginBottom:'0.375rem'}}>Tickers</label>
<input className="input" value={form.tickers} onChange={e => setForm(f => ({...f,tickers:e.target.value}))} />
</div>
<button className="btn btn-primary" onClick={generate} disabled={generating}>
{generating ? <><div className="spinner" /> Generating...</> : 'โœจ Generate'}
</button>
</div>
</div>
{/* Report display */}
{selectedReport && (
<div className="card">
<div className="card-header">
<h3>{selectedReport.title}</h3>
<div className="flex-gap">
<span className="badge badge-primary">{selectedReport.report_type}</span>
<span style={{fontSize:'0.7rem',color:'var(--text-muted)'}}>{ selectedReport.ai_model}</span>
</div>
</div>
<div style={{whiteSpace:'pre-wrap',fontSize:'0.85rem',lineHeight:1.8,color:'var(--text-secondary)'}}>
{selectedReport.content}
</div>
<div style={{marginTop:'1rem',paddingTop:'0.75rem',borderTop:'1px solid var(--border-subtle)',display:'flex',gap:'0.5rem',flexWrap:'wrap',alignItems:'center'}}>
{selectedReport.tickers?.map((t: string) => <span key={t} className="badge badge-primary">{t}</span>)}
<div style={{ marginLeft: 'auto', display: 'flex', gap: '0.5rem' }}>
<button className="btn btn-secondary" style={{ fontSize: '0.75rem', padding: '0.375rem 0.75rem' }}
onClick={() => exportReport('pdf')}>๐Ÿ“„ Export PDF</button>
<button className="btn btn-secondary" style={{ fontSize: '0.75rem', padding: '0.375rem 0.75rem' }}
onClick={() => exportReport('docx')}>๐Ÿ“ Export Word</button>
</div>
</div>
</div>
)}
{!selectedReport && !generating && (
<div className="empty-state card">
<div style={{marginBottom:'1rem'}}><svg viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="1.5" style={{width:48,height:48}}><path d="M12 2a4 4 0 014 4v1a4 4 0 01-8 0V6a4 4 0 014-4z"/><path d="M6 21v-2a4 4 0 014-4h4a4 4 0 014 4v2"/></svg></div>
<h3>Generate AI Research</h3>
<p>Enter tickers and select an insight type to generate an AI-powered research report using Groq.</p>
</div>
)}
</div>
{/* Sidebar: past reports */}
<div className="card" style={{alignSelf:'start'}}>
<div className="card-header"><h3>Reports ({reports.length})</h3></div>
{reports.map((r: any) => (
<div key={r.id} onClick={() => setSelectedReport(r)}
style={{padding:'0.75rem',cursor:'pointer',borderBottom:'1px solid var(--border-subtle)'}}>
<div style={{fontSize:'0.8rem',fontWeight:600}}>{r.title}</div>
<div style={{display:'flex',justifyContent:'space-between',marginTop:'0.25rem'}}>
<span className="badge badge-primary" style={{fontSize:'0.6rem'}}>{r.report_type}</span>
<span style={{fontSize:'0.65rem',color:'var(--text-muted)'}}>{r.created_at?.slice(0,10)}</span>
</div>
</div>
))}
{!reports.length && <p style={{fontSize:'0.8rem',color:'var(--text-muted)',padding:'1rem 0'}}>No reports yet</p>}
</div>
</div>
</div>
);
}