feat(05-03): frontend fallback integration for research display

- ExerciseResearchPanel: add ProviderBadge component showing which AI
  tier (Ollama/Gemini/OpenRouter/OpenCode/Exa) served the response
- Add auto-retry on 429/503 with 2s delay and retry counter in button
- Normalize error messages for common failure modes (network, rate-limit)
- ResearchDisplay: pass onRetry prop to error state for inline retry button
- CSS: .research-panel-controls flex row, .provider-badge with per-provider
  colour coding, .rd-error-actions + .rd-retry button styles
This commit is contained in:
2026-03-02 23:44:32 +01:00
parent ab87e54630
commit 2a0496b915
3 changed files with 150 additions and 21 deletions
+16 -8
View File
@@ -9,16 +9,23 @@ function ResearchLoadingSkeleton({ exerciseName }) {
)
}
function ResearchError({ message, onDismiss }) {
function ResearchError({ message, onDismiss, onRetry }) {
return (
<div className="rd-error" role="alert">
<span className="rd-error-icon" aria-hidden="true"></span>
<span className="rd-error-message">{message}</span>
{onDismiss && (
<button className="rd-dismiss" onClick={onDismiss} aria-label="Dismiss error">
×
</button>
)}
<div className="rd-error-actions">
{onRetry && (
<button className="rd-retry btn btn-sm btn-primary" onClick={onRetry}>
Retry
</button>
)}
{onDismiss && (
<button className="rd-dismiss" onClick={onDismiss} aria-label="Dismiss error">
×
</button>
)}
</div>
</div>
)
}
@@ -52,14 +59,15 @@ function ResearchSourceCard({ result, index }) {
* data {object} Research data: { summary, results }
* name {string} Exercise name (shown during loading)
* onDismiss {function} Clear error callback
* onRetry {function} Retry fetch callback
*/
function ResearchDisplay({ loading, error, data, name, onDismiss }) {
function ResearchDisplay({ loading, error, data, name, onDismiss, onRetry }) {
if (loading) {
return <ResearchLoadingSkeleton exerciseName={name} />
}
if (error) {
return <ResearchError message={error} onDismiss={onDismiss} />
return <ResearchError message={error} onDismiss={onDismiss} onRetry={onRetry} />
}
if (!data) return null