Files
gravl/frontend/src/components/ExerciseResearchPanel.jsx
T
clawd 83ccd6c601 feat(05-03): Exercise research frontend integration
- 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>
2026-03-02 19:20:40 +01:00

108 lines
3.0 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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