Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

ConversationThread.tsx4.92 kB
/** * ConversationThread Component * Container for managing and displaying conversation threads */ 'use client'; import React, { useMemo } from 'react'; import { ThreadedSpeechCard } from './ThreadedSpeechCard'; interface Statement { id: string; time?: string; who_en?: string; who_fr?: string; content_en?: string; content_fr?: string; h1_en?: string; h1_fr?: string; h2_en?: string; h2_fr?: string; statement_type?: string; wordcount?: number; thread_id?: string; parent_statement_id?: string; sequence_in_thread?: number; madeBy?: { id: string; name: string; party: string; photo_url?: string; }; replies?: Statement[]; } interface ConversationThreadProps { statements: Statement[]; defaultExpanded?: boolean; onStatementClick?: (statement: Statement) => void; className?: string; } interface Thread { id: string; root: Statement; replies: Statement[]; } /** * Groups statements into conversation threads */ function groupStatementsIntoThreads(statements: Statement[]): Thread[] { // If statements already have replies populated from GraphQL const rootStatements = statements.filter( (s) => !s.parent_statement_id || s.sequence_in_thread === 0 ); // Create thread objects const threads: Thread[] = rootStatements.map((root) => { // Find replies for this root const replies = statements .filter((s) => s.parent_statement_id === root.id && s.id !== root.id) .sort((a, b) => (a.sequence_in_thread || 0) - (b.sequence_in_thread || 0)); return { id: root.thread_id || root.id, root, replies, }; }); return threads; } /** * Client-side threading fallback * Used when thread_id/parent_statement_id are not available from backend */ function inferThreadsFromStatements(statements: Statement[]): Thread[] { const threads: Thread[] = []; let currentThread: Thread | null = null; // Sort by time const sorted = [...statements].sort((a, b) => { if (!a.time || !b.time) return 0; return new Date(a.time).getTime() - new Date(b.time).getTime(); }); sorted.forEach((stmt) => { const isQuestion = stmt.statement_type?.toLowerCase() === 'question'; const isReply = ['answer', 'interjection'].includes(stmt.statement_type?.toLowerCase() || ''); if (isQuestion || !currentThread) { // Start new thread if (currentThread) { threads.push(currentThread); } currentThread = { id: stmt.id, root: stmt, replies: [], }; } else if (isReply && currentThread) { // Add to current thread if within time threshold (5 min) const timeDiff = currentThread.root.time && stmt.time ? new Date(stmt.time).getTime() - new Date(currentThread.root.time).getTime() : 0; if (timeDiff <= 300000) { // 5 minutes currentThread.replies.push(stmt); } else { // Start new thread threads.push(currentThread); currentThread = { id: stmt.id, root: stmt, replies: [], }; } } else { // Not a question or reply, create standalone thread if (currentThread) { threads.push(currentThread); } currentThread = { id: stmt.id, root: stmt, replies: [], }; } }); // Add last thread if (currentThread) { threads.push(currentThread); } return threads; } /** * Main conversation thread container */ export const ConversationThread = React.memo(function ConversationThread({ statements, defaultExpanded = false, onStatementClick, className = '', }: ConversationThreadProps) { // Group statements into threads const threads = useMemo(() => { if (!statements || statements.length === 0) { return []; } // Check if statements have thread_id (from backend) const hasThreading = statements.some((s) => s.thread_id || s.parent_statement_id); if (hasThreading) { return groupStatementsIntoThreads(statements); } else { // Fallback to client-side inference return inferThreadsFromStatements(statements); } }, [statements]); if (threads.length === 0) { return null; } return ( <div className={`space-y-6 ${className}`}> {threads.map((thread) => ( <ThreadedSpeechCard key={thread.id} rootStatement={thread.root} replies={thread.replies} defaultExpanded={defaultExpanded} onStatementClick={onStatementClick} /> ))} </div> ); }); /** * Hook to determine if statements should be threaded */ export function useThreadedStatements(statements: Statement[]) { const hasThreading = useMemo( () => statements.some((s) => s.thread_id || s.parent_statement_id), [statements] ); return { hasThreading, canThread: statements.length > 0, }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/northernvariables/FedMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server