feat: ChatGPT/OWUI mobile assistant — viewport-locked overlay with drawer (no disjoint scrolling), plain rows without chat icons, ⋮ options; desktop collapsible saved-chats rail
This commit is contained in:
parent
b115491858
commit
ff8c1132c1
4 changed files with 72 additions and 48 deletions
|
|
@ -1,5 +1,6 @@
|
|||
<div class="assistant-topbar">
|
||||
<button id="btn-assistant-goback" class="btn-sm btn-ghost" type="button" title="Back to the main menu"><i class="fas fa-arrow-left"></i> Go back</button>
|
||||
<button id="btn-assistant-toggle-history" class="assistant-toggle-history" type="button" title="Toggle saved chats sidebar" aria-label="Toggle saved chats sidebar"><i class="fas fa-sidebar"></i></button>
|
||||
<button id="btn-assistant-mobile-menu" class="btn-sm btn-ghost assistant-mobile-menu" type="button" title="Saved chats"><i class="fas fa-bars"></i></button>
|
||||
<div class="assistant-topbar-title">
|
||||
<h2><i class="fas fa-brain" style="color:var(--purple);"></i> AI Clinical Assistant</h2>
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@
|
|||
.assistant-history .card { flex:1 1 auto; display:flex; flex-direction:column; min-height:0; overflow:hidden; }
|
||||
@media (max-width: 960px) { .assistant-layout { grid-template-columns:minmax(0,1fr) 300px; } .assistant-history { position:static; grid-column:1 / -1; grid-template-columns:auto 1fr; align-items:start; } .assistant-new-chat { grid-column:1; } .assistant-view-switch { grid-column:1; } .assistant-history .card { grid-column:2; grid-row:1 / span 2; } .assistant-main { min-height:auto; } #assistant-chat-view { min-height:auto; grid-template-rows:auto minmax(320px,1fr) auto; } }
|
||||
@media (max-width: 640px) {
|
||||
/* ── ChatGPT-style mobile: quiet, minimal, content first ── */
|
||||
/* ── ChatGPT/OWUI mobile: independent full-screen overlay; the page behind never scrolls ── */
|
||||
.assistant-topbar { flex-wrap:nowrap; gap:8px; padding:10px 14px; }
|
||||
.assistant-mobile-menu { display:inline-flex; }
|
||||
.assistant-topbar .btn-sm { padding:6px 8px; }
|
||||
|
|
@ -168,29 +168,44 @@
|
|||
#btn-assistant-goback { padding:8px; }
|
||||
.assistant-topbar-title h2 { font-size:15px; }
|
||||
.assistant-status { margin-left:auto; font-size:11px; }
|
||||
.assistant-layout { display:block; height:auto; min-height:100dvh; }
|
||||
/* Saved chats: a left drawer, ChatGPT-style, with the menu button as the opener */
|
||||
.assistant-history { position:fixed; inset:0 auto 0 0; width:min(88vw,340px); z-index:130; background:white; border-right:1px solid var(--g100); box-shadow:none; transform:translateX(-105%); transition:transform .18s ease; display:flex; flex-direction:column; padding:12px; overflow-y:auto; overflow-x:hidden; gap:2px; border-radius:0; }
|
||||
.assistant-toggle-history { display:none; }
|
||||
/* The assistant fills the viewport exactly; nothing behind can scroll */
|
||||
.assistant-layout { display:flex; flex-direction:column; height:100dvh; min-height:0; overflow:hidden; }
|
||||
.assistant-main { flex:1 1 auto; min-height:0; display:flex; }
|
||||
.assistant-main.card { box-shadow:none; border:none; margin:0; }
|
||||
#assistant-chat-view { flex:1 1 auto; min-height:0; height:auto; display:flex; flex-direction:column; }
|
||||
.assistant-messages { flex:1 1 auto; min-height:0; max-height:none; overflow-y:auto; -webkit-overflow-scrolling:touch; padding:8px 14px 12px; }
|
||||
.assistant-side { display:none; }
|
||||
/* Slim toolbar: icon-only actions in one row */
|
||||
.assistant-toolbar { padding:6px 10px; flex:0 0 auto; }
|
||||
.assistant-toolbar > div:first-child { display:none; }
|
||||
.assistant-toolbar-actions { display:flex; width:100%; justify-content:flex-end; gap:4px; }
|
||||
.assistant-toolbar-actions .btn-sm { font-size:0; padding:6px 8px; }
|
||||
.assistant-toolbar-actions .btn-sm i { font-size:13px; }
|
||||
/* Saved chats: a fixed drawer overlaying the chat */
|
||||
.assistant-history { position:fixed; left:0; top:0; bottom:0; width:min(85vw,320px); z-index:130; background:white; border-right:1px solid var(--g100); box-shadow:0 0 40px rgba(15,23,42,.14); transform:translateX(-105%); transition:transform .18s ease; display:flex; flex-direction:column; padding:10px 8px; overflow-y:auto; overflow-x:hidden; gap:2px; border-radius:0; }
|
||||
.assistant-layout.mobile-chats-open .assistant-history { transform:translateX(0); }
|
||||
.assistant-history .card { box-shadow:none; border:1px solid var(--g200); }
|
||||
.assistant-new-chat { align-self:stretch; }
|
||||
.assistant-view-switch { margin:6px 0; }
|
||||
#assistant-saved-chats { flex-direction:column; padding:0 6px 12px; gap:6px; overflow:visible; }
|
||||
.assistant-saved-chat { max-width:none; }
|
||||
.assistant-main { min-height:0; }
|
||||
#assistant-chat-view { display:flex; flex-direction:column; height:100dvh; }
|
||||
.assistant-messages { flex:1 1 auto; max-height:none; overflow-y:auto; -webkit-overflow-scrolling:touch; padding:8px 14px 12px; }
|
||||
.assistant-history .card { border:none; box-shadow:none; background:none; padding:0; }
|
||||
.assistant-history .card-header { padding:6px 10px 2px; }
|
||||
#assistant-saved-chats { padding:2px 0 12px; gap:0; overflow:visible; }
|
||||
.assistant-saved-chat { flex-direction:row; align-items:center; gap:0; border:none; border-radius:8px; padding:11px 34px 11px 12px; min-width:0; width:100%; max-width:none; }
|
||||
.assistant-saved-chat:hover { background:var(--g100); }
|
||||
.assistant-saved-chat.active { background:var(--g100); }
|
||||
.assistant-saved-chat-title { flex:1 1 auto; min-width:0; max-width:none; font-size:14px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.assistant-saved-chat-meta { display:none; }
|
||||
.assistant-saved-chat-menu { position:absolute; right:6px; top:50%; transform:translateY(-50%); opacity:1; pointer-events:auto; }
|
||||
.assistant-new-chat, .assistant-goback-rail { display:flex; align-items:center; gap:10px; width:100%; border:none; background:none; color:var(--g700); font-size:14px; padding:10px 12px; border-radius:8px; text-align:left; }
|
||||
.assistant-new-chat:hover, .assistant-goback-rail:hover { background:var(--g100); }
|
||||
.assistant-create-image { display:flex; align-items:center; gap:10px; width:100%; background:none; color:var(--purple); font-weight:700; font-size:14px; padding:10px 12px; border-radius:8px; text-align:left; }
|
||||
.assistant-create-image:hover { background:var(--purple-light,#faf5ff); }
|
||||
.assistant-view-switch { margin:6px 8px; }
|
||||
.assistant-empty { margin:28px auto; max-width:100%; }
|
||||
.assistant-empty i { font-size:26px; }
|
||||
.assistant-empty h3 { font-size:16px; text-align:left; }
|
||||
.assistant-empty p { text-align:left; font-size:13px; }
|
||||
.assistant-examples { flex-direction:column; align-items:stretch; gap:0; margin:14px 0 0; }
|
||||
.assistant-examples button { border:none; background:none; color:var(--g700); text-align:left; padding:12px 2px; font-size:14px; border-radius:0; border-bottom:1px solid var(--g100); white-space:normal; width:100%; }
|
||||
/* Toolbar actions move into the drawer */
|
||||
.assistant-toolbar { padding:10px 14px 0; }
|
||||
.assistant-toolbar-actions { display:none; }
|
||||
.assistant-layout.mobile-chats-open .assistant-toolbar-actions { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
|
||||
/* Floating pill composer */
|
||||
/* Floating pill composer pinned at the bottom */
|
||||
.assistant-composer { position:static; flex:0 0 auto; margin:0 10px 10px; z-index:60; background:white; border:1px solid var(--g200); border-radius:20px; box-shadow:0 8px 30px rgba(0,0,0,.14); padding:8px 12px 6px; display:flex; flex-direction:column; gap:6px; }
|
||||
#assistant-input { font-size:16px; }
|
||||
.assistant-composer > label { display:none; }
|
||||
|
|
@ -204,7 +219,6 @@
|
|||
.assistant-composer-footer #btn-assistant-cancel:not([hidden]) { width:auto; padding:8px 12px; }
|
||||
.assistant-mic { font-size:0; }
|
||||
.assistant-mic i { font-size:18px; }
|
||||
.assistant-side { max-height:60vh; overflow-y:auto; border-top:1px solid var(--g100); padding:12px 14px; }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -242,6 +256,12 @@
|
|||
|
||||
/* Full-screen Open WebUI workspace: the assistant replaces the app chrome */
|
||||
body.assistant-workspace .assistant-layout { height: calc(100vh - 64px); min-height: 0; grid-template-rows: minmax(0, 1fr); overflow: hidden; }
|
||||
/* Desktop: the saved-chats rail collapses like ChatGPT's sidebar */
|
||||
.assistant-toggle-history { border:none; background:none; color:var(--g600); padding:6px 8px; border-radius:8px; cursor:pointer; }
|
||||
.assistant-toggle-history:hover { background:var(--g100); color:var(--g800); }
|
||||
.assistant-layout { transition:grid-template-columns .18s ease; }
|
||||
.assistant-layout.history-collapsed { grid-template-columns:0 minmax(0,1fr) 330px; }
|
||||
.assistant-layout.history-collapsed .assistant-history { display:none; }
|
||||
body.assistant-workspace .assistant-layout > * { min-height: 0; }
|
||||
body.assistant-workspace .assistant-main,
|
||||
body.assistant-workspace .assistant-main #assistant-chat-view { min-height: 0; }
|
||||
|
|
@ -322,7 +342,6 @@ body.assistant-workspace .assistant-learning-view { min-height: 0; height: 100vh
|
|||
.assistant-goback-rail { display:none; }
|
||||
@media (max-width: 640px) {
|
||||
.assistant-goback-rail { display:inline-flex; align-self:stretch; justify-content:flex-start; margin-bottom:6px; }
|
||||
.assistant-topbar #btn-assistant-goback { display:none; }
|
||||
}
|
||||
/* Library item hover download */
|
||||
.assistant-gallery-item-wrap { position:relative; }
|
||||
|
|
@ -336,31 +355,7 @@ body.assistant-workspace .assistant-learning-view { min-height: 0; height: 100vh
|
|||
.assistant-layout.mobile-chats-open .assistant-drawer-backdrop { display:block; }
|
||||
.assistant-drawer-close { display:flex; align-items:center; justify-content:center; align-self:flex-end; width:30px; height:30px; border:none; background:var(--g100); border-radius:50%; color:var(--g600); }
|
||||
}
|
||||
/* Mobile drawer borrows the main-menu look (icon rows + section labels) */
|
||||
@media (max-width: 640px) {
|
||||
.assistant-history { gap:2px; background:white; padding:10px 8px; }
|
||||
.assistant-history .card { border:none; box-shadow:none; background:none; }
|
||||
.assistant-history .card-header h3 { font-size:11px; font-weight:700; color:var(--g500); text-transform:uppercase; letter-spacing:.04em; padding:6px 10px 2px; }
|
||||
.assistant-new-chat, .assistant-goback-rail { display:flex; align-items:center; gap:10px; width:100%; border:none; background:none; color:var(--g700); font-size:14px; padding:10px 12px; border-radius:8px; text-align:left; }
|
||||
.assistant-new-chat:hover, .assistant-goback-rail:hover { background:var(--g100); }
|
||||
.assistant-create-image { display:flex; align-items:center; gap:10px; width:100%; background:none; color:var(--purple); font-weight:700; font-size:14px; padding:10px 12px; border-radius:8px; text-align:left; }
|
||||
.assistant-create-image:hover { background:var(--purple-light,#faf5ff); }
|
||||
.assistant-saved-chat { flex-direction:row; align-items:center; gap:10px; border:none; border-radius:8px; padding:9px 12px; min-width:0; width:100%; }
|
||||
.assistant-saved-chat-icon { font-size:13px; color:var(--g400); width:16px; text-align:center; flex:0 0 auto; }
|
||||
.assistant-saved-chat-title { flex:1 1 auto; min-width:0; }
|
||||
.assistant-saved-chat-meta { flex:0 0 auto; }
|
||||
.assistant-saved-chat-menu { position:static; opacity:1; pointer-events:auto; }
|
||||
}
|
||||
/* Saved chats: a plain sidebar list, not a card — desktop and mobile */
|
||||
.assistant-history .card { border:none; box-shadow:none; background:none; padding:0; }
|
||||
.assistant-history .card-header { padding:2px 4px; }
|
||||
.assistant-history .card-header h3 { font-size:11px; font-weight:700; color:var(--g500); text-transform:uppercase; letter-spacing:.04em; }
|
||||
/* Mobile drawer rows: no icons, hard-truncated titles, ⋮ always visible */
|
||||
@media (max-width: 640px) {
|
||||
.assistant-saved-chat-icon { flex:0 0 16px; }
|
||||
.assistant-saved-chat { position:relative; overflow:hidden; max-width:100%; padding-right:36px; }
|
||||
.assistant-saved-chat-title { flex:1 1 auto; min-width:0; max-width:calc(100% - 30px); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.assistant-saved-chat-menu { position:absolute; right:6px; top:50%; transform:translateY(-50%); opacity:1; pointer-events:auto; }
|
||||
.assistant-saved-chat-meta { display:none; }
|
||||
.assistant-goback-rail i, .assistant-create-image i, .assistant-new-chat i { display:none; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,10 +110,6 @@ import {
|
|||
|
||||
function initIfNeeded() {
|
||||
if (initialized) return;
|
||||
if (typeof window !== 'undefined' && window.innerWidth <= 640) {
|
||||
var layoutEl = document.getElementById('assistant-layout');
|
||||
if (layoutEl) layoutEl.classList.add('mobile-chats-open');
|
||||
}
|
||||
var root = document.getElementById('assistant-tab');
|
||||
if (typeof window !== 'undefined' && window._userRole && window._userRole !== 'admin') {
|
||||
var downloadBtn = document.getElementById('btn-assistant-download-chat');
|
||||
|
|
@ -160,6 +156,22 @@ import {
|
|||
if (drawerCloseBtn) drawerCloseBtn.addEventListener('click', closeDrawer);
|
||||
var backdrop = document.getElementById('assistant-drawer-backdrop');
|
||||
if (backdrop) backdrop.addEventListener('click', closeDrawer);
|
||||
// Desktop: collapse/expand the saved-chats rail (ChatGPT-style sidebar)
|
||||
var toggleHistoryBtn = document.getElementById('btn-assistant-toggle-history');
|
||||
if (toggleHistoryBtn) toggleHistoryBtn.addEventListener('click', function() {
|
||||
var layout = document.getElementById('assistant-layout');
|
||||
if (!layout) return;
|
||||
var collapsed = layout.classList.toggle('history-collapsed');
|
||||
try { localStorage.setItem('ped_assistant_history_collapsed', collapsed ? '1' : '0'); } catch (e) {}
|
||||
});
|
||||
if (typeof window !== 'undefined' && window.innerWidth > 640) {
|
||||
try {
|
||||
if (localStorage.getItem('ped_assistant_history_collapsed') === '1') {
|
||||
var layoutForPref = document.getElementById('assistant-layout');
|
||||
if (layoutForPref) layoutForPref.classList.add('history-collapsed');
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (form) form.addEventListener('submit', onAsk);
|
||||
var sendBtn = document.getElementById('btn-assistant-send');
|
||||
|
|
@ -1882,7 +1894,6 @@ import {
|
|||
var title = chat.title || 'Saved chat';
|
||||
var pinned = isChatPinned(chat.id);
|
||||
return '<button type="button" class="assistant-saved-chat' + (pinned ? ' pinned' : '') + '" data-assistant-load-chat="' + escapeAttr(chat.id) + '" aria-label="Open saved chat" title="' + escapeAttr(title) + '">' +
|
||||
'<i class="fas fa-comment assistant-saved-chat-icon"></i>' +
|
||||
'<span class="assistant-saved-chat-title">' + (pinned ? '<i class="fas fa-thumbtack"></i> ' : '') + escapeHtml(title) + '</span>' +
|
||||
'<span class="assistant-saved-chat-meta">' + escapeHtml(formatSavedDate(chat.updated_at || chat.created_at)) + '</span>' +
|
||||
'<span class="assistant-saved-chat-menu" role="button" tabindex="0" data-assistant-chat-menu="' + escapeAttr(chat.id) + '" aria-label="Chat options" title="Options"><i class="fas fa-ellipsis-vertical"></i></span>' +
|
||||
|
|
|
|||
|
|
@ -98,3 +98,20 @@ test('the assistant tab keeps its clean structure next to the restored learning
|
|||
assert.match(indexHtml, /<section id="assistant-tab" class="tab-content" data-component="assistant"><\/section>/);
|
||||
assert.match(indexHtml, /<section id="learning-tab" class="tab-content" data-component="learning"><\/section>/);
|
||||
});
|
||||
|
||||
test('desktop rail collapses and expands via the topbar toggle', t => {
|
||||
const app = workspace(t);
|
||||
const layout = app.document.querySelector('.assistant-layout');
|
||||
const toggle = app.document.getElementById('btn-assistant-toggle-history');
|
||||
assert.ok(toggle, 'history toggle present in the topbar');
|
||||
toggle.click();
|
||||
assert.ok(layout.classList.contains('history-collapsed'), 'rail collapses');
|
||||
toggle.click();
|
||||
assert.ok(!layout.classList.contains('history-collapsed'), 'rail expands');
|
||||
});
|
||||
|
||||
test('mobile drawer rows carry no chat icons and keep the options menu', () => {
|
||||
const css = read('public/css/assistant.css');
|
||||
assert.ok(/\.assistant-layout \{ display:flex; flex-direction:column; height:100dvh;/.test(css), 'mobile layout locks the viewport');
|
||||
assert.ok(/\.assistant-side \{ display:none; \}/.test(css), 'no sources column under the chat on mobile');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue