import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
TrashIcon,
SparklesIcon,
PaperClipIcon,
MicrophoneIcon,
PaperAirplaneIcon,
ArrowPathIcon,
DocumentDuplicateIcon,
ChatBubbleLeftRightIcon,
CommandLineIcon,
} from "@heroicons/react/24/outline";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
// Internal hooks/types
import { useChat } from "./useChat";
import type { ThreadMeta } from "./threads";
// --- Constants & Config ---
const APP_TITLE = "Krishna's Digital Twin";
const BOT_AVATAR =
"https://api.dicebear.com/9.x/bottts-neutral/svg?seed=Krishna1&backgroundColor=6366f1"; // Cleaner 3D-ish Robot
/*
const DID_YOU_KNOW = [
"Krishna achieved a 3.95 GPA during his M.S. at Virginia Tech.",
"Krishna built an agent that automates Android UI tasks with 80%+ accuracy.",
"Krishna optimized genomic ETL pipelines reducing runtime by 70%.",
"Krishna specializes in building autonomous agents and RAG systems.",
];
*/
const SUGGESTIONS = [
{
title: "Summarize Experience",
text: "Give me a 90-second intro to Krishna Vamsi Dhulipalla—recent work, top strengths, and impact.",
icon: "✨",
},
{
title: "Download Resume",
text: "Share Krishna’s latest resume and provide a download link.",
icon: "📄",
},
{
title: "Capabilities",
text: "What tools and actions can you perform for me?",
icon: "🛠️",
},
{
title: "Schedule Meeting",
text: "Schedule a 30-minute meeting with Krishna next week.",
icon: "📅",
},
];
/*
function DidYouKnowRotator() {
const [index, setIndex] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setIndex((prev) => (prev + 1) % DID_YOU_KNOW.length);
}, 5000); // Rotate every 5 seconds
return () => clearInterval(interval);
}, []);
return (
Did you know?
"{DID_YOU_KNOW[index]}"
);
}
*/
// --- Helper: Date Grouping for Sidebar ---
function groupThreadsByDate(threads: ThreadMeta[]) {
const today = new Date();
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const groups: Record = {
Today: [],
Yesterday: [],
"Previous 7 Days": [],
Older: [],
};
threads.forEach((t) => {
const d = new Date(t.lastAt);
if (d.toDateString() === today.toDateString()) {
groups["Today"].push(t);
} else if (d.toDateString() === yesterday.toDateString()) {
groups["Yesterday"].push(t);
} else if (d.getTime() > today.getTime() - 7 * 24 * 60 * 60 * 1000) {
groups["Previous 7 Days"].push(t);
} else {
groups["Older"].push(t);
}
});
return groups;
}
// --- Main Component ---
export default function App() {
const {
threads,
active,
messages,
setActiveThread,
newChat,
clearChat,
deleteThread,
send,
isStreaming,
hasFirstToken,
} = useChat();
const [input, setInput] = useState("");
const bottomRef = useRef(null);
const inputRef = useRef(null);
const prevThreadId = useRef(null);
// --- Voice Input Logic ---
const [isListening, setIsListening] = useState(false);
const recognitionRef = useRef(null);
useEffect(() => {
const SpeechRecognition =
(window as any).SpeechRecognition ||
(window as any).webkitSpeechRecognition;
if (SpeechRecognition) {
recognitionRef.current = new SpeechRecognition();
recognitionRef.current.continuous = false;
recognitionRef.current.interimResults = false;
recognitionRef.current.lang = "en-US";
recognitionRef.current.onresult = (event: any) => {
const transcript = event.results[0][0].transcript;
setInput((prev) => (prev ? prev + " " + transcript : transcript));
setIsListening(false);
};
recognitionRef.current.onerror = (event: any) => {
console.error("Speech recognition error", event.error);
setIsListening(false);
};
recognitionRef.current.onend = () => {
setIsListening(false);
};
}
}, []);
const toggleListening = useCallback(() => {
if (!recognitionRef.current) {
alert("Browser does not support Speech Recognition");
return;
}
if (isListening) {
recognitionRef.current.stop();
setIsListening(false);
} else {
recognitionRef.current.start();
setIsListening(true);
}
}, [isListening]);
// Auto-scroll logic
useEffect(() => {
const currentThreadId = active?.id ?? null;
if (currentThreadId !== prevThreadId.current) {
prevThreadId.current = currentThreadId;
bottomRef.current?.scrollIntoView({ behavior: "auto" });
} else {
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
}
}, [messages, active?.id]);
const sendMessage = useCallback(() => {
const text = input.trim();
if (!text || isStreaming) return;
send(text);
setInput("");
}, [input, isStreaming, send]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
},
[sendMessage]
);
const groupedThreads = useMemo(() => groupThreadsByDate(threads), [threads]);
return (
{/* Background Ambience */}
{/* --- Sidebar --- */}
{/* --- Main Chat Area --- */}
{/* Top Navigation */}
{APP_TITLE}
Online & Ready
{/* Chat Stream */}
{messages.length === 0 ? (
How can I help you today?
I'm Krishna's digital twin. Ask me about his architecture
skills, recent projects, or schedule a meeting.
{/*
*/}
{/*
*/}
{/* Suggestions Grid */}
{SUGGESTIONS.map((s, i) => (
))}
) : (
{messages.map((m, idx) => (
))}
{/* Streaming Indicator */}
{isStreaming && !hasFirstToken && (
)}
)}
{/* --- Input Area --- */}
AI can make mistakes. Please verify important information.
);
}
// --- Message Bubble Component ---
function Bubble({
message,
isStreaming,
}: {
message: any;
isStreaming: boolean;
}) {
const isUser = message.role === "user";
return (
{!isUser && (
)}
(
),
code: ({ inline, className, children, ...props }: any) => {
if (inline)
return (
{children}
);
return (
{children}
);
},
ul: (props) => (
),
ol: (props) => (
),
p: (props) => ,
}}
>
{message.content}
{/* Actions for Assistant */}
{!isUser && !isStreaming && (
}
label="Copy"
onClick={() => navigator.clipboard.writeText(message.content)}
/>
}
label="Regenerate"
onClick={() => {}}
/>
)}
);
}
function ActionButton({
icon,
label,
onClick,
}: {
icon: any;
label: string;
onClick: () => void;
}) {
return (
);
}