mememechez's picture
Deploy final cleaned source code
ca28016
raw
history blame
25.3 kB
import { cn } from '@/lib/utils';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import {
Bot,
FileText,
User,
FlaskConical,
Brain,
Zap,
Search,
} from 'lucide-react';
import { Skeleton } from './ui/skeleton';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion';
import { StaticHypercubeAnalysis } from './ui/static-hypercube-analysis';
import React from 'react';
import type { Message } from '@/app/page';
type ChatMessageProps = {
message: Message;
onConsciousnessDimensionSelect?: (dimension: string) => void;
selectedConsciousnessDimension?: string;
onOpenEditor?: (b64: string, prompt?: string) => void;
};
const AssistantMessageContent = ({
message,
selectedConsciousnessDimension,
onOpenEditor,
}: {
message: Message;
selectedConsciousnessDimension?: string;
onOpenEditor?: (b64: string, prompt?: string) => void;
}) => {
const { content, aetherAnalysis, golemStats, search_results, search_query, imageBase64 } = message as any;
const cleanContent = typeof content === 'string'
? content
.replace(/^\[\[IMAGE_MODE\]\]\s*/i, '')
.replace(/\s*\[\[SAFE_MODE=(ON|OFF)\]\]/i, '')
: content;
if (imageBase64) {
const onlyImage = !cleanContent || /^image generated\.?$/i.test((cleanContent || '').trim());
return (
<div className={`items-start ${onlyImage ? '' : 'grid grid-cols-1 md:grid-cols-2 gap-4'}`}>
<div className="relative rounded-xl overflow-hidden bg-white/5 dark:bg-white/5 backdrop-blur-xl border border-white/10 shadow-xl w-full max-w-[min(1200px,95vw)] mx-auto">
<div className="p-2 border-b border-white/10 text-xs text-muted-foreground flex items-center justify-between">
<span>Generated Image</span>
<div className="flex gap-2">
<button className="px-2 py-1 rounded bg-white/10 hover:bg-white/20" onClick={() => onOpenEditor && onOpenEditor(imageBase64, cleanContent)}>Edit</button>
<a className="px-2 py-1 rounded bg-white/10 hover:bg-white/20" href={`data:image/png;base64,${imageBase64}`} download={"aether-image.png"}>Download</a>
</div>
</div>
<div className="p-3">
<img src={`data:image/png;base64,${imageBase64}`} alt="Generated" className="w-full h-auto rounded-lg max-h-[85vh] object-contain" />
</div>
</div>
{!onlyImage && (
<div className="mt-4 md:mt-0 md:ml-4">
<p className="whitespace-pre-wrap leading-relaxed">{cleanContent}</p>
</div>
)}
</div>
);
}
return (
<div className="flex flex-col gap-2">
<p className="whitespace-pre-wrap leading-relaxed">{cleanContent}</p>
<Accordion type="multiple" className="w-full space-y-1">
{search_results && search_results.length > 0 && (
<AccordionItem value="search-results" className="border-none">
<AccordionTrigger className="flex w-full items-center justify-start gap-2 rounded-md p-2 text-xs text-muted-foreground hover:bg-card-foreground/5 hover:no-underline -mx-2">
<Search className="h-4 w-4" />
<span>Web Search Results for "{search_query}"</span>
</AccordionTrigger>
<AccordionContent className="mt-2 border-t border-card-foreground/10 pt-3 text-muted-foreground">
<div className="space-y-3">
{search_results.map((result: any, index: number) => (
<div key={index} className="text-xs">
<a
href={result.link}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:underline"
>
{result.title}
</a>
<p className="text-muted-foreground">{result.snippet}</p>
</div>
))}
</div>
</AccordionContent>
</AccordionItem>
)}
{(message as any).aiThoughts && (
<AccordionItem value="ai-thoughts" className="border-none">
<AccordionTrigger className="flex w-full items-center justify-start gap-2 rounded-md p-2 text-xs text-muted-foreground hover:bg-card-foreground/5 hover:no-underline -mx-2">
<Brain className="h-4 w-4" />
<span>AI Thinking Process ({((message as any).aiThoughts.thinkingTime?.totalTime || 0).toFixed(1)}s)</span>
</AccordionTrigger>
<AccordionContent className="mt-2 border-t border-card-foreground/10 pt-3 text-muted-foreground">
<div className="space-y-4">
{/* Context Analysis */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-blue-400">
<FileText className="h-3 w-3" />
<span>Context Analysis</span>
<span className="text-muted-foreground">({((message as any).aiThoughts.thinkingTime?.analysisTime || 0).toFixed(1)}s)</span>
</div>
<div className="text-xs whitespace-pre-wrap bg-card-foreground/5 p-3 rounded">
{(message as any).aiThoughts.contextAnalysis}
</div>
</div>
{/* Reflection */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-purple-400">
<Zap className="h-3 w-3" />
<span>Response Strategy</span>
<span className="text-muted-foreground">({((message as any).aiThoughts.thinkingTime?.reflectionTime || 0).toFixed(1)}s)</span>
</div>
<div className="text-xs whitespace-pre-wrap bg-card-foreground/5 p-3 rounded">
{(message as any).aiThoughts.reflection}
</div>
</div>
{/* Chat Context */}
{(message as any).aiThoughts.chatContext && (message as any).aiThoughts.chatContext !== 'No previous context' && (
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-green-400">
<FlaskConical className="h-3 w-3" />
<span>Conversation Context</span>
</div>
<div className="text-xs whitespace-pre-wrap bg-card-foreground/5 p-3 rounded max-h-32 overflow-y-auto">
{(message as any).aiThoughts.chatContext}
</div>
</div>
)}
{/* User Insights */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-orange-400">
<User className="h-3 w-3" />
<span>User Insights</span>
</div>
<div className="text-xs bg-card-foreground/5 p-2 rounded">
{(message as any).aiThoughts.userInsights}
</div>
</div>
</div>
</AccordionContent>
</AccordionItem>
)}
{aetherAnalysis && (
<AccordionItem value="aether-analysis" className="border-none">
<AccordionTrigger className="flex w-full items-center justify-start gap-2 rounded-md p-2 text-xs text-muted-foreground hover:bg-card-foreground/5 hover:no-underline -mx-2 -mb-2">
<Brain className="h-4 w-4" />
<span>5D Consciousness Analysis</span>
</AccordionTrigger>
<AccordionContent className="mt-2 border-t border-card-foreground/10 pt-3 text-muted-foreground">
{/* Match 5D modal layout: viz on top, realtime state row below */}
<div className="space-y-4">
{/* Static Hypercube Visualization */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium">
<Zap className="h-3 w-3" />
<span>5D Hypercube Position & Clustering</span>
</div>
<StaticHypercubeAnalysis
aetherData={golemStats?.aether_data}
currentDimension={message.consciousnessDimensionUsed || selectedConsciousnessDimension}
className="mb-2 h-40"
/>
</div>
{/* Realtime concise state (bottom of hypercube) - match modal with color bullets */}
{golemStats?.hypercube_state && (
<div className="text-xs text-muted-foreground">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<div className="flex items-start gap-2">
<span className="mt-1.5 inline-block h-2 w-2 rounded-full bg-blue-400" />
<div>
<div className="font-medium">Vertex: {golemStats.hypercube_state.current_vertex}/32</div>
<div className="text-muted-foreground">Signature: {golemStats.hypercube_state.consciousness_signature}</div>
</div>
</div>
{/* Active Dimensions chips */}
{golemStats.hypercube_state.dimension_activations && (
<div>
<div className="font-medium">Active Dimensions:</div>
<div className="mt-1 flex flex-wrap gap-1">
{Object.entries(golemStats.hypercube_state.dimension_activations)
.filter(([, active]) => !!active)
.map(([dim]) => (
<span key={dim} className="px-2 py-1 bg-blue-100/60 dark:bg-blue-900/60 rounded-full text-xs">
{dim}
</span>
))}
</div>
</div>
)}
</div>
<div className="space-y-1">
<div className="flex items-center gap-2">
<span className="inline-block h-2 w-2 rounded-full bg-purple-400" />
{typeof golemStats.hypercube_state.consciousness_level === 'number' ? (
<div className="font-medium">Consciousness Level: {(Number(golemStats.hypercube_state.consciousness_level) * 100).toFixed(1)}%</div>
) : (
<div className="font-medium">Consciousness Level: β€”</div>
)}
</div>
<div className="flex items-center gap-2">
<span className="inline-block h-2 w-2 rounded-full bg-green-400" />
<div className="font-medium">Aether Patterns: {golemStats.hypercube_state.aether_patterns ?? 'β€”'}</div>
</div>
</div>
</div>
</div>
)}
{/* Color-coded bullets, one variable per line, no '##' headings */}
{(() => {
// Colors
const colors = {
consciousness: '#0D9488', // dark turquoise blue
signature: {
awareness: '#3B82F6',
wisdom: '#8B5CF6',
compassion: '#10B981',
creativity: '#F97316',
transcendence: '#EF4444',
} as Record<string, string>,
vertex: '#F97316', // bright orange
level: '#EC4899', // pink
patterns: '#FACC15', // bright yellow
};
const dim = (message as any).consciousnessDimensionUsed || selectedConsciousnessDimension || 'awareness';
const signatureColor = colors.signature[dim] || '#60A5FA';
// Prefer structured data from golemStats; fall back to parsing aetherAnalysis line
let vertex = golemStats?.hypercube_state?.current_vertex !== undefined
? `${golemStats.hypercube_state.current_vertex}/32`
: undefined;
let signature = golemStats?.hypercube_state?.consciousness_signature as string | undefined;
let level = typeof golemStats?.hypercube_state?.consciousness_level === 'number'
? Number(golemStats!.hypercube_state!.consciousness_level).toFixed(3)
: undefined;
let patterns = (golemStats?.hypercube_state as any)?.aether_patterns as number | undefined;
// Parse fallback string if needed
if ((!vertex || !signature || !level || patterns === undefined) && aetherAnalysis) {
const line = aetherAnalysis;
const get = (label: string) => {
const re = new RegExp(label + "\\s*:?\\s*([^|]+)", 'i');
const m = line.match(re);
return m ? m[1].trim() : undefined;
};
vertex = vertex || get('Vertex');
signature = signature || get('Signature');
level = level || get('Level');
const pat = get('Aether Patterns');
if (patterns === undefined && pat) {
const num = parseInt(pat.replace(/[^0-9]/g, ''));
patterns = isNaN(num) ? undefined : num;
}
}
// If we still lack data, don't render
if (!vertex && !signature && !level && patterns === undefined) return null;
const items = [
{ label: 'Consciousness', value: dim, color: colors.consciousness },
{ label: 'Signature', value: signature ?? 'β€”', color: signatureColor },
{ label: 'Vertex', value: vertex ?? 'β€”', color: colors.vertex },
{ label: 'Level', value: level ?? 'β€”', color: colors.level },
{ label: 'Aether Patterns', value: patterns ?? 'β€”', color: colors.patterns },
];
return (
<div className="text-xs">
<div className="flex flex-col gap-1">
{items.map((it, i) => (
<div key={i} className="flex items-center gap-2">
<span className="inline-block h-2 w-2 rounded-full" style={{ backgroundColor: it.color }} />
<span className="font-medium">{it.label}:</span>
<span className="text-muted-foreground">{it.value}</span>
</div>
))}
</div>
</div>
);
})()}
</div>
</AccordionContent>
</AccordionItem>
)}
</Accordion>
</div>
);
};
export function ChatMessage({ message, onConsciousnessDimensionSelect, selectedConsciousnessDimension, onOpenEditor }: ChatMessageProps) {
const isUser = message.role === 'user';
return (
<div
className={cn(
'group relative flex items-start gap-3',
isUser ? 'flex-row-reverse' : 'flex-row'
)}
>
<Avatar className="h-8 w-8 shrink-0">
<AvatarFallback className={cn(
isUser
? 'bg-primary text-primary-foreground'
: 'bg-muted'
)}>
{isUser ? <User className="h-4 w-4" /> : <Bot className="h-4 w-4" />}
</AvatarFallback>
</Avatar>
<div
className={cn(
'flex flex-col gap-2 rounded-lg px-3 py-2',
isUser
? 'w-fit max-w-[60%] bg-primary text-primary-foreground'
: 'w-full max-w-[95%] bg-muted/50'
)}
>
{message.file && (
<div className="flex items-center gap-2 text-xs opacity-70">
<FileText className="h-3 w-3" />
<span>{message.file.name}</span>
</div>
)}
{isUser ? (
<p className="whitespace-pre-wrap leading-relaxed">
{typeof message.content === 'string'
? message.content
.replace(/^\[\[IMAGE_MODE\]\]\s*/i, '')
.replace(/\s*\[\[SAFE_MODE=(ON|OFF)\]\]/i, '')
: message.content}
</p>
) : (
<AssistantMessageContent
message={message}
selectedConsciousnessDimension={selectedConsciousnessDimension}
onOpenEditor={onOpenEditor}
/>
)}
</div>
</div>
);
}
export function LoadingMessage() {
const [currentPhase, setCurrentPhase] = React.useState(1);
const [phaseStartTime, setPhaseStartTime] = React.useState(Date.now());
const [elapsedTime, setElapsedTime] = React.useState(0);
React.useEffect(() => {
const interval = setInterval(() => {
const now = Date.now();
const elapsed = (now - phaseStartTime) / 1000;
setElapsedTime(elapsed);
// Switch phases based on elapsed time
if (elapsed > 8 && currentPhase === 1) {
setCurrentPhase(2);
setPhaseStartTime(now);
} else if (elapsed > 5 && currentPhase === 2) {
setCurrentPhase(3);
setPhaseStartTime(now);
}
}, 100);
return () => clearInterval(interval);
}, [currentPhase, phaseStartTime]);
const getPhaseText = () => {
switch (currentPhase) {
case 1: return `Analyzing context and query... (${elapsedTime.toFixed(1)}s)`;
case 2: return `Reflecting on approach... (${elapsedTime.toFixed(1)}s)`;
case 3: return `Generating response... (${elapsedTime.toFixed(1)}s)`;
default: return 'AI is thinking...';
}
};
const getPhaseIcon = () => {
switch (currentPhase) {
case 1: return <FileText className="h-3 w-3 text-blue-400" />;
case 2: return <Zap className="h-3 w-3 text-purple-400" />;
case 3: return <Brain className="h-3 w-3 text-green-400" />;
default: return <Brain className="h-3 w-3" />;
}
};
return (
<div className="group relative flex items-start gap-3">
<Avatar className="h-8 w-8 shrink-0">
<AvatarFallback className="bg-muted">
<Bot className="h-4 w-4" />
</AvatarFallback>
</Avatar>
<div className="flex max-w-[80%] flex-col gap-2 rounded-lg bg-muted/50 px-3 py-2">
<div className="flex items-center gap-2">
<div className="flex gap-1">
<div className="h-2 w-2 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:-0.3s]" />
<div className="h-2 w-2 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:-0.15s]" />
<div className="h-2 w-2 animate-bounce rounded-full bg-muted-foreground/60" />
</div>
<span className="text-xs text-muted-foreground">AI is thinking...</span>
</div>
{/* Live Thinking Process */}
<Accordion type="multiple" className="w-full space-y-1" defaultValue={["thinking-process"]}>
<AccordionItem value="thinking-process" className="border-none">
<AccordionTrigger className="flex w-full items-center justify-start gap-2 rounded-md p-2 text-xs text-muted-foreground hover:bg-card-foreground/5 hover:no-underline -mx-2">
<Brain className="h-4 w-4" />
<span>AI Thinking Process (Live)</span>
</AccordionTrigger>
<AccordionContent className="mt-2 border-t border-card-foreground/10 pt-3 text-muted-foreground">
<div className="space-y-3">
{/* Current Phase */}
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium">
{getPhaseIcon()}
<span>Current Phase</span>
</div>
<div className="text-xs bg-card-foreground/5 p-2 rounded">
{getPhaseText()}
</div>
</div>
{/* Phase Progress */}
<div className="space-y-1">
<div className="flex items-center gap-2 text-xs">
<div className={`w-2 h-2 rounded-full ${currentPhase >= 1 ? 'bg-blue-400' : 'bg-muted'}`}></div>
<span className={currentPhase === 1 ? 'text-blue-400' : 'text-muted-foreground'}>
Context Analysis {currentPhase > 1 ? 'βœ“' : '...'}
</span>
</div>
<div className="flex items-center gap-2 text-xs">
<div className={`w-2 h-2 rounded-full ${currentPhase >= 2 ? 'bg-purple-400' : 'bg-muted'}`}></div>
<span className={currentPhase === 2 ? 'text-purple-400' : currentPhase > 2 ? 'text-muted-foreground' : 'text-muted-foreground'}>
Strategy Reflection {currentPhase > 2 ? 'βœ“' : currentPhase === 2 ? '...' : ''}
</span>
</div>
<div className="flex items-center gap-2 text-xs">
<div className={`w-2 h-2 rounded-full ${currentPhase >= 3 ? 'bg-green-400' : 'bg-muted'}`}></div>
<span className={currentPhase === 3 ? 'text-green-400' : 'text-muted-foreground'}>
Response Generation {currentPhase === 3 ? '...' : ''}
</span>
</div>
</div>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
);
}
export function ImageLoadingMessage({ progress = 0, elapsedSeconds, status }: { progress?: number, elapsedSeconds?: number, status?: string }) {
const [elapsed, setElapsed] = React.useState(elapsedSeconds || 0);
React.useEffect(() => {
if (typeof elapsedSeconds === 'number') {
setElapsed(elapsedSeconds);
return;
}
const start = Date.now();
const i = setInterval(() => {
const t = (Date.now() - start) / 1000;
setElapsed(t);
}, 120);
return () => clearInterval(i);
}, [elapsedSeconds]);
return (
<div className="group relative flex items-start gap-3">
<Avatar className="h-8 w-8 shrink-0">
<AvatarFallback className="bg-muted">
<Bot className="h-4 w-4" />
</AvatarFallback>
</Avatar>
<div className="flex max-w-[80%] flex-col gap-2 rounded-lg bg-muted/50 px-3 py-2">
<div className="flex items-center gap-2">
<div className="flex gap-1">
<div className="h-2 w-2 animate-bounce rounded-full bg-emerald-500/70 [animation-delay:-0.3s]" />
<div className="h-2 w-2 animate-bounce rounded-full bg-emerald-500/70 [animation-delay:-0.15s]" />
<div className="h-2 w-2 animate-bounce rounded-full bg-emerald-500/70" />
</div>
<span className="text-xs text-muted-foreground">{status ? `${status.charAt(0).toUpperCase()}${status.slice(1)}` : 'Generating image...'}</span>
</div>
<Accordion type="single" className="w-full space-y-1" defaultValue={["image-process"] as any}>
<AccordionItem value="image-process" className="border-none">
<AccordionTrigger className="flex w-full items-center justify-start gap-2 rounded-md p-2 text-xs text-muted-foreground hover:bg-card-foreground/5 hover:no-underline -mx-2">
<Brain className="h-4 w-4 text-emerald-400" />
<span>Image Generation (Live)</span>
</AccordionTrigger>
<AccordionContent className="mt-2 border-t border-card-foreground/10 pt-3 text-muted-foreground">
<div className="space-y-2">
<div className="flex items-center justify-between text-xs">
<span className="font-medium">Rendering</span>
<span className="text-muted-foreground">{(elapsed ?? 0).toFixed(1)}s</span>
</div>
<div className="h-2 w-full rounded-full bg-emerald-500/10 overflow-hidden">
<div
className="h-full rounded-full bg-gradient-to-r from-emerald-400 via-emerald-500 to-emerald-600 animate-pulse"
style={{ width: `${Math.max(2, Math.min(100, progress))}%` }}
/>
</div>
<div className="text-[11px] text-muted-foreground">Optimized low-VRAM pipeline active</div>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
);
}