feat: add PWA support and refine creativity parameter handling

- Register service worker (`sw.js`) with cache-first static assets and network-first HTML pages
- Update `site.webmanifest` with app name, colors, icons at `/static/`, and standalone display mode
- Add `<meta name="theme-color">` in layout for browser UI theming
- Simplify creativity slider: change range from 0–10 to 3.0–7.0 (step 0.5), aligning frontend and backend logic
- Adjust `generate.js` route and `generation.js` service to accept direct temperature values (3.0–7.0) instead of scaled inputs
This commit is contained in:
Brian Fertig 2026-04-13 19:42:30 -06:00
parent 4602087400
commit 428cfb1008
7 changed files with 84 additions and 10 deletions

View File

@ -1 +1,13 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
{
"name": "Bri-Tunes",
"short_name": "Bri-Tunes",
"description": "Retrofuture music player",
"start_url": "/",
"display": "standalone",
"background_color": "#05060d",
"theme_color": "#ff2bd6",
"icons": [
{ "src": "/static/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/static/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
]
}

54
public/sw.js Normal file
View File

@ -0,0 +1,54 @@
const CACHE = 'bri-tunes-v1';
const STATIC_PRECACHE = [
'/',
'/static/css/app.css',
'/static/js/player.js',
'/static/js/navigate.js',
'/static/js/bg-grid.js',
'/static/js/player-viz.js',
'/static/js/boot-sequence.js',
'/static/vendor/fonts/Orbitron.woff2',
'/static/vendor/fonts/Inter.woff2',
'/static/vendor/three/three.module.min.js',
];
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(STATIC_PRECACHE)));
self.skipWaiting();
});
self.addEventListener('activate', (e) => {
e.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
)
);
self.clients.claim();
});
self.addEventListener('fetch', (e) => {
const { request } = e;
const url = new URL(request.url);
// Never intercept audio streams or cross-origin requests.
if (url.pathname.startsWith('/stream/') || url.origin !== self.location.origin) return;
// Static assets: cache-first.
if (url.pathname.startsWith('/static/')) {
e.respondWith(
caches.match(request).then((cached) => cached || fetch(request))
);
return;
}
// HTML pages: network-first, fall back to cache.
e.respondWith(
fetch(request)
.then((res) => {
const clone = res.clone();
caches.open(CACHE).then((c) => c.put(request, clone));
return res;
})
.catch(() => caches.match(request))
);
});

View File

@ -24,6 +24,12 @@ app.use(pinoHttp());
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// Service worker — must be at root scope to intercept all page requests.
app.get('/sw.js', (req, res) => {
res.set('Service-Worker-Allowed', '/');
res.sendFile(path.join(__dirname, '..', 'public', 'sw.js'));
});
// Static assets (CSS, JS, vendor).
app.use('/static', express.static(path.join(__dirname, '..', 'public'), { maxAge: '1h' }));

View File

@ -123,7 +123,7 @@ module.exports = function generateRoutes(csrfProtection) {
bpm: parseInt(req.body.bpm, 10) || null,
keyscale: req.body.keyscale || null,
duration_seconds: parseInt(req.body.duration, 10) || null,
creativity: parseInt(req.body.creativity, 10) ?? 50,
creativity: parseFloat(req.body.creativity) || 3.5,
seed: parseInt(req.body.seed, 10) === -1 ? -1 : (parseInt(req.body.seed, 10) || -1),
};

View File

@ -112,8 +112,8 @@ async function submitToComfyUI(baseUrl, workflowJson, params, nodeIds) {
? Math.floor(Math.random() * 2 ** 32)
: params.seed;
// Scale creativity 0–10 (slider) → temperature 3.0–7.0
const temperature = 3.0 + ((params.creativity ?? 5) / 10) * 4.0;
// Creativity slider sends value directly in 3.0–7.0 range (step 0.5)
const temperature = params.creativity != null ? parseFloat(params.creativity) : 3.5;
const paramValues = {
style_prompt: params.style_prompt || '',

View File

@ -50,9 +50,9 @@
<label>
Creativity
<div class="gen-slider-wrap">
<input type="range" id="gen-creativity" min="0" max="10" step="1" value="5"
oninput="document.getElementById('gen-creativity-val').textContent = (3 + this.value * 0.4).toFixed(1)">
<span id="gen-creativity-val" class="gen-slider-val">5.0</span>
<input type="range" id="gen-creativity" min="3" max="7" step="0.5" value="3.5"
oninput="document.getElementById('gen-creativity-val').textContent = parseFloat(this.value).toFixed(1)">
<span id="gen-creativity-val" class="gen-slider-val">3.5</span>
</div>
</label>
@ -137,7 +137,7 @@
data-bpm="<%= g.bpm || '' %>"
data-keyscale="<%- encodeURIComponent(g.keyscale || '') %>"
data-duration="<%= g.duration_seconds || '' %>"
data-creativity="<%= g.creativity != null ? g.creativity : 5 %>"
data-creativity="<%= g.creativity != null ? g.creativity : 3.5 %>"
data-seed="<%= g.seed != null ? g.seed : -1 %>">
<td class="muted small"><%= new Date(g.created_at).toLocaleDateString() %></td>
<td class="gen-prompt-cell"><%= (g.style_prompt || '—').slice(0, 60) %><% if ((g.style_prompt || '').length > 60) { %>…<% } %></td>
@ -441,9 +441,9 @@
durationEl.value = row.dataset.duration || '';
seedEl.value = row.dataset.seed || '-1';
var creativity = row.dataset.creativity != null ? row.dataset.creativity : '5';
var creativity = row.dataset.creativity != null ? row.dataset.creativity : '3.5';
creativityEl.value = creativity;
if (creativityValEl) creativityValEl.textContent = (3 + parseFloat(creativity) * 0.4).toFixed(1);
if (creativityValEl) creativityValEl.textContent = parseFloat(creativity).toFixed(1);
// Set keyscale select
var keyscale = decodeURIComponent(row.dataset.keyscale || '');

View File

@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="theme-color" content="#ff2bd6">
<meta name="csrf-token" content="<%= csrfToken %>">
<title><%= typeof title !== 'undefined' ? title : siteName %></title>
<% if (typeof og !== 'undefined' && og) { %>
@ -54,5 +55,6 @@
<script type="module" src="/static/js/overlay-objects.js"></script>
<script type="module" src="/static/js/player-viz.js"></script>
<script type="module" src="/static/js/boot-sequence.js"></script>
<script>if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js');</script>
</body>
</html>