Spaces:
Sleeping
Sleeping
| 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> | |
| ); | |
| } | |