Files

252 lines
12 KiB
TypeScript

/* eslint-disable */
// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential.
"use client";
import { useEffect, useState, useRef } from "react";
import { XCUTelepathyMatrix, DecryptedMessage } from "../lib/xcu-telepathy-matrix";
export function XCUltraChat({ roomName, username }: { roomName: string, username: string }) {
const [logs, setLogs] = useState<string[]>([]);
const [isMatrixActive, setIsMatrixActive] = useState(false);
const [chatInput, setChatInput] = useState("");
const [chatMessages, setChatMessages] = useState<{id: string, sender: string, text: string, time: string}[]>([]);
const [typingUsers, setTypingUsers] = useState<string[]>([]);
const [activeReactions, setActiveReactions] = useState<{id: number, type: string, ts: number}[]>([]);
const chatEndRef = useRef<HTMLDivElement>(null);
const matrixRef = useRef<XCUTelepathyMatrix | null>(null);
const addLog = (msg: string) => {
setLogs(prev => [...prev, msg]);
};
const setStatus = (s: string) => addLog(`[SYS] ${s}`);
useEffect(() => {
if (chatEndRef.current) {
chatEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [chatMessages, typingUsers]);
useEffect(() => {
let matrix: XCUTelepathyMatrix | null = null;
const initMatrix = async () => {
try {
setStatus("Memulai Koneksi XCU Telepathy Matrix (XTM) E2EE...");
matrix = new XCUTelepathyMatrix(roomName);
matrixRef.current = matrix;
matrix.onMessagesUpdate = (messages: DecryptedMessage[]) => {
// Format time based on timestamp
const formatted = messages.map(m => {
const date = new Date(m.timestamp);
return {
id: m.id,
sender: m.sender,
text: m.content,
time: date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
};
});
// CRDT arrays are append-only mostly for chat, but we sync everything
setChatMessages(formatted);
};
matrix.onTypingUpdate = (typings: Record<string, number>) => {
const active = Object.keys(typings).filter(key => key !== username);
setTypingUsers(active);
};
// PKEPX Zoom-Killer Handlers
matrix.onQuantumResonance = (id, type) => {
setActiveReactions(prev => [...prev, { id, type, ts: Date.now() }]);
setTimeout(() => {
setActiveReactions(prev => prev.filter(r => Date.now() - r.ts < 4000));
}, 4000);
};
matrix.onSovereignSignal = (command, targetId) => {
if (command === 'MUTE_ALL') {
setStatus("Host telah mengunci matriks komunikasi (Sovereign Lock).");
}
};
// Determine server URL dynamically for backup WebSocket
const proto = window.location.protocol;
const host = window.location.host;
let serverUrl = `${proto}//${host}`;
if (typeof window === 'undefined' || host === '') {
serverUrl = 'http://localhost:4003';
}
matrix.ignite(serverUrl, username);
setIsMatrixActive(true);
setStatus("XTM CRDT Engine Siap. Enkripsi AES-256 Aktif.");
} catch (e: any) {
console.error("XTM ERROR DETAILS:", e, e.name, e.message);
setStatus("Koneksi Gagal: " + e.name + " - " + e.message);
}
};
initMatrix();
return () => {
// Dihapus untuk mencegah React 18 race condition yang memotong koneksi QUIC secara prematur
if (matrixRef.current) {
// matrixRef.current.shutdown();
}
};
}, []);
// (Moved setStatus up)
const handleSendChat = async (e?: React.FormEvent) => {
if (e) e.preventDefault();
if (!chatInput.trim() || !matrixRef.current) return;
const currentInput = chatInput;
// XTM Send Message (Automatically encrypted and synced via CRDT)
matrixRef.current.sendMessage(username, currentInput);
setChatInput("");
// FASE 87: OMNIBRAIN AVATAR PROTOCOL (CLIENT-SIDE TELEPATHY)
if (currentInput.includes('@OmniBrain')) {
// The browser acts as the medium for the AI to preserve E2EE
matrixRef.current.sendMessage('OmniBrain', 'Sedang mengurai matriks kuantum...');
try {
// Take last 5 decrypted messages for context
const recentHistory = chatMessages.slice(-5);
const res = await fetch('/api/omnibrain', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
history: recentHistory,
prompt: currentInput.replace('@OmniBrain', '').trim(),
sender: username
})
});
const data = await res.json();
if (data.reply && matrixRef.current) {
matrixRef.current.sendMessage('OmniBrain', data.reply);
}
} catch (err) {
if (matrixRef.current) {
matrixRef.current.sendMessage('OmniBrain', 'Gagal memanggil entitas di peladen pusat.');
}
}
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value;
setChatInput(val);
if (matrixRef.current && val.length > 0) {
matrixRef.current.setTyping(username, val.length);
}
};
return (
<div className="h-dvh w-full bg-[#0a0c10] text-slate-200 font-sans flex flex-col overflow-hidden relative">
{/* Top Banner */}
<div className="h-16 border-b border-white/5 bg-[#14161f] flex items-center justify-between px-6 shadow-md z-20 shrink-0">
<div className="flex items-center gap-4">
<div className="w-10 h-10 bg-gradient-to-br from-cyan-600 to-blue-700 rounded-xl flex items-center justify-center text-white shadow-[0_0_15px_rgba(8,145,178,0.4)]">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"></path></svg>
</div>
<div>
<h1 className="text-base font-bold tracking-wider uppercase text-cyan-400">XCU OMNI CHAT</h1>
<p className="text-xs text-slate-500 font-mono">{roomName}</p>
</div>
</div>
<div className="flex items-center gap-4 text-xs font-medium">
{/* PKEPX Resonance Button */}
<button onClick={() => { if(matrixRef.current) matrixRef.current.emitResonance('👍'); }} className="p-2 bg-yellow-500/10 hover:bg-yellow-500/20 text-yellow-500 rounded-lg transition-colors border border-yellow-500/20 shadow-[0_0_10px_rgba(234,179,8,0.2)]">
👍 React
</button>
<span className="bg-cyan-900/30 text-cyan-400 px-3 py-1.5 rounded-lg border border-cyan-500/30 flex items-center gap-2 shadow-[0_0_10px_rgba(8,145,178,0.2)]">
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
CRDT QUANTUM ENGINE
</span>
</div>
</div>
{/* PKEPX Floating Emojis */}
{activeReactions.map(r => (
<div key={r.ts} className="absolute inset-0 flex items-center justify-center pointer-events-none z-[100] animate-[ping_1s_ease-out]">
<span className="text-6xl drop-shadow-[0_0_30px_rgba(255,255,255,0.5)] animate-bounce">{r.type}</span>
</div>
))}
{!isMatrixActive ? (
<div className="flex-1 flex flex-col items-center justify-center gap-4 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-slate-900 to-[#0a0c10]">
<div className="w-16 h-16 border-4 border-slate-800 border-t-cyan-500 rounded-full animate-spin shadow-[0_0_30px_rgba(8,145,178,0.5)]"></div>
<div className="text-sm font-mono text-cyan-500 animate-pulse tracking-widest">{logs[logs.length-1] || "INisialisasi..."}</div>
</div>
) : (
<div className="flex-1 flex flex-col bg-transparent overflow-hidden">
<div className="flex-1 overflow-y-auto p-6 space-y-6 custom-scroll">
{chatMessages.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-slate-600 opacity-50">
<svg className="w-16 h-16 mb-4 text-cyan-900" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
<div className="text-center text-sm font-mono tracking-widest uppercase">P2P Kuantum Siap. 0ms Latensi.</div>
</div>
) : (
chatMessages.map(msg => {
const isMe = msg.sender === username;
return (
<div key={msg.id} className={`flex flex-col ${isMe ? 'items-end' : 'items-start'} animate-in fade-in slide-in-from-bottom-2`}>
<div className={`px-5 py-3 rounded-2xl max-w-[80%] text-[15px] leading-relaxed shadow-lg ${isMe ? 'bg-gradient-to-br from-cyan-600 to-blue-700 text-white rounded-br-sm shadow-cyan-900/20' : 'bg-[#1a1d24] text-slate-200 border border-white/5 rounded-bl-sm'}`}>
<div className="text-[10px] opacity-60 mb-1.5 font-mono tracking-wider uppercase flex items-center gap-2">
{isMe ? 'Anda' : msg.sender}
<span className="w-1 h-1 bg-white/30 rounded-full"></span>
{msg.time}
</div>
<p className="break-words">{msg.text}</p>
</div>
</div>
);
})
)}
{/* FASE 86: XTM Typing Indicator */}
{typingUsers.length > 0 && (
<div className="text-xs text-cyan-500 font-mono tracking-widest px-2 animate-pulse mt-1 mb-2">
{typingUsers.join(", ")} sedang merajut matriks teks...
</div>
)}
<div ref={chatEndRef} />
</div>
<div className="p-5 border-t border-white/5 bg-[#14161f] shrink-0">
<form onSubmit={handleSendChat} className="flex items-center gap-3">
<div className="flex-1 bg-[#0a0c10] rounded-xl px-5 py-3 border border-white/10 focus-within:border-cyan-500/50 focus-within:shadow-[0_0_15px_rgba(8,145,178,0.2)] transition-all flex items-center">
<input
type="text"
value={chatInput}
onChange={handleInputChange}
className="flex-1 bg-transparent text-sm text-slate-200 outline-none placeholder:text-slate-600"
placeholder="Transmisikan matriks teks E2EE..."
autoFocus
/>
</div>
<button title="Aksi" type="submit" disabled={!chatInput.trim()} className="bg-cyan-600 hover:bg-cyan-500 text-white p-3.5 rounded-xl disabled:opacity-50 disabled:cursor-not-allowed transition-all shadow-[0_0_15px_rgba(8,145,178,0.3)] flex items-center justify-center hover:scale-105 active:scale-95">
<svg className="w-5 h-5 translate-x-0.5" fill="currentColor" viewBox="0 0 20 20"><path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z"></path></svg>
</button>
</form>
</div>
</div>
)}
<style dangerouslySetInnerHTML={{__html: `
.custom-scroll::-webkit-scrollbar { width: 6px; }
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
.custom-scroll::-webkit-scrollbar-thumb { background: rgba(34, 211, 238, 0.2); border-radius: 6px; }
.custom-scroll::-webkit-scrollbar-thumb:hover { background: rgba(34, 211, 238, 0.4); }
`}} />
</div>
);
}