|
<!DOCTYPE html> |
|
<html lang="tr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>BF Chatbot Yöneticisi - Trek Bisiklet</title> |
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
height: 100vh; |
|
display: flex; |
|
overflow: hidden; |
|
} |
|
|
|
.sidebar { |
|
width: 350px; |
|
background: white; |
|
border-right: 1px solid #e4e6ea; |
|
display: flex; |
|
flex-direction: column; |
|
box-shadow: 2px 0 10px rgba(0,0,0,0.1); |
|
} |
|
|
|
.header { |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
color: white; |
|
padding: 20px; |
|
text-align: center; |
|
position: relative; |
|
} |
|
|
|
.header h1 { |
|
font-size: 20px; |
|
margin-bottom: 5px; |
|
} |
|
|
|
.header .subtitle { |
|
font-size: 12px; |
|
opacity: 0.9; |
|
} |
|
|
|
.refresh-indicator { |
|
position: absolute; |
|
top: 10px; |
|
right: 15px; |
|
width: 12px; |
|
height: 12px; |
|
border-radius: 50%; |
|
background: #4CAF50; |
|
animation: pulse 2s infinite; |
|
} |
|
|
|
.refresh-indicator.loading { |
|
background: #ff9800; |
|
animation: spin 1s linear infinite; |
|
} |
|
|
|
@keyframes pulse { |
|
0% { opacity: 1; } |
|
50% { opacity: 0.5; } |
|
100% { opacity: 1; } |
|
} |
|
|
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
|
|
.stats-bar { |
|
background: #f8f9fa; |
|
padding: 15px; |
|
border-bottom: 1px solid #e4e6ea; |
|
} |
|
|
|
.stats { |
|
display: flex; |
|
justify-content: space-around; |
|
} |
|
|
|
.stat-item { |
|
text-align: center; |
|
} |
|
|
|
.stat-value { |
|
font-size: 24px; |
|
font-weight: bold; |
|
color: #667eea; |
|
} |
|
|
|
.stat-label { |
|
font-size: 11px; |
|
color: #666; |
|
margin-top: 2px; |
|
} |
|
|
|
.search-bar { |
|
padding: 15px; |
|
border-bottom: 1px solid #e4e6ea; |
|
background: white; |
|
} |
|
|
|
.search-input { |
|
width: 100%; |
|
padding: 10px 15px; |
|
border: 1px solid #ddd; |
|
border-radius: 20px; |
|
outline: none; |
|
font-size: 14px; |
|
} |
|
|
|
.search-input:focus { |
|
border-color: #667eea; |
|
box-shadow: 0 0 5px rgba(102, 126, 234, 0.3); |
|
} |
|
|
|
.api-config { |
|
padding: 15px; |
|
border-bottom: 1px solid #e4e6ea; |
|
background: #f0f2f5; |
|
} |
|
|
|
.api-config input { |
|
width: 100%; |
|
padding: 8px; |
|
margin: 5px 0; |
|
border: 1px solid #ddd; |
|
border-radius: 4px; |
|
font-size: 12px; |
|
} |
|
|
|
.customer-list { |
|
flex: 1; |
|
overflow-y: auto; |
|
background: white; |
|
} |
|
|
|
.customer-item { |
|
padding: 15px 20px; |
|
border-bottom: 1px solid #f0f2f5; |
|
cursor: pointer; |
|
transition: background 0.2s; |
|
position: relative; |
|
} |
|
|
|
.customer-item:hover { |
|
background: #f8f9fa; |
|
} |
|
|
|
.customer-item.active { |
|
background: #e3f2fd; |
|
} |
|
|
|
.customer-name { |
|
font-size: 15px; |
|
font-weight: 600; |
|
margin-bottom: 4px; |
|
color: #333; |
|
} |
|
|
|
.customer-message { |
|
font-size: 13px; |
|
color: #666; |
|
overflow: hidden; |
|
white-space: nowrap; |
|
text-overflow: ellipsis; |
|
} |
|
|
|
.customer-time { |
|
position: absolute; |
|
top: 15px; |
|
right: 20px; |
|
font-size: 11px; |
|
color: #999; |
|
} |
|
|
|
.unread-badge { |
|
position: absolute; |
|
bottom: 15px; |
|
right: 20px; |
|
background: #667eea; |
|
color: white; |
|
font-size: 11px; |
|
padding: 2px 6px; |
|
border-radius: 10px; |
|
font-weight: bold; |
|
} |
|
|
|
.chat-area { |
|
flex: 1; |
|
display: flex; |
|
flex-direction: column; |
|
background: #e5ddd5; |
|
} |
|
|
|
.chat-header { |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
color: white; |
|
padding: 15px 20px; |
|
display: flex; |
|
align-items: center; |
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1); |
|
} |
|
|
|
.back-button { |
|
display: none; |
|
background: none; |
|
border: none; |
|
color: white; |
|
font-size: 24px; |
|
cursor: pointer; |
|
margin-right: 15px; |
|
padding: 5px; |
|
border-radius: 50%; |
|
transition: background 0.2s; |
|
} |
|
|
|
.back-button:hover { |
|
background: rgba(255,255,255,0.1); |
|
} |
|
|
|
.chat-header-info { |
|
flex: 1; |
|
} |
|
|
|
.chat-header .customer-name { |
|
font-size: 16px; |
|
font-weight: 600; |
|
margin-bottom: 2px; |
|
} |
|
|
|
.chat-header .customer-phone { |
|
font-size: 13px; |
|
opacity: 0.8; |
|
} |
|
|
|
.chat-messages { |
|
flex: 1; |
|
padding: 20px; |
|
overflow-y: auto; |
|
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%23e5ddd5"/><circle cx="50" cy="50" r="1" fill="%23d4cfc7" opacity="0.5"/></svg>'); |
|
} |
|
|
|
.message { |
|
margin-bottom: 10px; |
|
display: flex; |
|
align-items: flex-end; |
|
animation: fadeIn 0.3s ease-in; |
|
} |
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
.message.sent { |
|
justify-content: flex-end; |
|
} |
|
|
|
.message.received { |
|
justify-content: flex-start; |
|
} |
|
|
|
.message-bubble { |
|
max-width: 65%; |
|
padding: 8px 12px; |
|
border-radius: 18px; |
|
box-shadow: 0 1px 0.5px rgba(0,0,0,0.13); |
|
} |
|
|
|
.message.sent .message-bubble { |
|
background: white; |
|
margin-left: auto; |
|
border-bottom-right-radius: 4px; |
|
} |
|
|
|
.message.received .message-bubble { |
|
background: #d9fdd3; |
|
margin-right: auto; |
|
border-bottom-left-radius: 4px; |
|
} |
|
|
|
.message-text { |
|
margin-bottom: 4px; |
|
line-height: 1.4; |
|
white-space: pre-wrap; |
|
} |
|
|
|
.message-time { |
|
font-size: 11px; |
|
color: #667781; |
|
text-align: right; |
|
} |
|
|
|
.empty-state { |
|
flex: 1; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
flex-direction: column; |
|
color: #999; |
|
} |
|
|
|
.empty-state svg { |
|
width: 100px; |
|
height: 100px; |
|
margin-bottom: 20px; |
|
opacity: 0.5; |
|
} |
|
|
|
|
|
.product-mention { |
|
color: #667eea; |
|
font-weight: bold; |
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
.sidebar { |
|
width: 100%; |
|
position: absolute; |
|
z-index: 10; |
|
transition: transform 0.3s; |
|
} |
|
|
|
.sidebar.hidden { |
|
transform: translateX(-100%); |
|
} |
|
|
|
.chat-area { |
|
width: 100%; |
|
} |
|
|
|
.back-button { |
|
display: block !important; |
|
} |
|
} |
|
|
|
|
|
::-webkit-scrollbar { |
|
width: 6px; |
|
height: 6px; |
|
} |
|
|
|
::-webkit-scrollbar-track { |
|
background: #f1f1f1; |
|
} |
|
|
|
::-webkit-scrollbar-thumb { |
|
background: #888; |
|
border-radius: 3px; |
|
} |
|
|
|
::-webkit-scrollbar-thumb:hover { |
|
background: #555; |
|
} |
|
|
|
.connection-status { |
|
position: fixed; |
|
bottom: 20px; |
|
right: 20px; |
|
background: white; |
|
padding: 10px 20px; |
|
border-radius: 20px; |
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
|
display: flex; |
|
align-items: center; |
|
gap: 10px; |
|
font-size: 12px; |
|
z-index: 1000; |
|
} |
|
|
|
.connection-status.connected { |
|
background: #4CAF50; |
|
color: white; |
|
} |
|
|
|
.connection-status.disconnected { |
|
background: #f44336; |
|
color: white; |
|
} |
|
|
|
.connection-status.connecting { |
|
background: #ff9800; |
|
color: white; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
|
|
<div class="sidebar" id="sidebar"> |
|
<div class="header"> |
|
<div class="refresh-indicator" id="refreshIndicator"></div> |
|
<h1>🚴 Trek BF Chatbot</h1> |
|
<div class="subtitle">Konuşma Yöneticisi</div> |
|
</div> |
|
|
|
<div class="stats-bar"> |
|
<div class="stats"> |
|
<div class="stat-item"> |
|
<div class="stat-value" id="totalConversations">0</div> |
|
<div class="stat-label">Toplam</div> |
|
</div> |
|
<div class="stat-item"> |
|
<div class="stat-value" id="todayConversations">0</div> |
|
<div class="stat-label">Bugün</div> |
|
</div> |
|
<div class="stat-item"> |
|
<div class="stat-value" id="activeConversations">0</div> |
|
<div class="stat-label">Aktif</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="search-bar"> |
|
<input type="text" class="search-input" placeholder="Konuşmalarda ara..." id="searchInput"> |
|
</div> |
|
|
|
<div class="api-config"> |
|
<input type="text" id="apiEndpoint" placeholder="API Endpoint (örn: https://your-space.hf.space)" value=""> |
|
<small style="color: #666;">Hugging Face Space URL'nizi girin</small> |
|
</div> |
|
|
|
<div class="customer-list" id="customerList"> |
|
|
|
</div> |
|
</div> |
|
|
|
|
|
<div class="chat-area"> |
|
<div class="chat-header" id="chatHeader" style="display: none;"> |
|
<button class="back-button" onclick="showSidebar()">←</button> |
|
<div class="chat-header-info"> |
|
<div class="customer-name" id="chatCustomerName">Müşteri</div> |
|
<div class="customer-phone" id="chatCustomerPhone">Oturum</div> |
|
</div> |
|
</div> |
|
|
|
<div class="chat-messages" id="chatMessages"> |
|
<div class="empty-state"> |
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"> |
|
<path d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> |
|
</svg> |
|
<div>Bir konuşma seçin</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="connection-status" id="connectionStatus"> |
|
<span id="statusText">Bağlanıyor...</span> |
|
</div> |
|
|
|
<script> |
|
|
|
let API_ENDPOINT = localStorage.getItem('bf_api_endpoint') || ''; |
|
|
|
let conversations = {}; |
|
let selectedCustomer = null; |
|
let refreshInterval; |
|
let filteredConversations = {}; |
|
|
|
|
|
document.getElementById('apiEndpoint').value = API_ENDPOINT; |
|
document.getElementById('apiEndpoint').addEventListener('change', function() { |
|
API_ENDPOINT = this.value.trim(); |
|
if (!API_ENDPOINT.endsWith('/')) { |
|
API_ENDPOINT += '/'; |
|
} |
|
localStorage.setItem('bf_api_endpoint', API_ENDPOINT); |
|
loadConversations(); |
|
}); |
|
|
|
function updateConnectionStatus(status) { |
|
const statusEl = document.getElementById('connectionStatus'); |
|
const statusText = document.getElementById('statusText'); |
|
|
|
statusEl.className = 'connection-status'; |
|
|
|
switch(status) { |
|
case 'connected': |
|
statusEl.classList.add('connected'); |
|
statusText.textContent = '✓ Bağlandı'; |
|
setTimeout(() => statusEl.style.display = 'none', 3000); |
|
break; |
|
case 'disconnected': |
|
statusEl.classList.add('disconnected'); |
|
statusText.textContent = '✗ Bağlantı koptu'; |
|
statusEl.style.display = 'flex'; |
|
break; |
|
case 'connecting': |
|
statusEl.classList.add('connecting'); |
|
statusText.textContent = '⟳ Bağlanıyor...'; |
|
statusEl.style.display = 'flex'; |
|
break; |
|
} |
|
} |
|
|
|
async function loadConversations() { |
|
if (!API_ENDPOINT) { |
|
updateConnectionStatus('disconnected'); |
|
return; |
|
} |
|
|
|
const indicator = document.getElementById('refreshIndicator'); |
|
indicator.classList.add('loading'); |
|
updateConnectionStatus('connecting'); |
|
|
|
try { |
|
const response = await fetch(API_ENDPOINT + 'api/conversations'); |
|
if (!response.ok) throw new Error('API yanıt vermedi'); |
|
|
|
conversations = await response.json(); |
|
filteredConversations = {...conversations}; |
|
|
|
|
|
const statsResponse = await fetch(API_ENDPOINT + 'api/stats'); |
|
if (statsResponse.ok) { |
|
const stats = await statsResponse.json(); |
|
document.getElementById('totalConversations').textContent = stats.total || 0; |
|
document.getElementById('todayConversations').textContent = stats.today || 0; |
|
document.getElementById('activeConversations').textContent = Object.keys(conversations).length; |
|
} |
|
|
|
renderCustomerList(); |
|
updateConnectionStatus('connected'); |
|
} catch (error) { |
|
console.error('API Hatası:', error); |
|
updateConnectionStatus('disconnected'); |
|
} finally { |
|
indicator.classList.remove('loading'); |
|
} |
|
} |
|
|
|
function formatTime(dateString) { |
|
const date = new Date(dateString); |
|
const now = new Date(); |
|
const diff = now - date; |
|
|
|
if (diff < 60000) return 'şimdi'; |
|
if (diff < 3600000) return Math.floor(diff / 60000) + ' dk'; |
|
if (diff < 86400000) return Math.floor(diff / 3600000) + ' sa'; |
|
|
|
return date.toLocaleDateString('tr-TR'); |
|
} |
|
|
|
function highlightProducts(text) { |
|
const products = ['MADONE', 'MARLIN', 'DOMANE', 'CHECKPOINT', 'EMONDA', 'FX', 'RAIL', 'POWERFLY']; |
|
let result = text; |
|
products.forEach(product => { |
|
const regex = new RegExp(`(${product}[^\\s]*)`, 'gi'); |
|
result = result.replace(regex, '<span class="product-mention">$1</span>'); |
|
}); |
|
return result; |
|
} |
|
|
|
function renderCustomerList() { |
|
const listEl = document.getElementById('customerList'); |
|
listEl.innerHTML = ''; |
|
|
|
Object.entries(filteredConversations).forEach(([phone, data]) => { |
|
const lastMessage = data.messages[data.messages.length - 1]; |
|
if (!lastMessage) return; |
|
|
|
const item = document.createElement('div'); |
|
item.className = 'customer-item'; |
|
if (phone === selectedCustomer) { |
|
item.classList.add('active'); |
|
} |
|
|
|
item.innerHTML = ` |
|
<div class="customer-name">Kullanıcı ${phone.substr(-4)}</div> |
|
<div class="customer-message">${lastMessage.text.substring(0, 50)}...</div> |
|
<div class="customer-time">${formatTime(lastMessage.time)}</div> |
|
`; |
|
|
|
item.onclick = () => selectCustomer(phone); |
|
listEl.appendChild(item); |
|
}); |
|
} |
|
|
|
function selectCustomer(phone) { |
|
selectedCustomer = phone; |
|
const customerData = conversations[phone]; |
|
|
|
|
|
document.getElementById('chatHeader').style.display = 'flex'; |
|
document.getElementById('chatCustomerName').textContent = `Kullanıcı ${phone.substr(-4)}`; |
|
document.getElementById('chatCustomerPhone').textContent = phone; |
|
|
|
|
|
const messagesEl = document.getElementById('chatMessages'); |
|
messagesEl.innerHTML = ''; |
|
|
|
customerData.messages.forEach(msg => { |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = `message ${msg.type}`; |
|
|
|
messageDiv.innerHTML = ` |
|
<div class="message-bubble"> |
|
<div class="message-text">${highlightProducts(msg.text)}</div> |
|
<div class="message-time">${formatTime(msg.time)}</div> |
|
</div> |
|
`; |
|
|
|
messagesEl.appendChild(messageDiv); |
|
}); |
|
|
|
|
|
messagesEl.scrollTop = messagesEl.scrollHeight; |
|
|
|
|
|
renderCustomerList(); |
|
|
|
|
|
if (window.innerWidth <= 768) { |
|
document.getElementById('sidebar').classList.add('hidden'); |
|
} |
|
} |
|
|
|
function showSidebar() { |
|
document.getElementById('sidebar').classList.remove('hidden'); |
|
} |
|
|
|
|
|
document.getElementById('searchInput').addEventListener('input', function() { |
|
const searchTerm = this.value.toLowerCase(); |
|
|
|
if (!searchTerm) { |
|
filteredConversations = {...conversations}; |
|
} else { |
|
filteredConversations = {}; |
|
Object.entries(conversations).forEach(([phone, data]) => { |
|
const hasMatch = data.messages.some(msg => |
|
msg.text.toLowerCase().includes(searchTerm) |
|
); |
|
if (hasMatch) { |
|
filteredConversations[phone] = data; |
|
} |
|
}); |
|
} |
|
|
|
renderCustomerList(); |
|
}); |
|
|
|
|
|
function startAutoRefresh() { |
|
refreshInterval = setInterval(loadConversations, 10000); |
|
} |
|
|
|
function stopAutoRefresh() { |
|
if (refreshInterval) { |
|
clearInterval(refreshInterval); |
|
} |
|
} |
|
|
|
|
|
if (API_ENDPOINT) { |
|
loadConversations(); |
|
startAutoRefresh(); |
|
} else { |
|
updateConnectionStatus('disconnected'); |
|
} |
|
|
|
|
|
document.addEventListener('visibilitychange', function() { |
|
if (document.hidden) { |
|
stopAutoRefresh(); |
|
} else { |
|
loadConversations(); |
|
startAutoRefresh(); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |