mirror of
https://github.com/multipleof4/sune.git
synced 2026-01-14 00:27:56 +00:00
@@ -35,10 +35,7 @@
|
||||
<main id="chat" class="flex-1 overflow-y-auto no-scrollbar">
|
||||
<div id="messages" class="mx-auto w-full max-w-2xl px-4 py-4 sm:py-6 space-y-4"></div>
|
||||
<div class="h-24"></div>
|
||||
</main>
|
||||
|
||||
<!-- Input Dock -->
|
||||
<footer class="sticky bottom-0 z-10 bg-gradient-to-t from-white via-white/95 to-white/40 pt-3 pb-[calc(12px+var(--safe-bottom))] border-t border-gray-200">
|
||||
</main> <!-- Input Dock --> <footer class="sticky bottom-0 z-10 bg-gradient-to-t from-white via-white/95 to-white/40 pt-3 pb-[calc(12px+var(--safe-bottom))] border-t border-gray-200">
|
||||
<div class="mx-auto w-full max-w-2xl px-4">
|
||||
<form id="composer" class="group relative flex items-end gap-2">
|
||||
<textarea id="input" rows="1" placeholder="Send a message" spellcheck="true" class="flex-1 resize-none rounded-2xl border border-gray-300 bg-white px-4 py-3 text-[15px] leading-6 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-black/20 focus:border-gray-300 max-h-40"></textarea>
|
||||
@@ -53,16 +50,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div> <!-- Sidebar --> <div id="sidebarOverlay" class="fixed inset-0 z-40 bg-black/20 hidden"></div>
|
||||
</footer> </div> <!-- Sidebar --> <div id="sidebarOverlay" class="fixed inset-0 z-40 bg-black/20 hidden"></div>
|
||||
<aside id="sidebar" class="fixed inset-y-0 left-0 z-50 w-72 max-w-[85vw] bg-white border-r border-gray-200 shadow-xl transform -translate-x-full transition-transform duration-200 ease-out flex flex-col">
|
||||
<div class="p-3 border-b flex items-center gap-2">
|
||||
<button id="newAssistantBtn" class="px-3 py-2 rounded-xl bg-black text-white text-sm hover:bg-black/90">New assistant</button>
|
||||
<span class="text-xs text-gray-500">Click name to equip</span>
|
||||
</div>
|
||||
<div id="assistantList" class="flex-1 overflow-y-auto divide-y"></div>
|
||||
</aside> <!-- Settings Dialog --> <div id="settingsModal" class="hidden fixed inset-0 z-50">
|
||||
</aside>
|
||||
<!-- Settings Dialog -->
|
||||
<div id="settingsModal" class="hidden fixed inset-0 z-50">
|
||||
<div class="absolute inset-0 bg-black/30"></div>
|
||||
<div class="absolute inset-x-0 top-12 mx-auto w-full max-w-md px-4">
|
||||
<div class="rounded-2xl bg-white shadow-xl border border-gray-200 overflow-hidden">
|
||||
@@ -100,14 +97,21 @@
|
||||
<p class="mt-1 text-xs text-gray-500">Saved per assistant.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2 px-4 py-3 border-t">
|
||||
<button type="button" id="cancelSettings" class="px-3 py-2 rounded-xl border bg-white hover:bg-gray-50">Cancel</button>
|
||||
<button type="submit" class="px-3 py-2 rounded-xl bg-black text-white hover:bg-black/90">Save</button>
|
||||
<!-- Footer actions: Delete (left), Cancel/Save (right) -->
|
||||
<div class="flex items-center justify-between gap-2 px-4 py-3 border-t">
|
||||
<button type="button" id="deleteAssistantBtn" class="inline-flex items-center gap-2 px-3 py-2 rounded-xl border border-red-200 text-red-700 hover:bg-red-50">
|
||||
<svg viewBox="0 0 24 24" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 6h18M8 6v12a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2V6m-9 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
<span>Delete assistant</span>
|
||||
</button>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button type="button" id="cancelSettings" class="px-3 py-2 rounded-xl border bg-white hover:bg-gray-50">Cancel</button>
|
||||
<button type="submit" class="px-3 py-2 rounded-xl bg-black text-white hover:bg-black/90">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div> <script>
|
||||
</div> <script>
|
||||
// === Constants ===
|
||||
const DEFAULT_MODEL = 'openai/gpt-4o';
|
||||
const DEFAULT_API_KEY = '';
|
||||
@@ -117,7 +121,7 @@
|
||||
'chat','messages','composer','input','sendBtn','stopBtn','clearBtn','apiBadge','statusText','settingsBtnTop',
|
||||
'settingsModal','settingsForm','closeSettings','cancelSettings','tabModel','tabPrompt','panelModel','panelPrompt',
|
||||
'set_model','set_temperature','set_top_p','set_top_k','set_frequency_penalty','set_presence_penalty','set_repetition_penalty','set_min_p','set_top_a','set_system_prompt',
|
||||
'sidebar','sidebarOverlay','sidebarBtn','assistantList','newAssistantBtn'
|
||||
'sidebar','sidebarOverlay','sidebarBtn','assistantList','newAssistantBtn','deleteAssistantBtn'
|
||||
].map(id => [id, document.getElementById(id)]));
|
||||
|
||||
// === Utilities ===
|
||||
@@ -154,6 +158,8 @@
|
||||
const getActive = () => assistants.find(a => a.id === as.getActiveId()) || assistants[0];
|
||||
const setActive = (id) => { as.setActiveId(id); renderSidebar(); reflectActiveAssistant(); clearChat(); };
|
||||
|
||||
const createDefaultAssistant = () => ({ id: gid(), name: 'Default', settings: { model: DEFAULT_MODEL, temperature:1, top_p:1, top_k:0, frequency_penalty:0, presence_penalty:0, repetition_penalty:1, min_p:0, top_a:0, system_prompt:'' } });
|
||||
|
||||
// === Legacy-compatible settings proxy bound to active assistant ===
|
||||
const store = new Proxy({}, {
|
||||
get(_, prop){
|
||||
@@ -346,6 +352,32 @@
|
||||
reflectActiveAssistant();
|
||||
});
|
||||
|
||||
// NEW: Delete assistant (bottom-left of settings)
|
||||
el.deleteAssistantBtn.addEventListener('click', () => {
|
||||
const activeId = as.getActiveId();
|
||||
const active = getActive();
|
||||
const name = active?.name || 'this assistant';
|
||||
if (!confirm(`Delete "${name}"?`)) return;
|
||||
// Remove active from list
|
||||
assistants = assistants.filter(a => a.id !== activeId);
|
||||
// Persist
|
||||
as.save(assistants);
|
||||
// Decide next active
|
||||
if (assistants.length === 0){
|
||||
const def = createDefaultAssistant();
|
||||
assistants = [def];
|
||||
as.save(assistants);
|
||||
as.setActiveId(def.id);
|
||||
}else{
|
||||
as.setActiveId(assistants[0].id);
|
||||
}
|
||||
// Refresh UI
|
||||
renderSidebar();
|
||||
reflectActiveAssistant();
|
||||
clearChat();
|
||||
closeSettings();
|
||||
});
|
||||
|
||||
// Composer
|
||||
el.composer.addEventListener('submit', async (e) => {
|
||||
e.preventDefault(); if (state.busy) return; const text = el.input.value.trim(); if (!text) return;
|
||||
@@ -411,5 +443,5 @@
|
||||
function initFromQuery(){ const url = new URL(location.href); const key = url.searchParams.get('key'); const model = url.searchParams.get('model'); if (key) store.apiKey = key; if (model){ const a=getActive(); a.settings.model = model; as.save(assistants);} }
|
||||
function init(){ initFromQuery(); updateStatus(); renderSidebar(); reflectActiveAssistant(); clearChat(); }
|
||||
init();
|
||||
</script></body>
|
||||
</html>
|
||||
|
||||
</script></body></html>
|
||||
|
||||
62
index.html
62
index.html
@@ -35,10 +35,7 @@
|
||||
<main id="chat" class="flex-1 overflow-y-auto no-scrollbar">
|
||||
<div id="messages" class="mx-auto w-full max-w-2xl px-4 py-4 sm:py-6 space-y-4"></div>
|
||||
<div class="h-24"></div>
|
||||
</main>
|
||||
|
||||
<!-- Input Dock -->
|
||||
<footer class="sticky bottom-0 z-10 bg-gradient-to-t from-white via-white/95 to-white/40 pt-3 pb-[calc(12px+var(--safe-bottom))] border-t border-gray-200">
|
||||
</main> <!-- Input Dock --> <footer class="sticky bottom-0 z-10 bg-gradient-to-t from-white via-white/95 to-white/40 pt-3 pb-[calc(12px+var(--safe-bottom))] border-t border-gray-200">
|
||||
<div class="mx-auto w-full max-w-2xl px-4">
|
||||
<form id="composer" class="group relative flex items-end gap-2">
|
||||
<textarea id="input" rows="1" placeholder="Send a message" spellcheck="true" class="flex-1 resize-none rounded-2xl border border-gray-300 bg-white px-4 py-3 text-[15px] leading-6 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-black/20 focus:border-gray-300 max-h-40"></textarea>
|
||||
@@ -53,16 +50,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div> <!-- Sidebar --> <div id="sidebarOverlay" class="fixed inset-0 z-40 bg-black/20 hidden"></div>
|
||||
</footer> </div> <!-- Sidebar --> <div id="sidebarOverlay" class="fixed inset-0 z-40 bg-black/20 hidden"></div>
|
||||
<aside id="sidebar" class="fixed inset-y-0 left-0 z-50 w-72 max-w-[85vw] bg-white border-r border-gray-200 shadow-xl transform -translate-x-full transition-transform duration-200 ease-out flex flex-col">
|
||||
<div class="p-3 border-b flex items-center gap-2">
|
||||
<button id="newAssistantBtn" class="px-3 py-2 rounded-xl bg-black text-white text-sm hover:bg-black/90">New assistant</button>
|
||||
<span class="text-xs text-gray-500">Click name to equip</span>
|
||||
</div>
|
||||
<div id="assistantList" class="flex-1 overflow-y-auto divide-y"></div>
|
||||
</aside> <!-- Settings Dialog --> <div id="settingsModal" class="hidden fixed inset-0 z-50">
|
||||
</aside>
|
||||
<!-- Settings Dialog -->
|
||||
<div id="settingsModal" class="hidden fixed inset-0 z-50">
|
||||
<div class="absolute inset-0 bg-black/30"></div>
|
||||
<div class="absolute inset-x-0 top-12 mx-auto w-full max-w-md px-4">
|
||||
<div class="rounded-2xl bg-white shadow-xl border border-gray-200 overflow-hidden">
|
||||
@@ -100,14 +97,21 @@
|
||||
<p class="mt-1 text-xs text-gray-500">Saved per assistant.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2 px-4 py-3 border-t">
|
||||
<button type="button" id="cancelSettings" class="px-3 py-2 rounded-xl border bg-white hover:bg-gray-50">Cancel</button>
|
||||
<button type="submit" class="px-3 py-2 rounded-xl bg-black text-white hover:bg-black/90">Save</button>
|
||||
<!-- Footer actions: Delete (left), Cancel/Save (right) -->
|
||||
<div class="flex items-center justify-between gap-2 px-4 py-3 border-t">
|
||||
<button type="button" id="deleteAssistantBtn" class="inline-flex items-center gap-2 px-3 py-2 rounded-xl border border-red-200 text-red-700 hover:bg-red-50">
|
||||
<svg viewBox="0 0 24 24" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 6h18M8 6v12a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2V6m-9 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
<span>Delete assistant</span>
|
||||
</button>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button type="button" id="cancelSettings" class="px-3 py-2 rounded-xl border bg-white hover:bg-gray-50">Cancel</button>
|
||||
<button type="submit" class="px-3 py-2 rounded-xl bg-black text-white hover:bg-black/90">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div> <script>
|
||||
</div> <script>
|
||||
// === Constants ===
|
||||
const DEFAULT_MODEL = 'openai/gpt-4o';
|
||||
const DEFAULT_API_KEY = '';
|
||||
@@ -117,7 +121,7 @@
|
||||
'chat','messages','composer','input','sendBtn','stopBtn','clearBtn','apiBadge','statusText','settingsBtnTop',
|
||||
'settingsModal','settingsForm','closeSettings','cancelSettings','tabModel','tabPrompt','panelModel','panelPrompt',
|
||||
'set_model','set_temperature','set_top_p','set_top_k','set_frequency_penalty','set_presence_penalty','set_repetition_penalty','set_min_p','set_top_a','set_system_prompt',
|
||||
'sidebar','sidebarOverlay','sidebarBtn','assistantList','newAssistantBtn'
|
||||
'sidebar','sidebarOverlay','sidebarBtn','assistantList','newAssistantBtn','deleteAssistantBtn'
|
||||
].map(id => [id, document.getElementById(id)]));
|
||||
|
||||
// === Utilities ===
|
||||
@@ -154,6 +158,8 @@
|
||||
const getActive = () => assistants.find(a => a.id === as.getActiveId()) || assistants[0];
|
||||
const setActive = (id) => { as.setActiveId(id); renderSidebar(); reflectActiveAssistant(); clearChat(); };
|
||||
|
||||
const createDefaultAssistant = () => ({ id: gid(), name: 'Default', settings: { model: DEFAULT_MODEL, temperature:1, top_p:1, top_k:0, frequency_penalty:0, presence_penalty:0, repetition_penalty:1, min_p:0, top_a:0, system_prompt:'' } });
|
||||
|
||||
// === Legacy-compatible settings proxy bound to active assistant ===
|
||||
const store = new Proxy({}, {
|
||||
get(_, prop){
|
||||
@@ -346,6 +352,32 @@
|
||||
reflectActiveAssistant();
|
||||
});
|
||||
|
||||
// NEW: Delete assistant (bottom-left of settings)
|
||||
el.deleteAssistantBtn.addEventListener('click', () => {
|
||||
const activeId = as.getActiveId();
|
||||
const active = getActive();
|
||||
const name = active?.name || 'this assistant';
|
||||
if (!confirm(`Delete "${name}"?`)) return;
|
||||
// Remove active from list
|
||||
assistants = assistants.filter(a => a.id !== activeId);
|
||||
// Persist
|
||||
as.save(assistants);
|
||||
// Decide next active
|
||||
if (assistants.length === 0){
|
||||
const def = createDefaultAssistant();
|
||||
assistants = [def];
|
||||
as.save(assistants);
|
||||
as.setActiveId(def.id);
|
||||
}else{
|
||||
as.setActiveId(assistants[0].id);
|
||||
}
|
||||
// Refresh UI
|
||||
renderSidebar();
|
||||
reflectActiveAssistant();
|
||||
clearChat();
|
||||
closeSettings();
|
||||
});
|
||||
|
||||
// Composer
|
||||
el.composer.addEventListener('submit', async (e) => {
|
||||
e.preventDefault(); if (state.busy) return; const text = el.input.value.trim(); if (!text) return;
|
||||
@@ -411,5 +443,5 @@
|
||||
function initFromQuery(){ const url = new URL(location.href); const key = url.searchParams.get('key'); const model = url.searchParams.get('model'); if (key) store.apiKey = key; if (model){ const a=getActive(); a.settings.model = model; as.save(assistants);} }
|
||||
function init(){ initFromQuery(); updateStatus(); renderSidebar(); reflectActiveAssistant(); clearChat(); }
|
||||
init();
|
||||
</script></body>
|
||||
</html>
|
||||
|
||||
</script></body></html>
|
||||
|
||||
Reference in New Issue
Block a user