Shivam311's picture
feat: CodeAtlas Enterprise - IBM Bob Engineering Intelligence Platform
3a7842d
Raw
History Blame Contribute Delete
4.27 kB
import { useEffect, useRef, useState } from 'react';
import { Bot, Send, Sparkles } from 'lucide-react';
import { motion } from 'framer-motion';
import { askQuestion } from '../../api/client';
import { useRepoStore } from '../../store/useRepoStore';
import MessageBubble from './MessageBubble';
import SuggestedQuestions from './SuggestedQuestions';
import TypingIndicator from './TypingIndicator';
export default function ChatInterface() {
const [input, setInput] = useState('');
const endRef = useRef(null);
const { repoId, chatHistory, addChatMessage, isAsking, setAsking } = useRepoStore();
useEffect(() => {
endRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatHistory, isAsking]);
const handleSend = async (question = input) => {
const trimmed = question.trim();
if (!trimmed || !repoId || isAsking) return;
const historyBeforeSend = chatHistory;
setInput('');
addChatMessage({ role: 'user', content: trimmed });
setAsking(true);
try {
const response = await askQuestion(repoId, trimmed, historyBeforeSend);
addChatMessage({ role: 'assistant', content: response.data.answer, details: response.data });
} catch (error) {
addChatMessage({
role: 'assistant',
content: error?.response?.data?.detail || 'IBM Bob could not complete that analysis. Please try another question.',
error: true,
});
} finally {
setAsking(false);
}
};
return (
<section className="flex h-full min-h-[720px] flex-col overflow-hidden rounded-lg border border-white/10 bg-[#0a0f1a]/85">
<div className="flex items-center gap-3 border-b border-white/10 p-5">
<span className="flex h-10 w-10 items-center justify-center rounded-lg border border-cyan-400/25 bg-cyan-500/10">
<Bot className="h-5 w-5 text-cyan-200" />
</span>
<div>
<h3 className="font-semibold text-white">Engineering Assistant</h3>
<p className="text-xs text-slate-500">Repository-aware IBM Bob analysis</p>
</div>
<div className="ml-auto flex items-center gap-2 text-xs text-emerald-300">
<span className="h-2 w-2 rounded-full bg-emerald-400 pulse-dot" />
Active
</div>
</div>
<div className="flex-1 space-y-4 overflow-y-auto p-5">
{chatHistory.length === 0 && (
<div className="space-y-6 py-8 text-center">
<Sparkles className="mx-auto h-8 w-8 text-cyan-300" />
<div>
<h4 className="font-semibold text-white">Ask about architecture, risks, APIs, or onboarding</h4>
<p className="mt-1 text-sm text-slate-500">Answers are grounded in the scanned repository context.</p>
</div>
<SuggestedQuestions onSelect={handleSend} />
</div>
)}
{chatHistory.map((message, index) => (
<MessageBubble key={`${message.role}-${index}`} message={message} />
))}
{isAsking && <TypingIndicator />}
<div ref={endRef} />
</div>
<div className="border-t border-white/10 p-4">
<div className="flex items-end gap-3">
<textarea
value={input}
onChange={(event) => setInput(event.target.value)}
onKeyDown={(event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
handleSend();
}
}}
rows={1}
placeholder="Ask a repository question..."
className="min-h-11 max-h-32 flex-1 resize-none rounded-lg border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-slate-100 outline-none placeholder:text-slate-600 focus:border-cyan-400/50"
/>
<motion.button
whileTap={{ scale: 0.96 }}
onClick={() => handleSend()}
disabled={!input.trim() || isAsking}
className="flex h-11 w-11 items-center justify-center rounded-lg bg-cyan-500 text-slate-950 transition-colors hover:bg-cyan-300 disabled:cursor-not-allowed disabled:opacity-40"
aria-label="Send question"
>
<Send className="h-4 w-4" />
</motion.button>
</div>
</div>
</section>
);
}