Skip to main content
Glama

OpenManager Vibe V4 MCP Server

by skyasu2
AIProcessor.jsx8.32 kB
import React, { useState, useEffect, useRef } from 'react'; import EventBus from '../services/EventBus'; import MCPService from '../services/MCPService'; const AIProcessor = () => { const [query, setQuery] = useState(''); const [processing, setProcessing] = useState(false); const [responses, setResponses] = useState([]); const chatEndRef = useRef(null); // 채팅 스크롤 맨 아래로 이동 useEffect(() => { if (chatEndRef.current) { chatEndRef.current.scrollIntoView({ behavior: 'smooth' }); } }, [responses]); // 이벤트 구독 useEffect(() => { // 에러 이벤트 구독 const errorUnsubscribe = EventBus.subscribe('error', handleError); // 컨텍스트 업데이트 이벤트 구독 const contextUnsubscribe = EventBus.subscribe('context:updated', handleContextUpdate); return () => { errorUnsubscribe(); contextUnsubscribe(); }; }, []); // 에러 처리 함수 const handleError = (errorData) => { console.error('에러 발생:', errorData); // 에러 발생 시 응답 추가 setResponses(prev => [...prev, { type: 'error', content: `오류가 발생했습니다: ${errorData.message || '알 수 없는 오류'}`, timestamp: new Date().toISOString() }]); setProcessing(false); }; // 컨텍스트 업데이트 처리 const handleContextUpdate = (context) => { console.log('컨텍스트 업데이트:', context); }; // 쿼리 제출 처리 const handleSubmit = async (e) => { e.preventDefault(); if (!query.trim()) return; // 사용자 질의 추가 setResponses(prev => [...prev, { type: 'user', content: query, timestamp: new Date().toISOString() }]); // 로딩 메시지 추가 setResponses(prev => [...prev, { type: 'loading', content: '분석 중입니다...', timestamp: new Date().toISOString() }]); setProcessing(true); try { // 실제 API 호출 (개발 중에는 주석 처리) // const response = await MCPService.processQuery(query); // 개발용 Mock 응답 const mockResponse = MCPService.getMockResponse(query); // 로딩 메시지 제거하고 실제 응답 추가 setResponses(prev => { const filtered = prev.filter(item => item.type !== 'loading'); return [...filtered, { type: 'ai', content: mockResponse.answer, data: mockResponse, timestamp: mockResponse.timestamp || new Date().toISOString() }]; }); // 응답 이벤트 발행 if (mockResponse.related_servers) { EventBus.publish('ai:response-received', { related_servers: mockResponse.related_servers, ...mockResponse }); } } catch (error) { // 에러 이벤트 발행 EventBus.publish('error', { source: 'ai-processor', message: 'AI 쿼리 처리 중 오류 발생', details: error.message }); } finally { setProcessing(false); setQuery(''); } }; return ( <div className="ai-processor w-full bg-white rounded-lg shadow-md p-4 max-h-[600px] flex flex-col"> <h2 className="text-xl font-semibold text-gray-800 mb-4">서버 모니터링 어시스턴트</h2> {/* 채팅 메시지 영역 */} <div className="chat-area flex-1 overflow-y-auto mb-4 p-2"> {responses.length === 0 ? ( <div className="text-center text-gray-500 italic py-10"> <p>서버 상태, 성능 또는 문제점에 대해 질문해보세요.</p> <p className="mt-2 text-sm">예: "어제 어떤 장애가 있었어?" 또는 "현재 서버 상태는 어때?"</p> </div> ) : ( responses.map((item, index) => ( <div key={index} className={`mb-4 ${item.type === 'user' ? 'text-right' : 'text-left'}`}> {item.type === 'user' && ( <div className="inline-block bg-blue-500 text-white rounded-lg py-2 px-4 max-w-[80%]"> {item.content} </div> )} {item.type === 'ai' && ( <div className="inline-block bg-gray-100 text-gray-800 rounded-lg py-2 px-4 max-w-[80%]"> {item.content} {item.data && item.data.suggestions && item.data.suggestions.length > 0 && ( <div className="mt-2 pt-2 border-t border-gray-200"> <p className="text-sm text-gray-600 font-medium">추천 작업:</p> <div className="flex flex-wrap gap-2 mt-1"> {item.data.suggestions.map((suggestion, idx) => ( <button key={idx} className="bg-gray-200 hover:bg-gray-300 text-gray-700 text-xs py-1 px-2 rounded-full" > {suggestion} </button> ))} </div> </div> )} </div> )} {item.type === 'loading' && ( <div className="inline-block bg-gray-100 text-gray-600 rounded-lg py-2 px-4"> <div className="flex items-center"> <div className="dot-flashing mr-2"></div> {item.content} </div> </div> )} {item.type === 'error' && ( <div className="inline-block bg-red-100 text-red-600 rounded-lg py-2 px-4"> {item.content} </div> )} <div className="text-xs text-gray-500 mt-1"> {new Date(item.timestamp).toLocaleTimeString()} </div> </div> )) )} <div ref={chatEndRef} /> </div> {/* 쿼리 입력 폼 */} <form onSubmit={handleSubmit} className="query-form mt-auto"> <div className="flex"> <input type="text" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="질문이나 명령을 입력하세요..." disabled={processing} className="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500" /> <button type="submit" disabled={processing || !query.trim()} className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-r-lg disabled:bg-blue-300" > {processing ? '처리 중...' : '질문하기'} </button> </div> </form> {/* 로딩 애니메이션 스타일 */} <style jsx>{` .dot-flashing { position: relative; width: 10px; height: 10px; border-radius: 5px; background-color: #9880ff; color: #9880ff; animation: dot-flashing 1s infinite linear alternate; animation-delay: 0.5s; } .dot-flashing::before, .dot-flashing::after { content: ''; display: inline-block; position: absolute; top: 0; } .dot-flashing::before { left: -15px; width: 10px; height: 10px; border-radius: 5px; background-color: #9880ff; color: #9880ff; animation: dot-flashing 1s infinite alternate; animation-delay: 0s; } .dot-flashing::after { left: 15px; width: 10px; height: 10px; border-radius: 5px; background-color: #9880ff; color: #9880ff; animation: dot-flashing 1s infinite alternate; animation-delay: 1s; } @keyframes dot-flashing { 0% { background-color: #9880ff; } 50%, 100% { background-color: rgba(152, 128, 255, 0.2); } } `}</style> </div> ); }; export default AIProcessor;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/skyasu2/openmanager-vibe-v4'

If you have feedback or need assistance with the MCP directory API, please join our Discord server