| import ReactMarkdown from 'react-markdown'; |
| import remarkGfm from 'remark-gfm'; |
| import { Clock, Server } from 'lucide-react'; |
|
|
| export default function ResponseBlock({ |
| response, |
| isNeonEmphasis = false, |
| isComparisonEmphasis = false, |
| showBadge = false, |
| showModelName = true, |
| showPersona = false, |
| showFooterParams = true, |
| showPrePromptIndicator = false, |
| }) { |
| const personaText = response.persona_name && response.is_neon ? response.persona_name : null; |
| const hasHeader = showModelName || showBadge; |
| const emphasisClass = isNeonEmphasis ? 'neon-emphasis' : (isComparisonEmphasis ? 'comp-emphasis' : ''); |
|
|
| return ( |
| <div className={`response-block ${emphasisClass}`}> |
| {hasHeader && ( |
| <div className="response-header"> |
| <div className="response-model-info"> |
| {showModelName && ( |
| <span className="response-model-name"> |
| {response.model_name} |
| {showPersona && personaText && ( |
| <span className="response-persona-inline"> · {personaText}</span> |
| )} |
| </span> |
| )} |
| </div> |
| </div> |
| )} |
| |
| <div className="response-body"> |
| {response.error ? ( |
| <div className="response-error">{response.response}</div> |
| ) : ( |
| <ReactMarkdown remarkPlugins={[remarkGfm]}>{response.response}</ReactMarkdown> |
| )} |
| </div> |
| |
| <div className="response-footer"> |
| <div className="response-timing"> |
| <Clock size={12} /> |
| {response.elapsed_seconds}s |
| </div> |
| {showPrePromptIndicator ? ( |
| <div className={`response-specs ${response.has_persona ? 'preprompt-yes' : 'preprompt-no'}`}> |
| <span className="spec-value"> |
| {response.has_persona ? 'With pre-prompt' : 'No pre-prompt'} |
| </span> |
| </div> |
| ) : showFooterParams ? ( |
| <div className="response-specs"> |
| <Server size={12} /> |
| <span className={response.params ? 'spec-value' : 'spec-placeholder'}> |
| {response.params ? `Params: ${response.params}` : 'Model size: TBD'} |
| </span> |
| </div> |
| ) : null} |
| </div> |
| |
| <style>{` |
| .response-block { |
| border: 1px solid var(--border-primary); |
| border-radius: 12px; |
| background: var(--card-bg); |
| overflow: hidden; |
| display: flex; |
| flex-direction: column; |
| min-width: 280px; |
| flex: 1; |
| transition: box-shadow 0.2s; |
| } |
| .response-block:hover { |
| box-shadow: var(--shadow-md); |
| } |
| .response-block.neon-emphasis { |
| border-color: var(--neon-border); |
| border-width: 2px; |
| box-shadow: 0 0 0 1px var(--neon-border), var(--shadow-sm); |
| } |
| .response-block.neon-emphasis .response-header { |
| background: var(--neon-bg); |
| } |
| .response-block.comp-emphasis { |
| border-color: var(--comp-border); |
| border-width: 2px; |
| box-shadow: 0 0 0 1px var(--comp-border), var(--shadow-sm); |
| } |
| .response-block.comp-emphasis .response-header { |
| background: var(--comp-bg); |
| } |
| .response-header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 10px 14px; |
| border-bottom: 1px solid var(--border-muted); |
| gap: 8px; |
| } |
| .response-model-info { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| min-width: 0; |
| } |
| .response-model-name { |
| font-size: 14px; |
| font-weight: 600; |
| color: var(--text-primary); |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| } |
| .response-persona-inline { |
| font-weight: 400; |
| color: var(--text-muted); |
| font-size: 13px; |
| } |
| .response-body { |
| padding: 14px; |
| font-size: 14px; |
| line-height: 1.6; |
| color: var(--text-secondary); |
| flex: 1; |
| overflow-y: auto; |
| max-height: 400px; |
| } |
| .response-body h1, .response-body h2, .response-body h3 { |
| color: var(--text-primary); |
| margin: 12px 0 6px; |
| } |
| .response-body h3 { font-size: 15px; } |
| .response-body p { margin-bottom: 8px; } |
| .response-body ul, .response-body ol { |
| padding-left: 20px; |
| margin-bottom: 8px; |
| } |
| .response-body li { margin-bottom: 4px; } |
| .response-body code { |
| font-size: 12px; |
| padding: 1px 4px; |
| border-radius: 4px; |
| background: var(--bg-tertiary); |
| font-family: 'Fira Code', 'Consolas', monospace; |
| } |
| .response-body pre { |
| padding: 10px; |
| border-radius: 8px; |
| background: var(--bg-tertiary); |
| overflow-x: auto; |
| margin-bottom: 8px; |
| } |
| .response-body pre code { |
| padding: 0; |
| background: none; |
| } |
| .response-body strong { |
| color: var(--text-primary); |
| font-weight: 600; |
| } |
| .response-error { |
| color: #EF4444; |
| font-style: italic; |
| } |
| .response-footer { |
| padding: 8px 14px; |
| border-top: 1px solid var(--border-muted); |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| font-size: 11px; |
| color: var(--text-muted); |
| flex-wrap: wrap; |
| gap: 4px; |
| } |
| .response-timing { |
| display: flex; |
| align-items: center; |
| gap: 4px; |
| font-weight: 500; |
| } |
| .response-specs { |
| display: flex; |
| align-items: center; |
| gap: 4px; |
| } |
| .spec-placeholder { |
| font-style: italic; |
| } |
| .spec-value { |
| font-weight: 500; |
| color: var(--text-secondary); |
| } |
| .preprompt-yes .spec-value { |
| color: var(--accent-primary); |
| font-weight: 600; |
| } |
| .preprompt-no .spec-value { |
| color: var(--text-muted); |
| font-style: italic; |
| } |
| |
| @media (max-width: 900px) { |
| .response-block { |
| min-width: 0; |
| } |
| .response-body { |
| max-height: 300px; |
| padding: 12px; |
| } |
| } |
| @media (max-width: 480px) { |
| .response-body { |
| max-height: none; |
| font-size: 13px; |
| padding: 10px; |
| } |
| .response-header { |
| padding: 8px 10px; |
| } |
| .response-footer { |
| padding: 6px 10px; |
| } |
| } |
| `}</style> |
| </div> |
| ); |
| } |
|
|