Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | "use client"; import React, { useEffect, useMemo, useState } from "react"; import { motion } from "framer-motion"; import { useSpeechRecognition } from "@neurals/hooks/useSpeechRecognition"; import { speak, stopSpeak } from "@neurals/audio/tts"; import { handleTranscript } from "@neurals/orchestrator/dialog.orchestrator"; import type { Insight } from "@/lib/awareness/collective/analysis/insight"; import { getPinnedInsights, pinInsight } from "@/lib/evolution/history.pins"; import { getEvolutionLogs, startAutoSnapshot, stopAutoSnapshot } from "@/lib/evolution/history.collector"; import { download as dl, toCSV, toJSON } from "@/lib/evolution/exporters/report"; import { downloadCSV as dlPinsCSV, downloadPDF as dlPinsPDF } from "@/lib/awareness/collective/analysis/exporter"; export const NeuralDialogHUD: React.FC<{ onClose: () => void; lastInsight?: Insight | null; onOpenAISummary?: () => void; onCloseAISummary?: () => void; }> = ({ onClose, lastInsight, onOpenAISummary, onCloseAISummary }) => { const { supported, listening, transcript, start, stop, error, setTranscript } = useSpeechRecognition("en-US"); const [phase, setPhase] = useState(0); useEffect(() => { const id = setInterval(() => setPhase(p => (p + 1) % 360), 50); return () => clearInterval(id); }, []); useEffect(() => { if (!transcript) return; (async () => { const reply = await handleTranscript(transcript, { lastInsight, pin: (i) => i && pinInsight({ timestamp: i.point.timestamp, trend: i.point.trend, confidence: i.point.confidence, note: i.point.notes || "Pinned via dialog" }), openSummary: onOpenAISummary, closeSummary: onCloseAISummary, exportFns: { logsCSV: () => dl(`evolution_logs_${Date.now()}.csv`, toCSV(getEvolutionLogs()), "text/csv"), logsJSON: () => dl(`evolution_logs_${Date.now()}.json`, toJSON(getEvolutionLogs()), "application/json"), pinsCSV: () => getPinnedInsights().length && dlPinsCSV(), pinsPDF: () => getPinnedInsights().length && dlPinsPDF(), }, snapshotFns: { start: () => startAutoSnapshot({ intervalSec: 60, confidenceDelta: 0.05 }), stop: () => stopAutoSnapshot(), }, }); speak(reply.reply); setTranscript(""); })(); }, [transcript]); return ( <motion.div className="fixed inset-0 z-[220] flex items-center justify-center bg-black/80 backdrop-blur-md" initial={{opacity:0}} animate={{opacity:1}} exit={{opacity:0}}> <motion.div className="relative w-[760px] max-w-[92%] rounded-[20px] p-[3px] bg-gradient-to-r from-cyan-500/40 via-blue-400/20 to-teal-400/40 shadow-[0_0_50px_rgba(0,255,255,0.25)]" initial={{scale:0.92}} animate={{scale:1}} transition={{type:"spring",stiffness:160,damping:15}}> <div className="relative bg-[#01131e]/90 p-6 rounded-[18px] shadow-[inset_0_0_35px_rgba(0,180,255,0.15)]"> <div className="flex justify-between items-center mb-4"> <h2 className="text-[20px] font-bold text-cyan-300">Interactive Neural Dialog</h2> <div className="flex gap-2"> {!supported ? ( <span className="text-xs text-amber-300">Not supported</span> ) : listening ? ( <button onClick={stop} className="px-3 py-1.5 text-sm border border-cyan-400/50 text-cyan-200 rounded-md hover:bg-cyan-500/10">⏹ Stop</button> ) : ( <button onClick={start} className="px-3 py-1.5 text-sm border border-cyan-400/50 text-cyan-200 rounded-md hover:bg-cyan-500/10">▶ Start</button> )} <button onClick={() => { stop(); stopSpeak(); onClose(); }} className="px-3 py-1.5 text-sm border border-cyan-400/50 text-cyan-200 rounded-md hover:bg-cyan-500/10">Close</button> </div> </div> {/* waveform */} <div className="relative h-[140px] flex items-end justify-center overflow-hidden mb-3"> {[...Array(48)].map((_, i) => { const barHeight = Math.sin((i * 12 + phase) * 0.12) * (listening ? 50 : 20) + 60; const opacity = listening ? 0.9 : 0.3; return <motion.div key={i} className="w-[4px] mx-[2px] bg-gradient-to-t from-cyan-500 to-blue-300 rounded-full" animate={{height:barHeight,opacity}} transition={{duration:0.1}} />; })} <div className="absolute top-2 text-xs text-cyan-300/80">{listening ? "🎤 Listening..." : "Idle"}</div> </div> <div className="font-mono text-[13px] text-cyan-100/90 min-h-[48px] border border-cyan-400/20 rounded-md p-3 bg-[#031a28]/60"> {transcript || (error ? `Error: ${error}` : "Say: “pin this”, “export csv”, “open summary”…")} </div> </div> </motion.div> </motion.div> ); }; |