feat: implement R42 chat infrastructure with Anthropic API integration and custom design system
This commit is contained in:
84
src/components/chat/ChatMessage.jsx
Normal file
84
src/components/chat/ChatMessage.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import Mark from '../ui/Mark';
|
||||
import { STRINGS } from './prompts';
|
||||
|
||||
/**
|
||||
* Renders one message bubble. Assistant messages may include a `suggestion`
|
||||
* (validated graph delta) — the user confirms or rejects inline.
|
||||
*/
|
||||
export default function ChatMessage({ msg, onAcceptSuggestion, onRejectSuggestion }) {
|
||||
if (msg.role === 'user') {
|
||||
return (
|
||||
<div className="r42-msg me">
|
||||
<div className="bub">{msg.content}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (msg.role === 'error') {
|
||||
return (
|
||||
<div className="r42-msg error">
|
||||
<div className="av-sm">
|
||||
<Mark state="error" size={20} brace="#ECE9E9" letter="#ECE9E9" />
|
||||
</div>
|
||||
<div className="bub">{msg.content}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// assistant
|
||||
const s = msg.suggestion;
|
||||
return (
|
||||
<div className="r42-msg">
|
||||
<div className="av-sm">
|
||||
<Mark state="idle" size={20} brace="#ECE9E9" letter="#ECE9E9" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="bub">{msg.content}</div>
|
||||
{s && (
|
||||
<div className="r42-suggestion" role="group" aria-label="Voorstel kennisgraaf">
|
||||
<div className="r42-suggestion-title">{STRINGS.suggestionTitle}</div>
|
||||
{s.reason && <div style={{ marginBottom: 6 }}>{s.reason}</div>}
|
||||
{(s.topics.length > 0 || s.relations.length > 0) && (
|
||||
<ul className="r42-suggestion-items">
|
||||
{s.topics.map(t => (
|
||||
<li key={`t-${t.id}`}>
|
||||
<strong>{t.label}</strong>{' '}
|
||||
<span style={{ opacity: 0.7 }}>({t.type})</span>
|
||||
</li>
|
||||
))}
|
||||
{s.relations.map((r, i) => (
|
||||
<li key={`r-${i}`}>
|
||||
<code>{r.source}</code>{' '}
|
||||
<span style={{ opacity: 0.7 }}>{r.type}</span>{' → '}
|
||||
<code>{r.target}</code>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{s.status === 'pending' ? (
|
||||
<div className="r42-suggestion-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="primary"
|
||||
onClick={() => onAcceptSuggestion(msg.id)}
|
||||
>
|
||||
{STRINGS.suggestionAccept}
|
||||
</button>
|
||||
<button type="button" onClick={() => onRejectSuggestion(msg.id)}>
|
||||
{STRINGS.suggestionReject}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="r42-suggestion-status">
|
||||
{s.status === 'applied' && STRINGS.suggestionAppliedAdmin}
|
||||
{s.status === 'queued' && STRINGS.suggestionQueuedUser}
|
||||
{s.status === 'rejected' && STRINGS.suggestionDismissed}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user