demo / ui-temp.html
chansung's picture
Upload folder using huggingface_hub
50e7067 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Chat UI with Per‑Session Summary, Settings & History</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap" rel="stylesheet">
<!-- Include marked.js for Markdown rendering -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
/* Global Styles */
* { box-sizing: border-box; }
body {
margin: 0;
padding: 0;
font-family: 'Poppins', sans-serif;
background: #e0e0e0;
overflow: hidden;
}
/* Overall App Container: Left Nav and Chat Area */
.app-container {
display: flex;
width: 100vw;
height: 100vh;
transition: all 0.3s ease;
}
/* Left Navigation Bar */
.nav-bar {
width: 300px;
background: #ffffff;
padding: 20px;
box-shadow: 2px 0 12px rgba(0,0,0,0.1);
overflow-y: auto;
transition: width 0.3s ease, padding 0.3s ease;
}
.nav-bar.hidden {
display: none;
}
.nav-bar h3 {
margin-top: 0;
font-size: 1.3em;
}
.nav-bar ul {
list-style: none;
padding: 0;
margin: 0;
}
.nav-bar li {
padding: 10px;
margin-bottom: 10px;
background: #fff;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
transition: background 0.3s;
font-size: 1.1em;
}
.nav-bar li.active,
.nav-bar li:hover {
background: #667eea;
color: #fff;
}
.session-actions {
display: inline-flex;
gap: 5px;
margin-left: 10px;
}
.session-summary-btn,
.session-settings-btn {
background: transparent;
border: none;
cursor: pointer;
font-size: 1.2em;
}
.session-summary-btn:hover,
.session-settings-btn:hover {
color: #667eea;
}
.nav-bar li button.remove-session {
background: transparent;
border: none;
color: inherit;
font-size: 1em;
cursor: pointer;
margin-left: auto;
}
.new-session-btn {
width: 100%;
padding: 10px;
background: #667eea;
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1.1em;
margin-top: 10px;
}
/* Chat Wrapper */
.chat-wrapper {
flex: 1;
background: #fff;
margin: 20px;
border-radius: 20px;
box-shadow: 0 12px 40px rgba(0,0,0,0.15);
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
transition: width 0.3s ease;
}
/* Hamburger Button inside Chat Wrapper */
#hamburgerBtn {
position: absolute;
top: 20px;
left: 20px;
background: #667eea;
color: #fff;
border: none;
padding: 10px;
font-size: 1.4em;
border-radius: 4px;
cursor: pointer;
z-index: 20;
}
/* Turn Label */
#turnLabel {
position: absolute;
top: 20px;
right: 20px;
background: #ff9800;
color: #fff;
padding: 5px 10px;
border-radius: 10px;
font-size: 1em;
z-index: 15;
}
/* Carousel Area */
.carousel-wrapper {
position: relative;
flex: 1;
background: #F9FBFD;
overflow: hidden;
margin-top: 80px;
}
.carousel {
display: flex;
height: 100%;
transition: transform 0.5s ease;
}
.card {
min-width: 100%;
height: 100%;
padding: 90px 30px 30px 30px;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.conversation {
display: flex;
flex-direction: column;
gap: 15px;
}
/* Message Bubbles */
.message {
padding: 10px 16px;
border-radius: 16px;
font-size: 1em;
line-height: 1.5;
max-width: 95%;
position: relative;
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
transition: background 0.3s, transform 0.3s;
}
.user { background: #F0F4FF; border: 1px solid #D9E2FF; align-self: flex-start; }
.ai { background: #FFF4E6; border: 1px solid #FFE0B2; align-self: flex-end; }
.message-text {
display: block;
max-height: 80px;
overflow: hidden;
transition: max-height 0.3s ease;
}
.message.expanded .message-text { max-height: none; }
.toggle-btn {
background: none;
border: none;
color: #667eea;
cursor: pointer;
font-size: 0.85em;
margin-top: 4px;
padding: 0;
}
.vertical-file-list {
margin-bottom: 10px;
display: flex;
flex-direction: column;
gap: 5px;
}
.file-item-vertical {
background: #eaeaea;
padding: 5px 8px;
border-radius: 4px;
font-size: 0.9em;
}
/* Navigation Buttons */
.nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 50px;
height: 50px;
background: rgba(255,255,255,0.5);
backdrop-filter: blur(4px);
border: none;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
transition: transform 0.3s, background 0.3s;
}
.nav:hover { transform: translateY(-50%) scale(1.1); background: rgba(255,255,255,0.8); }
.nav:disabled { opacity: 0.5; cursor: default; }
#prevBtn { left: 15px; }
#nextBtn { right: 15px; }
/* Input Section */
.input-container {
padding: 20px;
background: #FAFAFA;
border-top: 1px solid #EEE;
display: flex;
flex-direction: column;
}
.file-attachments {
display: flex;
gap: 10px;
overflow-x: auto;
padding-bottom: 10px;
scrollbar-width: thin;
}
.file-item {
flex: 0 0 auto;
background: #eee;
padding: 5px 10px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 5px;
font-size: 0.9em;
}
.file-item button {
background: none;
border: none;
color: #667eea;
cursor: pointer;
font-size: 1em;
}
.input-row {
display: flex;
align-items: center;
gap: 10px;
}
.attach-button {
background: #fff;
border: 1px solid #DDD;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
transition: background 0.3s;
}
.attach-button:hover { background: #f0f0f0; }
.input-container textarea {
flex: 1;
padding: 12px 15px;
font-size: 1em;
border: 1px solid #DDD;
border-radius: 8px;
outline: none;
resize: none;
overflow-y: auto;
min-height: 36px;
max-height: 150px;
line-height: 1.4em;
white-space: pre-wrap;
transition: border-color 0.3s;
}
.input-container textarea:focus { border-color: #667eea; }
.input-container button {
padding: 12px 20px;
font-size: 1em;
background: #667eea;
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background 0.3s;
}
.input-container button:hover { background: #556cd6; }
#fileInput { display: none; }
/* Summary Overlay Panel (Per‑Session, rendered markdown) */
#summaryOverlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 60%;
background: #fff;
box-shadow: -4px 0 12px rgba(0,0,0,0.15);
transform: translateX(100%);
transition: transform 0.3s ease;
z-index: 20;
display: flex;
flex-direction: column;
}
#summaryOverlay.active { transform: translateX(0); }
.summary-header {
padding: 16px;
background: #667eea;
color: #fff;
font-size: 1.2em;
display: flex;
justify-content: space-between;
align-items: center;
}
.summary-header-buttons {
display: flex;
gap: 10px;
}
.download-summary {
background: #fff;
color: #667eea;
border: 1px solid #667eea;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
}
.download-summary:hover { background: #667eea; color: #fff; }
.close-summary {
background: none;
border: none;
color: #fff;
font-size: 1.2em;
cursor: pointer;
}
.summary-content {
padding: 16px;
overflow-y: auto;
flex: 1;
}
/* Settings Overlay Panel (Per‑Session) */
#settingsOverlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 40%;
background: #fff;
box-shadow: -4px 0 12px rgba(0,0,0,0.15);
transform: translateX(100%);
transition: transform 0.3s ease;
z-index: 20;
display: flex;
flex-direction: column;
}
#settingsOverlay.active { transform: translateX(0); }
.settings-header {
padding: 16px;
background: #667eea;
color: #fff;
font-size: 1.4em;
display: flex;
justify-content: space-between;
align-items: center;
}
.close-settings {
background: none;
border: none;
color: #fff;
font-size: 1.4em;
cursor: pointer;
}
.settings-content {
padding: 16px;
overflow-y: auto;
flex: 1;
font-size: 1.1em;
line-height: 1.4;
}
.settings-group {
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 10px;
}
.settings-group label {
min-width: 100px;
}
.save-settings {
background: #667eea;
color: #fff;
border: none;
border-radius: 8px;
padding: 10px 20px;
cursor: pointer;
}
.save-settings:hover { background: #556cd6; }
/* Responsive */
@media (max-width: 600px) {
.nav-bar { display: none; }
.chat-wrapper { margin: 0; }
.message { font-size: 0.95em; max-width: 80%; }
.nav { width: 40px; height: 40px; font-size: 1.5em; }
.card { padding: 90px 20px 20px 20px; }
.input-container { padding: 15px; }
.input-row { flex-direction: column; gap: 10px; }
.input-container button { width: 100%; }
#summaryOverlay, #settingsOverlay { width: 80%; }
}
</style>
</head>
<body>
<div class="app-container">
<!-- Left Nav Bar for Chat History & Session Management -->
<div class="nav-bar" id="navBar">
<h3>Chat History</h3>
<ul id="sessionList"></ul>
<button class="new-session-btn" id="newSessionBtn">New Chat</button>
</div>
<!-- Main Chat Wrapper -->
<div class="chat-wrapper">
<!-- Hamburger Button (placed inside chat area) -->
<button id="hamburgerBtn">&#9776;</button>
<!-- Turn Label -->
<div id="turnLabel">Turn: 0/0</div>
<div class="carousel-wrapper">
<button id="prevBtn" class="nav">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button>
<div class="carousel" id="carousel">
<!-- Conversation cards will be dynamically rendered -->
</div>
<button id="nextBtn" class="nav">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#444" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
</div>
<!-- Input Section -->
<div class="input-container">
<div id="fileAttachments" class="file-attachments"></div>
<div class="input-row">
<button id="attachBtn" class="attach-button">Attach File</button>
<input type="file" id="fileInput" multiple accept="image/*,.pdf">
<textarea id="chatInput" placeholder="Type your message..."></textarea>
<button id="sendBtn">Send</button>
</div>
</div>
</div>
</div>
<!-- Summary Overlay Panel (Per‑Session, rendered markdown) -->
<div id="summaryOverlay">
<div class="summary-header">
<span>Chat Summary</span>
<div class="summary-header-buttons">
<button id="downloadSummary" class="download-summary">Download</button>
<button class="close-summary" id="closeSummaryBtn">&times;</button>
</div>
</div>
<div class="summary-content" id="summaryContent">
<!-- Rendered markdown will appear here -->
</div>
</div>
<!-- Settings Overlay Panel (Per‑Session) -->
<div id="settingsOverlay">
<div class="settings-header">
<span>Settings</span>
<button class="close-settings" id="closeSettingsBtn">&times;</button>
</div>
<div class="settings-content">
<form id="settingsForm">
<div class="settings-group">
<label for="temperature">Temperature:</label>
<input type="range" id="temperature" name="temperature" min="0" max="1" step="0.01">
<span id="temperatureValue"></span>
</div>
<div class="settings-group">
<label for="maxTokens">Max Tokens:</label>
<input type="number" id="maxTokens" name="maxTokens" min="10" max="2048">
</div>
<div class="settings-group">
<label for="persona">Persona:</label>
<select id="persona" name="persona">
<option value="professional">Professional</option>
<option value="friendly">Friendly</option>
</select>
</div>
<button type="button" id="saveSettings" class="save-settings">Save Settings</button>
</form>
</div>
</div>
<script>
// ----------------- Session Management -----------------
let sessions = [];
let currentSessionIndex = 0;
let currentCardIndex = 0;
// Initialize with a default session
function initSessions() {
sessions.push({
id: Date.now(),
name: "Chat Session 1",
messages: [],
summary: "# Chat Summary\n\nThis is the default summary for Chat Session 1.",
settings: { temperature: 0.7, maxTokens: 256, persona: "professional" }
});
currentSessionIndex = 0;
currentCardIndex = 0;
renderSessionList();
renderCurrentSession();
}
// Render the left nav bar session list with icons
function renderSessionList() {
const sessionList = document.getElementById('sessionList');
sessionList.innerHTML = "";
sessions.forEach((session, index) => {
const li = document.createElement('li');
// Session name
const nameSpan = document.createElement('span');
nameSpan.textContent = session.name;
li.appendChild(nameSpan);
// Action icons container
const actionsDiv = document.createElement('div');
actionsDiv.className = "session-actions";
const summaryBtn = document.createElement('button');
summaryBtn.className = "session-summary-btn";
summaryBtn.innerHTML = "📄";
summaryBtn.addEventListener('click', (e) => {
e.stopPropagation();
currentSessionIndex = index;
renderSessionList();
renderCurrentSession();
// Open summary overlay for this session
document.getElementById('summaryContent').innerHTML = marked.parse(sessions[currentSessionIndex].summary);
summaryOverlay.classList.add('active');
settingsOverlay.classList.remove('active');
});
actionsDiv.appendChild(summaryBtn);
const settingsBtn = document.createElement('button');
settingsBtn.className = "session-settings-btn";
settingsBtn.innerHTML = "⚙";
settingsBtn.addEventListener('click', (e) => {
e.stopPropagation();
currentSessionIndex = index;
renderSessionList();
renderCurrentSession();
// Open settings overlay for this session
const settings = sessions[currentSessionIndex].settings;
temperatureInput.value = settings.temperature;
temperatureValue.textContent = settings.temperature;
maxTokensInput.value = settings.maxTokens;
personaSelect.value = settings.persona;
settingsOverlay.classList.add('active');
summaryOverlay.classList.remove('active');
});
actionsDiv.appendChild(settingsBtn);
li.appendChild(actionsDiv);
// Remove session button
const removeBtn = document.createElement('button');
removeBtn.textContent = "×";
removeBtn.className = "remove-session";
removeBtn.addEventListener('click', (e) => {
e.stopPropagation();
removeSession(index);
});
li.appendChild(removeBtn);
li.addEventListener('click', () => {
currentSessionIndex = index;
currentCardIndex = 0;
renderSessionList();
renderCurrentSession();
});
if (index === currentSessionIndex) li.classList.add('active');
sessionList.appendChild(li);
});
}
// Create a new session
document.getElementById('newSessionBtn').addEventListener('click', () => {
const newSession = {
id: Date.now(),
name: "Chat Session " + (sessions.length + 1),
messages: [],
summary: "# Chat Summary\n\nThis is the default summary for Chat Session " + (sessions.length + 1) + ".",
settings: { temperature: 0.7, maxTokens: 256, persona: "professional" }
};
sessions.push(newSession);
currentSessionIndex = sessions.length - 1;
currentCardIndex = 0;
renderSessionList();
renderCurrentSession();
});
// Remove a session
function removeSession(index) {
sessions.splice(index, 1);
if (sessions.length === 0) {
initSessions();
} else {
if (currentSessionIndex >= sessions.length) {
currentSessionIndex = sessions.length - 1;
}
currentCardIndex = 0;
}
renderSessionList();
renderCurrentSession();
}
// ----------------- Carousel Rendering -----------------
const carousel = document.getElementById('carousel');
function renderCurrentSession() {
const session = sessions[currentSessionIndex];
carousel.innerHTML = "";
session.messages.forEach(message => {
const card = document.createElement('div');
card.className = 'card';
let attachmentHTML = "";
if (message.attachments && message.attachments.length > 0) {
attachmentHTML = `<div class="vertical-file-list">` +
message.attachments.map(name => `<div class="file-item-vertical">${name}</div>`).join("") +
`</div>`;
}
card.innerHTML = `
<div class="conversation">
<div class="message user">
${attachmentHTML}
<span class="message-text">User: ${message.userText}</span>
</div>
<div class="message ai">
<span class="message-text">AI: ${message.aiResponse}</span>
</div>
</div>
`;
carousel.appendChild(card);
processMessagesInContainer(card);
});
currentCardIndex = session.messages.length > 0 ? session.messages.length - 1 : 0;
updateCarousel();
}
function updateCarousel() {
const cards = document.querySelectorAll('.card');
carousel.style.transform = `translateX(-${currentCardIndex * 100}%)`;
prevBtn.disabled = currentCardIndex === 0;
nextBtn.disabled = currentCardIndex === cards.length - 1 || cards.length === 0;
updateTurnLabel(cards.length);
}
function updateTurnLabel(totalCards) {
const turnLabel = document.getElementById('turnLabel');
turnLabel.textContent = `Turn: ${totalCards ? currentCardIndex + 1 + "/" + totalCards : "0/0"}`;
}
// ----------------- Message Processing -----------------
function processMessage(messageEl) {
if (messageEl.dataset.processed) return;
const textEl = messageEl.querySelector('.message-text');
if (textEl.scrollHeight > 80) {
const toggleBtn = document.createElement('button');
toggleBtn.className = 'toggle-btn';
toggleBtn.textContent = 'Read more';
toggleBtn.addEventListener('click', function() {
if (messageEl.classList.contains('expanded')) {
messageEl.classList.remove('expanded');
toggleBtn.textContent = 'Read more';
} else {
messageEl.classList.add('expanded');
toggleBtn.textContent = 'Read less';
}
});
messageEl.appendChild(toggleBtn);
}
messageEl.dataset.processed = 'true';
}
function processMessagesInContainer(container) {
container.querySelectorAll('.message').forEach(processMessage);
}
// ----------------- Adding Conversation -----------------
const attachedFiles = []; // For demo, store file objects (only names)
function addConversation(userText, aiResponse) {
const attachmentNames = attachedFiles.map(file => file.name);
const message = {
userText,
aiResponse,
attachments: attachmentNames
};
sessions[currentSessionIndex].messages.push(message);
clearFileAttachments();
renderCurrentSession();
}
function clearFileAttachments() {
attachedFiles.length = 0;
updateFileAttachments();
}
// ----------------- Event Listeners -----------------
const sendBtn = document.getElementById('sendBtn');
const chatInput = document.getElementById('chatInput');
sendBtn.addEventListener('click', () => {
const text = chatInput.value;
if (text.trim() !== '') {
const aiResponse = "I'm here to help!";
addConversation(text, aiResponse);
chatInput.value = '';
chatInput.style.height = "36px";
}
});
chatInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
sendBtn.click();
}
});
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
prevBtn.addEventListener('click', () => {
if (currentCardIndex > 0) { currentCardIndex--; updateCarousel(); }
});
nextBtn.addEventListener('click', () => {
const cards = document.querySelectorAll('.card');
if (currentCardIndex < cards.length - 1) { currentCardIndex++; updateCarousel(); }
});
// ----------------- File Attachment Handling -----------------
const attachBtn = document.getElementById('attachBtn');
const fileInput = document.getElementById('fileInput');
const fileAttachments = document.getElementById('fileAttachments');
attachBtn.addEventListener('click', () => { fileInput.click(); });
fileInput.addEventListener('change', () => {
for (const file of fileInput.files) { attachedFiles.push(file); }
fileInput.value = "";
updateFileAttachments();
});
function updateFileAttachments() {
fileAttachments.innerHTML = "";
attachedFiles.forEach((file, index) => {
const fileDiv = document.createElement("div");
fileDiv.className = "file-item";
fileDiv.innerHTML = `<span>${file.name}</span> <button data-index="${index}">&times;</button>`;
fileAttachments.appendChild(fileDiv);
});
document.querySelectorAll(".file-item button").forEach(btn => {
btn.addEventListener("click", (e) => {
const idx = e.target.getAttribute("data-index");
attachedFiles.splice(idx, 1);
updateFileAttachments();
});
});
}
// ----------------- Summary Overlay (Per‑Session) -----------------
const summaryOverlay = document.getElementById('summaryOverlay');
const closeSummaryBtn = document.getElementById('closeSummaryBtn');
const summaryContent = document.getElementById('summaryContent');
const downloadSummaryBtn = document.getElementById('downloadSummary');
closeSummaryBtn.addEventListener('click', () => { summaryOverlay.classList.remove('active'); });
downloadSummaryBtn.addEventListener('click', () => {
const blob = new Blob([sessions[currentSessionIndex].summary], { type: "text/markdown" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "summary.md";
a.click();
URL.revokeObjectURL(url);
});
// ----------------- Settings Overlay (Per‑Session) -----------------
const settingsOverlay = document.getElementById('settingsOverlay');
const closeSettingsBtn = document.getElementById('closeSettingsBtn');
const temperatureInput = document.getElementById('temperature');
const temperatureValue = document.getElementById('temperatureValue');
const maxTokensInput = document.getElementById('maxTokens');
const personaSelect = document.getElementById('persona');
const saveSettingsBtn = document.getElementById('saveSettings');
closeSettingsBtn.addEventListener('click', () => { settingsOverlay.classList.remove('active'); });
temperatureInput.addEventListener('input', () => {
temperatureValue.textContent = temperatureInput.value;
});
saveSettingsBtn.addEventListener('click', () => {
sessions[currentSessionIndex].settings = {
temperature: parseFloat(temperatureInput.value),
maxTokens: parseInt(maxTokensInput.value),
persona: personaSelect.value
};
console.log('Session settings saved:', sessions[currentSessionIndex].settings);
settingsOverlay.classList.remove('active');
});
// ----------------- Auto-Dismiss Overlays on Outside Click -----------------
document.addEventListener('click', (e) => {
if (summaryOverlay.classList.contains('active') &&
!summaryOverlay.contains(e.target)) {
summaryOverlay.classList.remove('active');
}
if (settingsOverlay.classList.contains('active') &&
!settingsOverlay.contains(e.target)) {
settingsOverlay.classList.remove('active');
}
});
// ----------------- Global Keyboard Navigation -----------------
document.addEventListener('keydown', (e) => {
if (document.activeElement !== chatInput) {
if (e.key === 'ArrowLeft' && currentCardIndex > 0) {
currentCardIndex--;
updateCarousel();
} else if (e.key === 'ArrowRight') {
const cards = document.querySelectorAll('.card');
if (currentCardIndex < cards.length - 1) {
currentCardIndex++;
updateCarousel();
}
}
}
});
// ----------------- Hamburger Button (Toggle Nav Bar) -----------------
const hamburgerBtn = document.getElementById('hamburgerBtn');
const navBar = document.getElementById('navBar');
hamburgerBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (navBar.classList.contains('hidden')) {
navBar.classList.remove('hidden');
} else {
navBar.classList.add('hidden');
}
});
// ----------------- Initialization -----------------
initSessions();
</script>
</body>
</html>