multi-agent-chat / index.html
appleorangejuice's picture
멀티 에이전트 시스템과 대화를 나눌 수 있는 실험적인 채팅 인터페이스를 만들어줘. - Initial Deployment
d82b099 verified
<!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();
}
// 선택된 에이전트 UI 업데이트
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>