IMPROVEMENT: page switching system based on navbar now works
This commit is contained in:
parent
00fa5ae75a
commit
d56ac63c43
111
index.html
111
index.html
@ -11,7 +11,7 @@
|
||||
<body>
|
||||
<div class="container py-4 chat-wrap">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark rounded mb-3 px-3">
|
||||
<a class="navbar-brand" href="#">FMF GPT</a>
|
||||
<a class="navbar-brand" href="#">FMF-GPT</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#topNav" aria-controls="topNav"
|
||||
@ -20,12 +20,12 @@
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="topNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0 nav-tab">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#chat">Chat</a>
|
||||
<a class="nav-link active" href="#chatPage" data-page="chatPage">Chat</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#systemAcc">System</a>
|
||||
<a class="nav-link" href="#systemAcc" data-page="systemAcc">System</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -37,58 +37,63 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="accordion mb-3" id="systemAcc">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingOne">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne">
|
||||
System prompt (optional)
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseOne" class="accordion-collapse collapse" aria-labelledby="headingOne" data-bs-parent="#systemAcc">
|
||||
<div class="accordion-body">
|
||||
<textarea id="systemInput" class="form-control" rows="3" placeholder="You are a concise assistant that..."><?= $systemPrefill ?></textarea>
|
||||
<div class="form-text">Saved in the session and applied to each message until changed.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chat" class="chat-box p-3 mb-3 text-white">
|
||||
<?php if (!$history): ?>
|
||||
<div class="text-secondary">Start the conversation below…</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($history as $m): ?>
|
||||
<?php
|
||||
$role = htmlspecialchars($m['role'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$ts = (int)($m['ts'] ?? time());
|
||||
?>
|
||||
<div class="msg <?= $role ?>">
|
||||
<div class="content">
|
||||
<?php if ($role === 'assistant' && !empty($m['content_html'])): ?>
|
||||
<!-- Assistant: already sanitized HTML -->
|
||||
<?= $m['content_html'] ?>
|
||||
<?php else: ?>
|
||||
<!-- User (or legacy items): escape + preserve newlines -->
|
||||
<?= nl2br(htmlspecialchars($m['content'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), false) ?>
|
||||
<?php endif; ?>
|
||||
<div id="pageWrapper" class="tab-content">
|
||||
<div class="tab-pane page" id="systemAcc" role="tabpanel">
|
||||
<div class="accordion mb-3">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingOne">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne">
|
||||
System prompt (optional)
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseOne" class="accordion-collapse collapse" aria-labelledby="headingOne" data-bs-parent="#systemAcc">
|
||||
<div class="accordion-body">
|
||||
<textarea id="systemInput" class="form-control" rows="3" placeholder="You are a concise assistant that..."><?= $systemPrefill ?></textarea>
|
||||
<div class="form-text">Saved in the session and applied to each message until changed.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time"><?= date('H:i', $ts) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /#systemAcc -->
|
||||
|
||||
<form id="chatForm" class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<textarea id="messageInput" class="form-control" rows="3" placeholder="Ask something… (Shift+Enter for newline)" required></textarea>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button id="sendBtn" type="submit" class="btn btn-primary">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div id="chatPage" class="tab-pane page active show">
|
||||
<div id="chat" class="chat-box p-3 mb-3 text-white">
|
||||
<?php if (!$history): ?>
|
||||
<div class="text-secondary">Start the conversation below…</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($history as $m): ?>
|
||||
<?php
|
||||
$role = htmlspecialchars($m['role'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$ts = (int)($m['ts'] ?? time());
|
||||
?>
|
||||
<div class="msg <?= $role ?>">
|
||||
<div class="content">
|
||||
<?php if ($role === 'assistant' && !empty($m['content_html'])): ?>
|
||||
<!-- Assistant: already sanitized HTML -->
|
||||
<?= $m['content_html'] ?>
|
||||
<?php else: ?>
|
||||
<!-- User (or legacy items): escape + preserve newlines -->
|
||||
<?= nl2br(htmlspecialchars($m['content'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), false) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="time"><?= date('H:i', $ts) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div><!-- /.chat-box -->
|
||||
|
||||
<form id="chatForm" class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<textarea id="messageInput" class="form-control" rows="3" placeholder="Ask something… (Shift+Enter for newline)" required></textarea>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button id="sendBtn" type="submit" class="btn btn-primary">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</form><!-- /#chatForm -->
|
||||
</div><!-- /#chat -->
|
||||
</div><!-- /#pageWrapper -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
15
main.css
15
main.css
@ -209,3 +209,18 @@ html:not([data-theme="light"]) .btn-outline-light {
|
||||
html:not([data-theme="light"]) .btn-outline-light:hover {
|
||||
background-color: rgba(255,255,255,0.12) !important;
|
||||
}
|
||||
|
||||
/* Vanilla pages system (fixed) */
|
||||
.tab-content .page {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
|
||||
.tab-content .page.active {
|
||||
display: block; /* stays at opacity: 0 until .show is added */
|
||||
}
|
||||
|
||||
.tab-content .page.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
76
main.js
76
main.js
@ -110,3 +110,79 @@ $(function () {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ========= Vanilla JS Page Switching (fixed) =========
|
||||
(function () {
|
||||
const navLinks = document.querySelectorAll('.nav-tab .nav-link[data-page]');
|
||||
const pages = document.querySelectorAll('#pageWrapper .page');
|
||||
const DURATION = 200; // keep in sync with CSS
|
||||
|
||||
function getActivePage() {
|
||||
return document.querySelector('#pageWrapper .page.active');
|
||||
}
|
||||
|
||||
function setActiveNav(targetId) {
|
||||
navLinks.forEach(a => {
|
||||
const isActive = a.getAttribute('data-page') === targetId;
|
||||
a.classList.toggle('active', isActive);
|
||||
a.setAttribute('aria-current', isActive ? 'page' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
function showPage(targetId) {
|
||||
const current = getActivePage();
|
||||
const next = document.getElementById(targetId);
|
||||
if (!next || current === next) return;
|
||||
|
||||
// Prepare next: visible container, but still transparent
|
||||
next.classList.add('active'); // display:block; opacity:0
|
||||
|
||||
// Fade-in next on the next frame
|
||||
requestAnimationFrame(() => {
|
||||
next.classList.add('show'); // opacity -> 1 (transition)
|
||||
});
|
||||
|
||||
// Fade-out current (if any)
|
||||
if (current) {
|
||||
current.classList.remove('show'); // opacity -> 0 (transition)
|
||||
|
||||
let cleaned = false;
|
||||
const cleanup = () => {
|
||||
if (cleaned) return;
|
||||
cleaned = true;
|
||||
current.classList.remove('active');
|
||||
current.removeEventListener('transitionend', onEnd);
|
||||
};
|
||||
|
||||
const onEnd = (e) => {
|
||||
if (e.target === current && e.propertyName === 'opacity') {
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
current.addEventListener('transitionend', onEnd);
|
||||
// Fallback in case transitionend doesn't fire (reduced motion, etc.)
|
||||
setTimeout(cleanup, DURATION + 50);
|
||||
}
|
||||
|
||||
setActiveNav(targetId);
|
||||
history.replaceState(null, '', '#' + targetId);
|
||||
}
|
||||
|
||||
// Click handlers
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', (ev) => {
|
||||
ev.preventDefault();
|
||||
const targetId = link.getAttribute('data-page');
|
||||
showPage(targetId);
|
||||
});
|
||||
});
|
||||
|
||||
// Initial page from hash
|
||||
const initial = (location.hash || '#chat').slice(1);
|
||||
if (initial !== 'chat' && document.getElementById(initial)) {
|
||||
showPage(initial);
|
||||
} else {
|
||||
setActiveNav('chat');
|
||||
}
|
||||
})();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user