| | <!DOCTYPE html> |
| | <html lang="ko"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>멀티 에이전트 채팅 시스템</title> |
| | <script src="https://cdn.tailwindcss.com"></script> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| | <style> |
| | .agent-card:hover { |
| | transform: translateY(-5px); |
| | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); |
| | } |
| | .message-animation { |
| | animation: fadeIn 0.3s ease-out; |
| | } |
| | @keyframes fadeIn { |
| | from { opacity: 0; transform: translateY(10px); } |
| | to { opacity: 1; transform: translateY(0); } |
| | } |
| | .typing-indicator { |
| | display: inline-flex; |
| | align-items: center; |
| | } |
| | .typing-dot { |
| | width: 8px; |
| | height: 8px; |
| | background-color: #9CA3AF; |
| | border-radius: 50%; |
| | margin: 0 2px; |
| | animation: typingAnimation 1.4s infinite ease-in-out; |
| | } |
| | .typing-dot:nth-child(1) { animation-delay: 0s; } |
| | .typing-dot:nth-child(2) { animation-delay: 0.2s; } |
| | .typing-dot:nth-child(3) { animation-delay: 0.4s; } |
| | @keyframes typingAnimation { |
| | 0%, 60%, 100% { transform: translateY(0); } |
| | 30% { transform: translateY(-5px); } |
| | } |
| | </style> |
| | </head> |
| | <body class="bg-gray-100 font-sans"> |
| | <div class="container mx-auto px-4 py-8 max-w-7xl"> |
| | <header class="mb-8 text-center"> |
| | <h1 class="text-4xl font-bold text-indigo-800 mb-2">멀티 에이전트 채팅 시스템</h1> |
| | <p class="text-gray-600">여러 AI 에이전트와 동시에 대화하며 다양한 관점을 경험하세요</p> |
| | </header> |
| |
|
| | <div class="flex flex-col lg:flex-row gap-6"> |
| | |
| | <div class="w-full lg:w-1/4 bg-white rounded-xl shadow-md p-4 h-fit"> |
| | <h2 class="text-xl font-semibold text-gray-800 mb-4">에이전트 선택</h2> |
| | <div class="space-y-3"> |
| | <div class="agent-card bg-indigo-50 p-4 rounded-lg cursor-pointer transition-all duration-300 border border-indigo-100" |
| | onclick="selectAgent('analyst')" data-agent="analyst"> |
| | <div class="flex items-center"> |
| | <div class="w-10 h-10 rounded-full bg-indigo-200 flex items-center justify-center mr-3"> |
| | <i class="fas fa-chart-line text-indigo-600"></i> |
| | </div> |
| | <div> |
| | <h3 class="font-medium text-indigo-800">분석가 알렉스</h3> |
| | <p class="text-xs text-gray-500">데이터 분석 전문가</p> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="agent-card bg-green-50 p-4 rounded-lg cursor-pointer transition-all duration-300 border border-green-100" |
| | onclick="selectAgent('creative')" data-agent="creative"> |
| | <div class="flex items-center"> |
| | <div class="w-10 h-10 rounded-full bg-green-200 flex items-center justify-center mr-3"> |
| | <i class="fas fa-paint-brush text-green-600"></i> |
| | </div> |
| | <div> |
| | <h3 class="font-medium text-green-800">크리에이터 클로이</h3> |
| | <p class="text-xs text-gray-500">창의적 아이디어 생성기</p> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="agent-card bg-red-50 p-4 rounded-lg cursor-pointer transition-all duration-300 border border-red-100" |
| | onclick="selectAgent('debater')" data-agent="debater"> |
| | <div class="flex items-center"> |
| | <div class="w-10 h-10 rounded-full bg-red-200 flex items-center justify-center mr-3"> |
| | <i class="fas fa-comments text-red-600"></i> |
| | </div> |
| | <div> |
| | <h3 class="font-medium text-red-800">토론가 데이빗</h3> |
| | <p class="text-xs text-gray-500">논리적 토론 전문가</p> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="agent-card bg-yellow-50 p-4 rounded-lg cursor-pointer transition-all duration-300 border border-yellow-100" |
| | onclick="selectAgent('advisor')" data-agent="advisor"> |
| | <div class="flex items-center"> |
| | <div class="w-10 h-10 rounded-full bg-yellow-200 flex items-center justify-center mr-3"> |
| | <i class="fas fa-lightbulb text-yellow-600"></i> |
| | </div> |
| | <div> |
| | <h3 class="font-medium text-yellow-800">조언가 제시카</h3> |
| | <p class="text-xs text-gray-500">전략적 조언 제공자</p> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <div class="mt-6"> |
| | <h3 class="text-sm font-semibold text-gray-700 mb-2">시스템 설정</h3> |
| | <div class="flex items-center justify-between mb-2"> |
| | <span class="text-sm text-gray-600">에이전트 간 상호작용</span> |
| | <label class="relative inline-flex items-center cursor-pointer"> |
| | <input type="checkbox" id="agentInteraction" class="sr-only peer" checked> |
| | <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-indigo-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div> |
| | </label> |
| | </div> |
| | <div class="flex items-center justify-between"> |
| | <span class="text-sm text-gray-600">실시간 반응</span> |
| | <label class="relative inline-flex items-center cursor-pointer"> |
| | <input type="checkbox" id="realtimeResponse" class="sr-only peer" checked> |
| | <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-indigo-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div> |
| | </label> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="w-full lg:w-3/4 flex flex-col"> |
| | |
| | <div class="bg-white rounded-xl shadow-md p-4 mb-4"> |
| | <div class="flex flex-wrap gap-3" id="selectedAgentsContainer"> |
| | <div class="text-sm text-gray-500">에이전트를 선택하세요 (복수 선택 가능)</div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="flex-1 bg-white rounded-xl shadow-md p-4 mb-4 overflow-y-auto" id="chatContainer" style="min-height: 400px;"> |
| | <div class="text-center text-gray-500 py-10" id="welcomeMessage"> |
| | <i class="fas fa-robot text-4xl text-indigo-300 mb-3"></i> |
| | <p class="text-lg">멀티 에이전트 채팅 시스템에 오신 것을 환영합니다!</p> |
| | <p class="text-sm mt-2">왼쪽에서 대화할 에이전트를 선택해 주세요.</p> |
| | </div> |
| | <div id="chatMessages" class="space-y-4 hidden"></div> |
| | </div> |
| |
|
| | |
| | <div class="bg-white rounded-xl shadow-md p-4"> |
| | <div class="flex items-center"> |
| | <input type="text" id="userInput" placeholder="메시지를 입력하세요..." |
| | class="flex-1 border border-gray-300 rounded-l-lg py-2 px-4 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> |
| | <button onclick="sendMessage()" class="bg-indigo-600 text-white py-2 px-6 rounded-r-lg hover:bg-indigo-700 transition duration-300"> |
| | <i class="fas fa-paper-plane"></i> |
| | </button> |
| | </div> |
| | <div class="flex justify-between mt-2"> |
| | <div class="text-xs text-gray-500"> |
| | <span id="charCount">0</span>/500 |
| | </div> |
| | <div class="flex space-x-2"> |
| | <button class="text-gray-500 hover:text-indigo-600" title="파일 첨부"> |
| | <i class="fas fa-paperclip"></i> |
| | </button> |
| | <button class="text-gray-500 hover:text-indigo-600" title="음성 입력"> |
| | <i class="fas fa-microphone"></i> |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | |
| | let selectedAgents = []; |
| | let conversationHistory = []; |
| | |
| | |
| | const agents = { |
| | analyst: { |
| | name: "분석가 알렉스", |
| | color: "indigo", |
| | icon: "chart-line", |
| | description: "데이터 분석 전문가", |
| | personality: "저는 데이터와 사실에 기반한 분석을 제공합니다. 감정보다는 숫자와 통계를 신뢰합니다." |
| | }, |
| | creative: { |
| | name: "크리에이터 클로이", |
| | color: "green", |
| | icon: "paint-brush", |
| | description: "창의적 아이디어 생성기", |
| | personality: "상상력이 풍부하고 독창적인 아이디어를 제공하는 것이 제 강점입니다. 틀에 얽매이지 않는 사고를 좋아해요!" |
| | }, |
| | debater: { |
| | name: "토론가 데이빗", |
| | color: "red", |
| | icon: "comments", |
| | description: "논리적 토론 전문가", |
| | personality: "논리적 오류를 찾아내고 다양한 관점에서 문제를 바라보는 것을 좋아합니다. 강한 주장도 두려워하지 않아요." |
| | }, |
| | advisor: { |
| | name: "조언가 제시카", |
| | color: "yellow", |
| | icon: "lightbulb", |
| | description: "전략적 조언 제공자", |
| | personality: "실용적이고 실행 가능한 조언을 제공하는 것을 좋아합니다. 장기적인 관점에서 문제를 해결하는 데 집중합니다." |
| | } |
| | }; |
| | |
| | |
| | function selectAgent(agentId) { |
| | const agentCard = document.querySelector(`[data-agent="${agentId}"]`); |
| | |
| | if (selectedAgents.includes(agentId)) { |
| | |
| | selectedAgents = selectedAgents.filter(id => id !== agentId); |
| | agentCard.classList.remove(`border-${agents[agentId].color}-300`, `ring-2`, `ring-${agents[agentId].color}-300`); |
| | } else { |
| | |
| | selectedAgents.push(agentId); |
| | agentCard.classList.add(`border-${agents[agentId].color}-300`, `ring-2`, `ring-${agents[agentId].color}-300`); |
| | } |
| | |
| | updateSelectedAgentsUI(); |
| | } |
| | |
| | |
| | function updateSelectedAgentsUI() { |
| | const container = document.getElementById('selectedAgentsContainer'); |
| | |
| | if (selectedAgents.length === 0) { |
| | container.innerHTML = '<div class="text-sm text-gray-500">에이전트를 선택하세요 (복수 선택 가능)</div>'; |
| | document.getElementById('welcomeMessage').classList.remove('hidden'); |
| | document.getElementById('chatMessages').classList.add('hidden'); |
| | return; |
| | } |
| | |
| | container.innerHTML = ''; |
| | selectedAgents.forEach(agentId => { |
| | const agent = agents[agentId]; |
| | const badge = document.createElement('div'); |
| | badge.className = `flex items-center bg-${agent.color}-100 text-${agent.color}-800 px-3 py-1 rounded-full text-sm`; |
| | badge.innerHTML = ` |
| | <i class="fas fa-${agent.icon} mr-1"></i> |
| | ${agent.name} |
| | `; |
| | container.appendChild(badge); |
| | }); |
| | |
| | document.getElementById('welcomeMessage').classList.add('hidden'); |
| | document.getElementById('chatMessages').classList.remove('hidden'); |
| | } |
| | |
| | |
| | function sendMessage() { |
| | const userInput = document.getElementById('userInput'); |
| | const message = userInput.value.trim(); |
| | |
| | if (message === '' || selectedAgents.length === 0) return; |
| | |
| | |
| | addMessage('user', '당신', 'gray', 'user', message); |
| | userInput.value = ''; |
| | updateCharCount(); |
| | |
| | |
| | scrollChatToBottom(); |
| | |
| | |
| | simulateAgentResponses(message); |
| | } |
| | |
| | |
| | function simulateAgentResponses(userMessage) { |
| | const interactionEnabled = document.getElementById('agentInteraction').checked; |
| | const realtimeEnabled = document.getElementById('realtimeResponse').checked; |
| | |
| | |
| | conversationHistory.push({ |
| | sender: 'user', |
| | message: userMessage, |
| | timestamp: new Date().toISOString() |
| | }); |
| | |
| | |
| | selectedAgents.forEach((agentId, index) => { |
| | const agent = agents[agentId]; |
| | |
| | |
| | setTimeout(() => { |
| | |
| | if (interactionEnabled && index > 0) { |
| | const previousAgentId = selectedAgents[index-1]; |
| | const previousAgent = agents[previousAgentId]; |
| | |
| | |
| | const response = generateInteractiveResponse(agentId, previousAgentId, userMessage); |
| | addMessage('agent', agent.name, agent.color, agentId, response); |
| | |
| | |
| | conversationHistory.push({ |
| | sender: agentId, |
| | message: response, |
| | timestamp: new Date().toISOString() |
| | }); |
| | } else { |
| | |
| | const response = generateAgentResponse(agentId, userMessage); |
| | |
| | |
| | if (realtimeEnabled) { |
| | showTypingIndicator(agentId); |
| | |
| | setTimeout(() => { |
| | removeTypingIndicator(agentId); |
| | addMessage('agent', agent.name, agent.color, agentId, response); |
| | |
| | |
| | conversationHistory.push({ |
| | sender: agentId, |
| | message: response, |
| | timestamp: new Date().toISOString() |
| | }); |
| | |
| | scrollChatToBottom(); |
| | }, 1500 + Math.random() * 1000); |
| | } else { |
| | addMessage('agent', agent.name, agent.color, agentId, response); |
| | |
| | |
| | conversationHistory.push({ |
| | sender: agentId, |
| | message: response, |
| | timestamp: new Date().toISOString() |
| | }); |
| | |
| | scrollChatToBottom(); |
| | } |
| | } |
| | }, index * 500); |
| | }); |
| | } |
| | |
| | |
| | function generateAgentResponse(agentId, userMessage) { |
| | const agent = agents[agentId]; |
| | const responses = { |
| | analyst: [ |
| | `제가 분석한 결과, "${userMessage}"라는 주제는 몇 가지 핵심 요소로 나눌 수 있습니다. 첫째, 데이터 상으로는...`, |
| | `흥미로운 질문입니다. 제가 최근에 본 연구에 따르면, 이와 관련된 통계는...`, |
| | `저는 이 문제를 정량적으로 접근하는 것을 선호합니다. 측정 가능한 지표로 보면...` |
| | ], |
| | creative: [ |
| | `와, "${userMessage}"라니 정말 영감을 주는 주제네요! 제 상상력이 즉시 활활 타오르는군요. 만약 우리가...`, |
| | `이 주제로 이런 아이디어는 어떨까요? [창의적인 제안]. 물론 이건 시작일 뿐이고 더 발전시킬 수 있어요!`, |
| | `제 머릿속에 재미있는 이미지가 떠오르네요. 이 주제를 색다른 각도에서 보면...` |
| | ], |
| | debater: [ |
| | `논리적으로 접근해보자면, "${userMessage}"에 대한 주장에는 몇 가지 논점이 있습니다. 첫째로...`, |
| | `반대 의견을 제시해도 될까요? 왜냐하면... 물론 다른 관점도 있습니다.`, |
| | `이 문제를 양면에서 바라보는 것이 중요합니다. 한편으로는... 하지만 다른 한편으로는...` |
| | ], |
| | advisor: [ |
| | `실용적인 조언을 드리자면, "${userMessage}"에 대해 다음 단계를 고려해보세요: 1) ... 2) ...`, |
| | `장기적인 관점에서, 이 문제를 해결하기 위한 전략은... 단기적으로는...`, |
| | `제 경험상, 이런 유형의 문제에는 이러한 접근 방식이 효과적이었습니다...` |
| | ] |
| | }; |
| | |
| | |
| | const randomIndex = Math.floor(Math.random() * responses[agentId].length); |
| | return responses[agentId][randomIndex]; |
| | } |
| | |
| | |
| | function generateInteractiveResponse(agentId, previousAgentId, userMessage) { |
| | const agent = agents[agentId]; |
| | const previousAgent = agents[previousAgentId]; |
| | |
| | |
| | const interactionResponses = { |
| | analyst_creative: [ |
| | `[${previousAgent.name}]님의 창의적인 아이디어에 데이터를 더해보겠습니다. 통계적으로 보면 그 아이디어는...`, |
| | `창의성과 데이터는 좋은 조합입니다. ${previousAgent.name}님의 제안을 분석해보니...` |
| | ], |
| | creative_debater: [ |
| | `[${previousAgent.name}]님의 논점은 흥미롭지만, 조금 더 파격적인 접근은 어떨까요? 예를 들어...`, |
| | `토론을 더 생동감 있게 만들기 위해 제안을 드리자면...` |
| | ], |
| | debater_advisor: [ |
| | `[${previousAgent.name}]님의 논리적 지적은 타당합니다. 이를 바탕으로 실행 가능한 계획을 세운다면...`, |
| | `토론 내용을 실전에 적용하기 위한 구체적인 단계는...` |
| | ], |
| | advisor_analyst: [ |
| | `[${previousAgent.name}]님의 조언을 데이터로 검증해보았습니다. 결과는...`, |
| | `전략적 제안을 수치화해보니 몇 가지 흥미로운 점이 발견되었습니다...` |
| | ] |
| | }; |
| | |
| | const key = `${previousAgentId}_${agentId}`; |
| | if (interactionResponses[key]) { |
| | const randomIndex = Math.floor(Math.random() * interactionResponses[key].length); |
| | return interactionResponses[key][randomIndex]; |
| | } else { |
| | |
| | return generateAgentResponse(agentId, userMessage); |
| | } |
| | } |
| | |
| | |
| | function addMessage(sender, name, color, agentId, message) { |
| | const chatMessages = document.getElementById('chatMessages'); |
| | const messageDiv = document.createElement('div'); |
| | messageDiv.className = `message-animation flex ${sender === 'user' ? 'justify-end' : 'justify-start'}`; |
| | |
| | if (sender === 'user') { |
| | messageDiv.innerHTML = ` |
| | <div class="max-w-xs md:max-w-md lg:max-w-lg bg-gray-100 rounded-xl p-3"> |
| | <div class="text-xs text-gray-500 mb-1">${name}</div> |
| | <p>${message}</p> |
| | </div> |
| | `; |
| | } else { |
| | messageDiv.innerHTML = ` |
| | <div class="flex items-start"> |
| | <div class="w-8 h-8 rounded-full bg-${color}-200 flex items-center justify-center mr-2"> |
| | <i class="fas fa-${agents[agentId].icon} text-${color}-600"></i> |
| | </div> |
| | <div class="max-w-xs md:max-w-md lg:max-w-lg bg-${color}-50 rounded-xl p-3"> |
| | <div class="flex items-center"> |
| | <span class="font-medium text-${color}-800 mr-2">${name}</span> |
| | <span class="text-xs text-gray-500">${agents[agentId].description}</span> |
| | </div> |
| | <p class="mt-1">${message}</p> |
| | </div> |
| | </div> |
| | `; |
| | } |
| | |
| | chatMessages.appendChild(messageDiv); |
| | scrollChatToBottom(); |
| | } |
| | |
| | |
| | function showTypingIndicator(agentId) { |
| | const chatMessages = document.getElementById('chatMessages'); |
| | const agent = agents[agentId]; |
| | |
| | const typingDiv = document.createElement('div'); |
| | typingDiv.className = `flex justify-start typing-indicator-${agentId}`; |
| | typingDiv.innerHTML = ` |
| | <div class="flex items-start"> |
| | <div class="w-8 h-8 rounded-full bg-${agent.color}-200 flex items-center justify-center mr-2"> |
| | <i class="fas fa-${agent.icon} text-${agent.color}-600"></i> |
| | </div> |
| | <div class="bg-${agent.color}-50 rounded-xl p-3"> |
| | <div class="flex items-center"> |
| | <span class="font-medium text-${agent.color}-800 mr-2">${agent.name}</span> |
| | <span class="text-xs text-gray-500">${agent.description}</span> |
| | </div> |
| | <div class="typing-indicator mt-1"> |
| | <div class="typing-dot"></div> |
| | <div class="typing-dot"></div> |
| | <div class="typing-dot"></div> |
| | </div> |
| | </div> |
| | </div> |
| | `; |
| | |
| | chatMessages.appendChild(typingDiv); |
| | scrollChatToBottom(); |
| | } |
| | |
| | |
| | function removeTypingIndicator(agentId) { |
| | const indicators = document.querySelectorAll(`.typing-indicator-${agentId}`); |
| | indicators.forEach(indicator => indicator.remove()); |
| | } |
| | |
| | |
| | function scrollChatToBottom() { |
| | const chatContainer = document.getElementById('chatContainer'); |
| | chatContainer.scrollTop = chatContainer.scrollHeight; |
| | } |
| | |
| | |
| | document.getElementById('userInput').addEventListener('input', function() { |
| | updateCharCount(); |
| | }); |
| | |
| | function updateCharCount() { |
| | const input = document.getElementById('userInput'); |
| | const charCount = document.getElementById('charCount'); |
| | const count = input.value.length; |
| | charCount.textContent = count; |
| | |
| | if (count > 400) { |
| | charCount.classList.add('text-red-500'); |
| | charCount.classList.remove('text-gray-500'); |
| | } else { |
| | charCount.classList.remove('text-red-500'); |
| | charCount.classList.add('text-gray-500'); |
| | } |
| | } |
| | |
| | |
| | document.getElementById('userInput').addEventListener('keypress', function(e) { |
| | if (e.key === 'Enter') { |
| | sendMessage(); |
| | } |
| | }); |
| | </script> |
| | <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=appleorangejuice/multi-agent-chat" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| | </html> |