cre-sync/components/control-center/ChatInterface.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

207 lines
5.5 KiB
TypeScript

'use client';
import React, { useCallback } from 'react';
import { cn } from '@/lib/utils';
import { useChatContext } from './ChatProvider';
import { MessageList } from './MessageList';
import { ChatComposer } from './ChatComposer';
import { Sparkles, AlertCircle, X } from 'lucide-react';
// =============================================================================
// Types
// =============================================================================
interface ChatInterfaceProps {
className?: string;
}
// =============================================================================
// Status Indicator Component
// =============================================================================
interface StatusIndicatorProps {
status: 'idle' | 'loading' | 'streaming' | 'error';
}
function StatusIndicator({ status }: StatusIndicatorProps) {
const statusConfig = {
idle: {
color: 'bg-emerald-400',
pulse: false,
label: 'Ready',
},
loading: {
color: 'bg-amber-400',
pulse: true,
label: 'Loading',
},
streaming: {
color: 'bg-indigo-400',
pulse: true,
label: 'Responding',
},
error: {
color: 'bg-red-400',
pulse: false,
label: 'Error',
},
};
const config = statusConfig[status];
return (
<div className="flex items-center gap-2">
<span
className={cn(
'w-2.5 h-2.5 rounded-full',
config.color,
config.pulse && 'animate-pulse'
)}
/>
<span className="text-xs text-gray-500 font-medium">{config.label}</span>
</div>
);
}
// =============================================================================
// Error Banner Component
// =============================================================================
interface ErrorBannerProps {
message: string;
onDismiss: () => void;
}
function ErrorBanner({ message, onDismiss }: ErrorBannerProps) {
return (
<div
className={cn(
'flex items-center gap-3 px-4 py-3 mx-4 mt-4 rounded-xl',
'bg-red-50 border border-red-200',
'shadow-[inset_2px_2px_4px_rgba(0,0,0,0.03),inset_-2px_-2px_4px_rgba(255,255,255,0.5)]'
)}
>
<AlertCircle className="w-5 h-5 text-red-500 flex-shrink-0" />
<p className="flex-1 text-sm text-red-700">{message}</p>
<button
onClick={onDismiss}
className={cn(
'p-1 rounded-lg text-red-400 hover:text-red-600',
'hover:bg-red-100 transition-colors'
)}
aria-label="Dismiss error"
>
<X className="w-4 h-4" />
</button>
</div>
);
}
// =============================================================================
// Header Component
// =============================================================================
interface HeaderProps {
status: 'idle' | 'loading' | 'streaming' | 'error';
}
function Header({ status }: HeaderProps) {
return (
<header
className={cn(
'flex items-center justify-between px-6 py-4',
'border-b border-gray-200/60',
'bg-gradient-to-r from-[#F0F4F8] to-[#E8ECF0]',
'shadow-[inset_0_-2px_4px_rgba(0,0,0,0.02)]'
)}
>
<div className="flex items-center gap-3">
<div
className={cn(
'w-10 h-10 rounded-xl flex items-center justify-center',
'bg-gradient-to-br from-indigo-500 to-purple-600',
'shadow-[4px_4px_8px_#bfc3cc,-4px_-4px_8px_#ffffff]'
)}
>
<Sparkles className="w-5 h-5 text-white" />
</div>
<div>
<h1 className="text-lg font-bold text-gray-800">Control Center</h1>
<p className="text-xs text-gray-500">AI-powered assistant</p>
</div>
</div>
<StatusIndicator status={status} />
</header>
);
}
// =============================================================================
// Main Component
// =============================================================================
export function ChatInterface({ className }: ChatInterfaceProps) {
const {
isLoading,
isStreaming,
error,
clearError,
sendMessage,
} = useChatContext();
// Determine current status
const getStatus = (): 'idle' | 'loading' | 'streaming' | 'error' => {
if (error) return 'error';
if (isStreaming) return 'streaming';
if (isLoading) return 'loading';
return 'idle';
};
// Handle message sending from ChatComposer
const handleSend = useCallback((message: string, provider?: string) => {
sendMessage(message, provider);
}, [sendMessage]);
return (
<div
className={cn(
'flex flex-col h-full w-full',
'bg-[#F0F4F8]',
'rounded-3xl overflow-hidden',
'shadow-[8px_8px_16px_#b8bcc5,-8px_-8px_16px_#ffffff]',
'border-2 border-white/50',
className
)}
>
{/* Header */}
<Header status={getStatus()} />
{/* Error Banner */}
{error && (
<ErrorBanner message={error} onDismiss={clearError} />
)}
{/* Message List */}
<div className="flex-1 min-h-0 overflow-hidden flex flex-col">
<MessageList />
</div>
{/* Chat Composer */}
<div
className={cn(
'px-4 py-4 border-t border-gray-200/60',
'bg-gradient-to-r from-[#F0F4F8] to-[#E8ECF0]'
)}
>
<ChatComposer
onSend={handleSend}
disabled={isLoading}
isStreaming={isStreaming}
/>
</div>
</div>
);
}
export default ChatInterface;