cre-sync/components/control-center/ToolResultCard.tsx
BusyBee3333 4e6467ffb0 Add CRESync CRM application with Setup page
- Build complete Next.js CRM for commercial real estate
- Add authentication with JWT sessions and role-based access
- Add GoHighLevel API integration for contacts, conversations, opportunities
- Add AI-powered Control Center with tool calling
- Add Setup page with onboarding checklist (/setup)
- Add sidebar navigation with Setup menu item
- Fix type errors in onboarding API, GHL services, and control center tools
- Add Prisma schema with SQLite for local development
- Add UI components with clay morphism design system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:30:55 -05:00

137 lines
4.2 KiB
TypeScript

'use client';
import React, { useState } from 'react';
import { CheckCircle, XCircle, ChevronDown, ChevronUp, FileCode } from 'lucide-react';
import type { ToolResult } from '@/types/control-center';
import { cn } from '@/lib/utils';
interface ToolResultCardProps {
toolResult: ToolResult;
}
// Threshold for auto-collapsing large results
const LARGE_RESULT_THRESHOLD = 500;
export const ToolResultCard: React.FC<ToolResultCardProps> = ({ toolResult }) => {
const isError = !toolResult.success;
const resultData = isError ? toolResult.error : toolResult.result;
// Determine if result is large and should be collapsed by default
const resultString = typeof resultData === 'string'
? resultData
: JSON.stringify(resultData, null, 2);
const isLargeResult = resultString.length > LARGE_RESULT_THRESHOLD;
const [isExpanded, setIsExpanded] = useState(!isLargeResult);
const formatResult = (data: any): string => {
if (data === null || data === undefined) {
return 'No result data';
}
if (typeof data === 'string') {
return data;
}
return JSON.stringify(data, null, 2);
};
return (
<div
className={cn(
'bg-[#E8EDF2] rounded-2xl border-2',
'shadow-[4px_4px_8px_#c5c9d1,-4px_-4px_8px_#ffffff]',
'transition-all duration-300',
isError ? 'border-danger-200' : 'border-success-200'
)}
>
{/* Header */}
<div
className="flex items-center gap-3 p-4 cursor-pointer"
onClick={() => setIsExpanded(!isExpanded)}
>
{/* Status Icon */}
<div
className={cn(
'p-2.5 rounded-xl',
isError
? 'bg-danger-100 text-danger-600'
: 'bg-success-100 text-success-600'
)}
>
{isError ? <XCircle size={18} /> : <CheckCircle size={18} />}
</div>
{/* Result Info */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<FileCode size={14} className="text-gray-400" />
<span className="text-sm font-medium text-gray-600">
Tool Result
</span>
</div>
<div className="flex items-center gap-2 mt-0.5">
<span
className={cn(
'text-xs font-semibold px-2 py-0.5 rounded-full',
isError
? 'bg-danger-100 text-danger-700'
: 'bg-success-100 text-success-700'
)}
>
{isError ? 'Error' : 'Success'}
</span>
{!isExpanded && (
<span className="text-xs text-gray-500 truncate">
{resultString.substring(0, 50)}
{resultString.length > 50 ? '...' : ''}
</span>
)}
</div>
</div>
{/* Expand/Collapse Toggle */}
<button
className={cn(
'p-1.5 rounded-lg transition-colors',
'hover:bg-gray-200 text-gray-500'
)}
aria-label={isExpanded ? 'Collapse result' : 'Expand result'}
>
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</button>
</div>
{/* Collapsible Result Content */}
{isExpanded && (
<div className="px-4 pb-4">
<div
className={cn(
'rounded-xl p-3',
'shadow-[inset_2px_2px_4px_rgba(0,0,0,0.05),inset_-2px_-2px_4px_rgba(255,255,255,0.7)]',
isError ? 'bg-danger-50' : 'bg-[#F5F8FA]'
)}
>
<p
className={cn(
'text-xs font-medium uppercase tracking-wide mb-2',
isError ? 'text-danger-600' : 'text-gray-500'
)}
>
{isError ? 'Error Details' : 'Result Data'}
</p>
<pre
className={cn(
'text-xs overflow-x-auto whitespace-pre-wrap break-words font-mono max-h-64 overflow-y-auto',
isError ? 'text-danger-700' : 'text-gray-700'
)}
>
{formatResult(resultData)}
</pre>
</div>
</div>
)}
</div>
);
};
export default ToolResultCard;