In this guide, we'll build a production-ready Arabic Legal AI Chatbot in React/TypeScript that streams grounded legal answers, renders interactive citation hovercards, and handles weak grounding gracefully.
Rendering diagram...
1. Requesting Streaming Reasoning
When the user asks a question, open an SSE connection to POST /v1/reason:
// services/dike-chat.ts
export interface Citation {
marker: number;
canonical_id: string;
article_no: string;
citation_string: string;
snippet: string;
verified: boolean;
}
export interface ChatStreamCallbacks {
onToken: (token: string) => void;
onCitations: (citations: Citation[], isWeak: boolean) => void;
onError: (err: Error) => void;
}
export async function streamLegalAnswer(
question: string,
callbacks: ChatStreamCallbacks
) {
try {
const res = await fetch('https://api.dike.it.com/v1/reason', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_DIKE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
question,
filters: { jurisdiction: 'EG', status: ['in_force'] },
stream: true,
language: 'ar',
}),
});
const isWeak = res.headers.get('X-Dike-Grounding') === 'weak';
const reader = res.body?.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (reader) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ') && !line.includes('[DONE]')) {
const payload = JSON.parse(line.slice(6));
if (payload.token) callbacks.onToken(payload.token);
} else if (line.startsWith('event: citations')) {
// Next line contains citations payload
}
}
}
} catch (err: any) {
callbacks.onError(err);
}
}2. Rendering Interactive Citation Badges (React)
Replace plain [1] markers in the generated text with clickable interactive badges that display source snippets:
// components/GroundedMessage.tsx
import React, { useState } from 'react';
import type { Citation } from '../services/dike-chat';
export function GroundedMessage({
text,
citations,
isWeakGrounding,
}: {
text: string;
citations: Citation[];
isWeakGrounding?: boolean;
}) {
const [activeCitation, setActiveCitation] = useState<Citation | null>(null);
// Split text by citation markers like [1], [2]
const parts = text.split(/(\[\d+\])/g);
return (
<div className="space-y-4 rounded-xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-950">
{/* Weak Grounding Warning Banner */}
{isWeakGrounding && (
<div className="flex items-center gap-2 rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800 dark:border-amber-900/50 dark:bg-amber-950/40 dark:text-amber-300">
<span className="font-semibold">تنبيه:</span>
<span>لم يعثر النظام على سند قانوني كافٍ في المصادر المفهرسة لدعم هذه الإجابة بشكل كامل.</span>
</div>
)}
{/* Message Text with Interactive Footnotes */}
<div className="leading-relaxed text-slate-800 dark:text-slate-200" dir="rtl">
{parts.map((part, index) => {
const match = part.match(/^\[(\d+)\]$/);
if (match) {
const markerNum = parseInt(match[1], 10);
const citation = citations.find((c) => c.marker === markerNum);
if (!citation) return <span key={index}>{part}</span>;
return (
<button
key={index}
onClick={() => setActiveCitation(citation)}
className="mx-1 inline-flex items-center rounded bg-amber-100 px-1.5 py-0.5 text-xs font-bold text-amber-900 transition hover:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-300"
title={citation.citation_string}
>
[{markerNum}]
</button>
);
}
return <span key={index}>{part}</span>;
})}
</div>
{/* Active Citation Modal / Card */}
{activeCitation && (
<div className="mt-4 rounded-lg border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-900" dir="rtl">
<div className="flex items-center justify-between">
<span className="font-semibold text-slate-900 dark:text-white">
{activeCitation.citation_string}
</span>
<span className="text-xs text-emerald-600 dark:text-emerald-400">✓ موثق ومطابق</span>
</div>
<p className="mt-2 text-sm text-slate-600 dark:text-slate-400">
"{activeCitation.snippet}"
</p>
<a
href={`/docs/api-reference/document?id=${activeCitation.canonical_id}`}
className="mt-2 inline-block text-xs text-amber-700 hover:underline dark:text-amber-400"
>
عرض النص الكامل للقانون ←
</a>
</div>
)}
</div>
);
}Next Steps
- Learn how to audit contracts for legal citations in Contract Citation Auditing.
- Explore graph relationship mapping in Legal Graph Explorer.