) {
const codeEl = (children as any)?.props;
const lang = codeEl?.className?.replace(/^language-/, '') || '';
- const code = typeof codeEl?.children === 'string'
- ? codeEl.children.replace(/\n$/, '')
- : '';
-
+ const code = typeof codeEl?.children === 'string' ? codeEl.children.replace(/\n$/, '') : '';
return (
{lang && (
@@ -41,10 +44,7 @@ function PreBlock({ children, ...props }: React.HTMLAttributes)
)}
-
+
{children}
@@ -60,15 +60,7 @@ function InlineCode({ children, className, ...props }: React.HTMLAttributes
- {content}
-
- );
- }
-
+function RenderMarkdown({ content }: { content: string }) {
return (
-
+
{content}
);
}
+// ── Chat message types ───────────────────────────────────────
+
+interface ChatMsg {
+ id: string;
+ role: 'user' | 'assistant' | 'system';
+ content: string;
+ format?: 'text' | 'markdown' | 'card' | 'buttons' | 'image' | 'file';
+ card?: any;
+ buttons?: { text: string; data: string }[][];
+ imageUrl?: string;
+ fileName?: string;
+ fileSize?: number;
+ streaming?: boolean;
+ timestamp?: string;
+}
+
+// ── Card renderer ────────────────────────────────────────────
+
+function CardBlock({ card, onAction }: { card: any; onAction: (v: string) => void }) {
+ if (!card) return null;
+ return (
+
+ {card.header && (
+
+ {card.header.title}
+
+ )}
+
+ {card.elements?.map((el: any, i: number) => (
+
+ ))}
+
+
+ );
+}
+
+function CardElement({ el, onAction }: { el: any; onAction: (v: string) => void }) {
+ if (el.type === 'markdown') return ;
+ if (el.type === 'divider') return
;
+ if (el.type === 'note') return {el.text}
;
+ if (el.type === 'actions') {
+ return (
+
+ {el.buttons?.map((btn: any, j: number) => (
+
+ ))}
+
+ );
+ }
+ if (el.type === 'list_item') {
+ return (
+
+ {el.text}
+
+
+ );
+ }
+ if (el.type === 'select') {
+ return (
+
+ );
+ }
+ return null;
+}
+
+function colorToBg(c?: string) {
+ const map: Record = {
+ blue: 'bg-blue-600', green: 'bg-green-600', red: 'bg-red-600', orange: 'bg-orange-500',
+ purple: 'bg-purple-600', grey: 'bg-gray-600', turquoise: 'bg-teal-600', violet: 'bg-violet-600',
+ indigo: 'bg-indigo-600', wathet: 'bg-sky-500', yellow: 'bg-yellow-500', carmine: 'bg-rose-600',
+ };
+ return map[c || ''] || 'bg-gray-800';
+}
+
+// ── Buttons renderer ─────────────────────────────────────────
+
+function ButtonsBlock({ content, buttons, onAction }: { content: string; buttons: { text: string; data: string }[][]; onAction: (v: string) => void }) {
+ return (
+
+
+ {buttons.map((row, i) => (
+
+ {row.map((btn, j) => (
+
+ ))}
+
+ ))}
+
+ );
+}
+
+// ── File/Image attachments ───────────────────────────────────
+
+function FileBlock({ name, size }: { name: string; size?: number }) {
+ return (
+
+
+
+
{name}
+ {size !== undefined &&
{(size / 1024).toFixed(1)} KB
}
+
+
+ );
+}
+
+function ImageBlock({ url }: { url: string }) {
+ return
;
+}
+
+// ── Connection status badge ──────────────────────────────────
+
+function StatusBadge({ status }: { status: BridgeStatus }) {
+ const { t } = useTranslation();
+ if (status === 'connected') {
+ return (
+
+ {t('sessions.bridgeConnected', 'connected')}
+
+ );
+ }
+ if (status === 'connecting' || status === 'registering') {
+ return (
+
+ {t('sessions.bridgeConnecting', 'connecting...')}
+
+ );
+ }
+ return (
+
+ {t('sessions.bridgeDisconnected', 'disconnected')}
+
+ );
+}
+
+// ── Main component ───────────────────────────────────────────
+
export default function SessionChat() {
const { t } = useTranslation();
const { project, id } = useParams<{ project: string; id: string }>();
const [session, setSession] = useState(null);
+ const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [sending, setSending] = useState(false);
const [loading, setLoading] = useState(true);
+ const [typing, setTyping] = useState(false);
+ const [bridgeCfg, setBridgeCfg] = useState(null);
const messagesEnd = useRef(null);
+ const streamBufRef = useRef