App.tsx•5.42 kB
import React, { useState, useRef, useEffect } from 'react'
interface Message {
type: 'user' | 'assistant'
content: string
timestamp: Date
}
interface ServiceInfo {
name: string
capabilities: string[]
model: string
}
function App() {
const [messages, setMessages] = useState<Message[]>([])
const [input, setInput] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [serviceInfo, setServiceInfo] = useState<ServiceInfo | null>(null)
const messagesEndRef = useRef<HTMLDivElement>(null)
useEffect(() => {
// Fetch service info on component mount
fetch('http://localhost:3000/api/services')
.then(res => res.json())
.then(data => {
if (data.llm) {
setServiceInfo(data.llm)
}
})
.catch(err => console.error('Error fetching service info:', err))
}, [])
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}
useEffect(() => {
scrollToBottom()
}, [messages])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!input.trim()) return
const userMessage: Message = {
type: 'user',
content: input,
timestamp: new Date()
}
setMessages(prev => [...prev, userMessage])
setInput('')
setIsLoading(true)
try {
const response = await fetch('http://localhost:3000/api/nl', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: input }),
})
const data = await response.json()
const assistantMessage: Message = {
type: 'assistant',
content: data.error
? `Error: ${data.error}`
: data.output
? `Command: ${data.command}\n\nOutput:\n${data.output}${data.summary ? `\n\nSummary:\n${data.summary}` : ''}`
: `Command: ${data.command}`,
timestamp: new Date()
}
setMessages(prev => [...prev, assistantMessage])
} catch (error) {
console.error('Error:', error)
const errorMessage: Message = {
type: 'assistant',
content: 'Sorry, there was an error processing your request.',
timestamp: new Date()
}
setMessages(prev => [...prev, errorMessage])
} finally {
setIsLoading(false)
}
}
return (
<div className="flex flex-col h-screen bg-gray-50">
{/* Header */}
<header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
<h1 className="text-2xl font-semibold text-gray-900">Core MCP Chat</h1>
{serviceInfo && (
<p className="text-sm text-gray-500">
Using {serviceInfo.model} with capabilities: {serviceInfo.capabilities.join(', ')}
</p>
)}
</div>
</header>
{/* Chat messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((message, index) => (
<div
key={index}
className={`flex ${
message.type === 'user' ? 'justify-end' : 'justify-start'
}`}
>
<div
className={`max-w-3xl rounded-lg px-4 py-2 ${
message.type === 'user'
? 'bg-blue-600 text-white'
: 'bg-white text-gray-900 shadow'
}`}
>
<div className="whitespace-pre-wrap font-mono text-sm">
{message.content}
</div>
<div
className={`text-xs mt-1 ${
message.type === 'user' ? 'text-blue-200' : 'text-gray-500'
}`}
>
{message.timestamp.toLocaleTimeString()}
</div>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-white text-gray-900 shadow rounded-lg px-4 py-2">
<div className="flex space-x-2">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.4s' }}></div>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Input form */}
<form onSubmit={handleSubmit} className="border-t bg-white p-4">
<div className="max-w-7xl mx-auto flex space-x-4">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type your command..."
className="flex-1 rounded-lg border border-gray-300 px-4 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading}
className="bg-blue-600 text-white px-6 py-2 rounded-lg font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50"
>
Send
</button>
</div>
</form>
</div>
)
}
export default App