From be74226840590bb69572697e896f63fd8287fa8f Mon Sep 17 00:00:00 2001 From: Saood Karim Date: Sun, 10 Aug 2025 23:04:20 -0500 Subject: [PATCH] Major UI work (and also add update backend endpoints to accomadate) --- examples/server/public_mikupad/index.html | 620 +++++++++++++++++++++- examples/server/server.cpp | 152 +++++- 2 files changed, 761 insertions(+), 11 deletions(-) diff --git a/examples/server/public_mikupad/index.html b/examples/server/public_mikupad/index.html index c053c006..d1714a30 100644 --- a/examples/server/public_mikupad/index.html +++ b/examples/server/public_mikupad/index.html @@ -1953,15 +1953,16 @@ function Sessions({ sessionStorage, disabled }) { }; const exportAll = async () => { - alert("Warning: This can take a lot of time and space. Leave the page now if you do not want to proceed.") - const db = await sessionStorage.openDatabase(); - const sessionKeys = Object.keys(sessionStorage.sessions); - for (const sessionKey of sessionKeys) { - const processedSession = await sessionStorage.loadFromDatabase(db, sessionKey); - for (const [key, value] of Object.entries(processedSession)) { - processedSession[key] = JSON.stringify(value); + if (confirm("Warning: This can take a lot of time and space. Be patient if you proceed.")) { + const db = await sessionStorage.openDatabase(); + const sessionKeys = Object.keys(sessionStorage.sessions); + for (const sessionKey of sessionKeys) { + const processedSession = await sessionStorage.loadFromDatabase(db, sessionKey); + for (const [key, value] of Object.entries(processedSession)) { + processedSession[key] = JSON.stringify(value); + } + exportText(`${processedSession.name}.json`, JSON.stringify(processedSession)); } - exportText(`${processedSession.name}.json`, JSON.stringify(processedSession)); } }; @@ -2096,7 +2097,7 @@ function Sessions({ sessionStorage, disabled }) { `)} -
+
@@ -2424,6 +2425,66 @@ const SVG_Moveable = ({...props}) => { `}; +const SVG_SortIndicator = ({ sortOrder, ...props }) => { + return html` + <${SVG} + ...${props} + width="8" + height="8" + viewBox="0 0 8 8" + style=${{ 'margin-left': '2px' }}> + ${sortOrder === 'asc' + ? html`` + : html``} + +`}; + +const SVG_SortName = ({...props}) => { + return html` + <${SVG} + ...${props} + width="16" + height="16" + viewBox="0 0 16 16"> + + + +`}; + +const SVG_SortTokens = ({...props}) => { + return html` + <${SVG} + ...${props} + width="16" + height="16" + viewBox="0 0 16 16"> + + +`}; + +const SVG_SortSize = ({...props}) => { + return html` + <${SVG} + ...${props} + width="16" + height="16" + viewBox="0 0 16 16"> + + +`}; + +const SVG_SortDate = ({...props}) => { + return html` + <${SVG} + ...${props} + width="16" + height="16" + viewBox="0 0 16 16"> + + +`}; + + function Modal({ isOpen, onClose, title, description, children, ...props }) { if (!isOpen) { return null; @@ -3725,6 +3786,423 @@ function InstructModal({ isOpen, closeModal, predict, cancel, modalState, templa `; } +function SavedPromptsModal({ isOpen, closeModal, cancel }) { + const [savedPrompts, setSavedPrompts] = useState([]); + const [simplePrompts, setSimplePrompts] = useState([]); + const [selectedPrompt, setSelectedPrompt] = useState(null); + const [loading, setLoading] = useState(false); + const [saveName, setSaveName] = useState(''); + const [loadSlot, setLoadSlot] = useState(''); + const [activeTab, setActiveTab] = useState('saved'); // 'saved' or 'simple' + const [sortBy, setSortBy] = useState('name'); // 'name', 'tokens', 'size', 'date' + const [sortOrder, setSortOrder] = useState('asc'); // 'asc' or 'desc' + const [listWidth, setListWidth] = useState(300); + const [modalHeight, setModalHeight] = useState(500); + const [renameNewName, setRenameNewName] = useState(''); + const [renameOldName, setRenameOldName] = useState(''); + + // Fetch data on modal open + useEffect(() => { + if (isOpen) { + fetchSavedPrompts(); + fetchSimplePrompts(); + } + }, [isOpen]); + + const fetchSavedPrompts = async () => { + setLoading(true); + try { + const res = await fetch('/list'); + const data = await res.json(); + setSavedPrompts(data); + } catch (error) { + console.error('Failed to fetch saved prompts:', error); + } + setLoading(false); + }; + + const fetchSimplePrompts = async () => { + try { + const res = await fetch('/slots/list'); + const data = await res.json(); + setSimplePrompts(data); + } catch (error) { + console.error('Failed to fetch simple prompts:', error); + } + }; + + const handleSort = (type) => { + if (sortBy === type) { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + } else { + setSortBy(type); + setSortOrder('asc'); + } + }; + + const sortedPrompts = [...savedPrompts].sort((a, b) => { + let compareValue = 0; + switch (sortBy) { + case 'name': + compareValue = a.filename.localeCompare(b.filename); + break; + case 'tokens': + compareValue = a.token_count - b.token_count; + break; + case 'size': + compareValue = a.filesize - b.filesize; + break; + case 'date': + compareValue = (new Date(a.mtime))- (new Date(b.mtime)); + break; + } + return sortOrder === 'asc' ? compareValue : -compareValue; + }); + + const handleSavePrompt = async (slot_id) => { + if (!saveName.trim()) { + alert('Please enter a name for the saved prompt'); + return; + } + + try { + await fetch('/slots/' + slot_id + '?action=save', { + method: 'POST', + body: JSON.stringify({ + filename: `${saveName}.bin`, + }) + }); + setSaveName(''); + fetchSavedPrompts(); + } catch (error) { + console.error('Failed to save prompt:', error); + } + }; + + const handleLoadPrompt = async (filename) => { + if (loadSlot === '') { + alert('Please select a slot to restore to'); + return; + } + if (confirm(`Load "${filename}" into slot "${loadSlot}"? This will overwrite what is currently there.`)) { + try { + await fetch('/slots/' + loadSlot + '?action=restore', { + method: 'POST', + body: JSON.stringify({ filename }) + }); + fetchSimplePrompts(); + } catch (error) { + console.error('Failed to load prompt:', error); + } + } + }; + + const handleDeletePrompt = async (filename) => { + if (confirm(`Delete "${filename}"? This cannot be undone.`)) { + try { + await fetch('/delete_prompt', { + method: 'POST', + body: JSON.stringify({ filename }) + }); + setSelectedPrompt(null); + fetchSavedPrompts(); + } catch (error) { + console.error('Failed to delete prompt:', error); + } + } + }; + + const handleRenamePrompt = async (old_filename, new_filename) => { + try { + await fetch('/rename_prompt', { + method: 'POST', + body: JSON.stringify({ old_filename, new_filename }) + }); + fetchSavedPrompts(); + } catch (error) { + console.error('Failed to rename prompt:', error); + } + }; + + const startRenamePrompt = (old_filename) => { + setRenameNewName(old_filename); + setRenameOldName(old_filename); + }; + + function handleKeyDown(old_filename, key) { + if (event.key === 'Enter') { + if (renameOldName !== undefined) + handleRenamePrompt(old_filename, renameNewName); + } else if (event.key === 'Escape') { + if (renameOldName !== undefined) + setRenameOldName(undefined); + } + } + + const SortButton = ({ type, icon }) => html` + + `; + + + const formatFileSize = (bytes) => { + const units = ['B', 'KB', 'MB', 'GB']; + let size = bytes; + let unitIndex = 0; + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + return `${size.toFixed(1)} ${units[unitIndex]}`; + }; + +return html` + <${Modal} + isOpen=${isOpen} + onClose=${closeModal} + title="Saved Prompts" + description="Manage your saved prompts and slots." + style=${{ + 'height': `${modalHeight}px`, + 'resize': 'vertical', + 'overflow': 'auto', + 'minHeight': '200px', + 'maxHeight': '90vh' + }}> + +
+ + +
+ + +
+ + +
+ + ${activeTab === 'saved' ? html` + +
+ <${SortButton} type="name" icon=${html`<${SVG_SortName}/>`}/> + <${SortButton} type="tokens" icon=${html`<${SVG_SortTokens}/>`}/> + <${SortButton} type="size" icon=${html`<${SVG_SortSize}/>`}/> + <${SortButton} type="date" icon=${html`<${SVG_SortDate}/>`}/> +
+ + + ${loading ? html` +
+ Loading... +
+ ` : html` + + `}` : html` + + + `} +
+ + +
{ + const startX = e.clientX; + const startWidth = listWidth; + + const handleMouseMove = (e) => { + const newWidth = Math.max(200, Math.min(600, startWidth + e.clientX - startX)); + setListWidth(newWidth); + }; + + const handleMouseUp = () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }}> +
+
+ + +
+ ${selectedPrompt ? html` +
+

${Object.hasOwn(selectedPrompt, 'slot_id') ? `Slot #${selectedPrompt.slot_id}` : selectedPrompt.filename.replace('.bin', '')}

+ ${Object.hasOwn(selectedPrompt, 'slot_id') && html` +
+ ${selectedPrompt.token_count.toLocaleString()} tokens +
+ <${InputBox} + 'label'="" + 'type'="text" + 'placeholder'="Name for saved prompt" + 'readOnly'=${!!cancel} + 'value'=${saveName} + 'onValueChange'=${setSaveName} + style=${{ 'flex': 1, 'width': '10vw'}}/> + +
+ `} + ${Object.hasOwn(selectedPrompt, 'filename') && html` +
+ ${selectedPrompt.token_count.toLocaleString()} tokens + ${formatFileSize(selectedPrompt.filesize)} + ${new Date(selectedPrompt.mtime).toLocaleString()} +
+ <${SelectBox} + label="Slot" + value=${loadSlot} + onValueChange=${setLoadSlot} + options=${[{ name: '', value: '' }, ...simplePrompts.map(prompt => ({ name: prompt.slot_id, value: prompt.slot_id}))]}/> + +
+ `} +
+