83ccd6c601
- Add ExerciseResearchPanel component with Get Research button, loading state, summary display, and source links - Add ExerciseEncyclopediaPage with exercise list and integrated research panel - Wire encyclopedia view into App.jsx navigation - Add encyclopedia nav button to Dashboard - Add CSS for research panel and encyclopedia search Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
108 lines
3.0 KiB
React
108 lines
3.0 KiB
React
import { useState } from 'react'
|
||
|
||
const API_URL = '/api'
|
||
|
||
function ExerciseResearchPanel({ exerciseId, exerciseName }) {
|
||
const [loading, setLoading] = useState(false)
|
||
const [research, setResearch] = useState(null)
|
||
const [error, setError] = useState(null)
|
||
|
||
const fetchResearch = async () => {
|
||
setLoading(true)
|
||
setError(null)
|
||
try {
|
||
const res = await fetch(`${API_URL}/exercises/${exerciseId}/research`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({})
|
||
})
|
||
if (!res.ok) {
|
||
const data = await res.json()
|
||
throw new Error(data.error || 'Failed to fetch research')
|
||
}
|
||
const data = await res.json()
|
||
setResearch(data)
|
||
} catch (err) {
|
||
setError(err.message)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="research-panel">
|
||
<div className="research-panel-header">
|
||
<h3 className="research-panel-title">Research</h3>
|
||
{!research && (
|
||
<button
|
||
className="btn btn-primary research-btn"
|
||
onClick={fetchResearch}
|
||
disabled={loading}
|
||
>
|
||
{loading ? 'Fetching...' : 'Get Research'}
|
||
</button>
|
||
)}
|
||
{research && (
|
||
<button
|
||
className="btn btn-secondary research-btn"
|
||
onClick={fetchResearch}
|
||
disabled={loading}
|
||
>
|
||
{loading ? 'Fetching...' : 'Refresh'}
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{loading && (
|
||
<div className="research-loading">
|
||
<div className="research-spinner"></div>
|
||
<span>Searching for information on {exerciseName}...</span>
|
||
</div>
|
||
)}
|
||
|
||
{error && (
|
||
<div className="research-error">
|
||
<span>{error}</span>
|
||
<button className="btn-close" onClick={() => setError(null)}>×</button>
|
||
</div>
|
||
)}
|
||
|
||
{research && !loading && (
|
||
<div className="research-results">
|
||
{research.summary && (
|
||
<div className="research-summary">
|
||
<h4>Summary</h4>
|
||
<p>{research.summary}</p>
|
||
</div>
|
||
)}
|
||
|
||
{research.results && research.results.length > 0 && (
|
||
<div className="research-sources">
|
||
<h4>Sources</h4>
|
||
<ul className="research-sources-list">
|
||
{research.results.map((result, i) => (
|
||
<li key={i} className="research-source-item">
|
||
<a
|
||
href={result.url}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="research-source-link"
|
||
>
|
||
{result.title}
|
||
</a>
|
||
{result.snippet && (
|
||
<p className="research-source-snippet">{result.snippet}</p>
|
||
)}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default ExerciseResearchPanel
|