From 5e908da5aabf83826fd729e89c5c713a785950f2 Mon Sep 17 00:00:00 2001 From: AML - A Laycock Date: Thu, 30 Jul 2026 10:45:16 +0100 Subject: [PATCH 1/6] feat: increment version --- app/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/constants.ts b/app/lib/constants.ts index 0d5682d..7963fb8 100644 --- a/app/lib/constants.ts +++ b/app/lib/constants.ts @@ -1,4 +1,4 @@ -export const VERSION = '1.7.0' +export const VERSION = '1.7.1' export const RequiredVersions = { controller: VERSION, From fb658821c63ae0d049b7178843dbf996bcb3670a Mon Sep 17 00:00:00 2001 From: Adam Laycock Date: Thu, 30 Jul 2026 14:23:32 +0100 Subject: [PATCH 2/6] feat: improve the control point api (#29) --- app/lib/constants.ts | 3 +- app/lib/settings.server.ts | 2 ++ app/locales/en.ts | 3 ++ app/routes/about.tsx | 47 +++++++++++++++++++++++++-- app/routes/control-point.config.tsx | 21 +++++++++--- app/routes/control-point.trigger.tsx | 2 +- app/routes/settings.tsx | 48 +++++++++++++++++++++++----- 7 files changed, 110 insertions(+), 16 deletions(-) diff --git a/app/lib/constants.ts b/app/lib/constants.ts index 7963fb8..118f487 100644 --- a/app/lib/constants.ts +++ b/app/lib/constants.ts @@ -5,7 +5,8 @@ export const RequiredVersions = { tts: '2.0.0', piper: '1.3.0', sounder: '2.4.0', - button: '1.0.0' + button: '1.0.0', + controlPoint: '1.0.0' } export const DOCS_URL = `https://openschoolbell.co.uk` diff --git a/app/lib/settings.server.ts b/app/lib/settings.server.ts index d8ecdb4..7874a26 100644 --- a/app/lib/settings.server.ts +++ b/app/lib/settings.server.ts @@ -9,6 +9,7 @@ type SettingKey = | 'ttsSpeed' | 'enrollUrl' | 'controlPointKey' + | 'controlPointDefaultZone' | 'ttsLastSeen' | 'workerLastSeen' | 'lockdownEntrySequence' @@ -22,6 +23,7 @@ export const DEFAULT_SETTINGS: {[setting in SettingKey]: string} = { ttsSpeed: '1', enrollUrl: 'http://controller:3000', controlPointKey: '', + controlPointDefaultZone: '', ttsLastSeen: '"1970-01-01T23:00:00.000Z"', workerLastSeen: '"1970-01-01T23:00:00.000Z"', lockdownEntrySequence: '[]', diff --git a/app/locales/en.ts b/app/locales/en.ts index 7e1b01f..861ea53 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -222,6 +222,9 @@ export const en = { 'settings.controlPointKey.label': 'Control Point Key', 'settings.controlPointKey.helper': 'The key used by the Control Point App to communicate with the controller.', + 'settings.controlPointDefaultZone.label': 'Control Point Zone', + 'settings.controlPointDefaultZone.helper': + 'Which zone should the control point user interface default to?', 'settings.password.label': 'Change password', 'settings.password.helper': 'Leave fields empty to keep the current password.', diff --git a/app/routes/about.tsx b/app/routes/about.tsx index e0a4632..7066156 100644 --- a/app/routes/about.tsx +++ b/app/routes/about.tsx @@ -131,6 +131,25 @@ export const loader = async ({request}: LoaderFunctionArgs) => { .catch(() => resolve('error')) }) + const controlPointLatest = await new Promise(resolve => { + fetch( + 'https://api.github.com/repos/Open-School-Bell/control-point/releases?per_page=1', + { + headers: { + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28' + } + } + ) + .then(response => { + response + .json() + .then(data => resolve(data[0].tag_name)) + .catch(() => resolve('error')) + }) + .catch(() => resolve('error')) + }) + const prisma = getPrisma() const redis = getRedis() @@ -160,6 +179,8 @@ export const loader = async ({request}: LoaderFunctionArgs) => { buttonVersions[id] = version ? version : '0.0.0' }) + const controlPointVersion = await redis.get('osb-control-point-version') + const license = ( await readFile(path.join(process.cwd(), 'LICENSE')) ).toString() @@ -174,7 +195,9 @@ export const loader = async ({request}: LoaderFunctionArgs) => { buttonLatest, ttsLatest, controllerLatest, - license + license, + controlPointVersion, + controlPointLatest } } @@ -194,7 +217,9 @@ const About = () => { buttonLatest, ttsLatest, controllerLatest, - license + license, + controlPointVersion, + controlPointLatest } = useLoaderData() const {t} = useTranslation() @@ -284,6 +309,24 @@ const About = () => { ) })} + {controlPointVersion ? ( + + Control Point + {controlPointVersion} + + {controlPointLatest.replace('v', '')} + + + {RequiredVersions.controlPoint} + + + ) : ( + '' + )} diff --git a/app/routes/control-point.config.tsx b/app/routes/control-point.config.tsx index 95513f2..33daf83 100644 --- a/app/routes/control-point.config.tsx +++ b/app/routes/control-point.config.tsx @@ -1,10 +1,16 @@ import {type LoaderFunctionArgs} from '@remix-run/node' -import {getSetting} from '~/lib/settings.server' +import {getSettings} from '~/lib/settings.server' import {getPrisma} from '~/lib/prisma.server' +import {getRedis} from '~/lib/redis.server.mjs' export const loader = async ({request}: LoaderFunctionArgs) => { - const controlPointKey = await getSetting('controlPointKey') + const {controlPointKey, controlPointDefaultZone} = await getSettings([ + 'controlPointKey', + 'controlPointDefaultZone' + ]) + + const redis = getRedis() if (controlPointKey === '') { return Response.json({ @@ -14,13 +20,18 @@ export const loader = async ({request}: LoaderFunctionArgs) => { } const authHeader = request.headers.get('Auth') + const versionHeader = request.headers.get('Version') if (!authHeader) { return Response.json({result: 'error', error: 'No key provided.'}) } if (authHeader !== controlPointKey) { - return Response.json({result: 'error', error: 'Invalid Ket provided.'}) + return Response.json({result: 'error', error: 'Invalid Key provided.'}) + } + + if (versionHeader) { + void redis.set(`osb-control-point-version`, versionHeader) } const prisma = getPrisma() @@ -30,6 +41,8 @@ export const loader = async ({request}: LoaderFunctionArgs) => { return Response.json({ zones: zones.map(({id, name}) => { return {id, name} - }) + }), + defaultZone: + controlPointDefaultZone !== '' ? controlPointDefaultZone : zones[0].id }) } diff --git a/app/routes/control-point.trigger.tsx b/app/routes/control-point.trigger.tsx index 617b443..018bebc 100644 --- a/app/routes/control-point.trigger.tsx +++ b/app/routes/control-point.trigger.tsx @@ -22,7 +22,7 @@ export const action = async ({request}: ActionFunctionArgs) => { } if (authHeader !== controlPointKey) { - return Response.json({result: 'error', error: 'Invalid Ket provided.'}) + return Response.json({result: 'error', error: 'Invalid Key provided.'}) } const data = (await request.json()) as {pin: string; zone: string} diff --git a/app/routes/settings.tsx b/app/routes/settings.tsx index cd9b7d7..2314fae 100644 --- a/app/routes/settings.tsx +++ b/app/routes/settings.tsx @@ -4,7 +4,7 @@ import { type MetaFunction, redirect } from '@remix-run/node' -import {useLoaderData} from '@remix-run/react' +import {Form, useLoaderData} from '@remix-run/react' import {invariant} from '@arcath/utils' import {getSettings, setSetting} from '~/lib/settings.server' @@ -14,6 +14,7 @@ import {Page, FormElement} from '~/lib/ui' import {useTranslation} from '~/lib/i18n' import {translate} from '~/lib/i18n.shared' import {getRootI18n} from '~/lib/i18n.meta' +import {getPrisma} from '~/lib/prisma.server' export const meta: MetaFunction = ({matches}) => { const {messages} = getRootI18n(matches) @@ -27,16 +28,27 @@ export const loader = async ({request}: LoaderFunctionArgs) => { return redirect('/login') } - const {ttsSpeed, enrollUrl, controlPointKey} = await getSettings([ - 'ttsSpeed', - 'enrollUrl', - 'controlPointKey' - ]) + const prisma = getPrisma() + + const {ttsSpeed, enrollUrl, controlPointKey, controlPointDefaultZone} = + await getSettings([ + 'ttsSpeed', + 'enrollUrl', + 'controlPointKey', + 'controlPointDefaultZone' + ]) + + const zones = await prisma.zone.findMany({ + select: {id: true, name: true}, + orderBy: {name: 'asc'} + }) return { ttsSpeed, enrollUrl, - controlPointKey + controlPointKey, + controlPointDefaultZone, + zones } } @@ -52,14 +64,19 @@ export const action = async ({request}: ActionFunctionArgs) => { | undefined) ? (formData.get('controlPointKey') as string | undefined) : '' + const controlPointDefaultZone = formData.get('controlPointDefaultZone') as + | string + | undefined invariant(enrollUrl) invariant(ttsSpeed) invariant(controlPointKey) + invariant(controlPointDefaultZone) await setSetting('enrollUrl', enrollUrl) await setSetting('ttsSpeed', ttsSpeed) await setSetting('controlPointKey', controlPointKey) + await setSetting('controlPointDefaultZone', controlPointDefaultZone) if (password && checkPassword && password === checkPassword) { await setSetting('password', password) @@ -69,7 +86,8 @@ export const action = async ({request}: ActionFunctionArgs) => { } const Settings = () => { - const {ttsSpeed, enrollUrl, controlPointKey} = useLoaderData() + const {ttsSpeed, enrollUrl, controlPointKey, controlPointDefaultZone, zones} = + useLoaderData() const {t} = useTranslation() return ( @@ -108,6 +126,20 @@ const Settings = () => { defaultValue={controlPointKey} /> + + + Date: Thu, 30 Jul 2026 14:36:14 +0100 Subject: [PATCH 3/6] feat: site name as a setting --- app/lib/settings.server.ts | 4 ++- app/locales/en.ts | 3 ++ app/root.tsx | 9 ++++-- app/routes/_index.tsx | 35 +++++++++++++++------ app/routes/control-point.config.tsx | 13 +++++--- app/routes/settings.tsx | 47 +++++++++++++++++++++++------ 6 files changed, 84 insertions(+), 27 deletions(-) diff --git a/app/lib/settings.server.ts b/app/lib/settings.server.ts index 7874a26..2e301ba 100644 --- a/app/lib/settings.server.ts +++ b/app/lib/settings.server.ts @@ -14,6 +14,7 @@ type SettingKey = | 'workerLastSeen' | 'lockdownEntrySequence' | 'lockdownExitSequence' + | 'siteName' export const DEFAULT_SETTINGS: {[setting in SettingKey]: string} = { lockdownRepeat: '5', @@ -27,7 +28,8 @@ export const DEFAULT_SETTINGS: {[setting in SettingKey]: string} = { ttsLastSeen: '"1970-01-01T23:00:00.000Z"', workerLastSeen: '"1970-01-01T23:00:00.000Z"', lockdownEntrySequence: '[]', - lockdownExitSequence: '[]' + lockdownExitSequence: '[]', + siteName: 'Open School Bell' } export const getSetting = async (setting: SettingKey) => { diff --git a/app/locales/en.ts b/app/locales/en.ts index 861ea53..527911f 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -230,6 +230,9 @@ export const en = { 'Leave fields empty to keep the current password.', 'settings.password.placeholderNew': 'New password', 'settings.password.placeholderConfirm': 'Repeat new password', + 'settings.siteName.label': 'Site Name', + 'settings.siteName.helper': + 'The site names to display at the top of the application.', 'schedule.metaTitle': 'Schedule', 'schedule.pageTitle': 'Schedule', 'schedule.defaultOption': 'Default', diff --git a/app/root.tsx b/app/root.tsx index 95b40cc..4c11875 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -38,6 +38,7 @@ import {locales, type SupportedLocale} from '~/locales' import {SidebarLink, NavSep} from './lib/ui' import {I18nProvider, useTranslation} from './lib/i18n' import {initTranslations, type InitTranslationsReturn} from './lib/i18n.server' +import {getSetting} from './lib/settings.server' const FALLBACK_LOCALE: SupportedLocale = 'en' const FALLBACK_TRANSLATIONS: InitTranslationsReturn = { @@ -49,7 +50,10 @@ export const links: LinksFunction = () => [] export const loader = async ({request}: LoaderFunctionArgs) => { const {locale, messages} = initTranslations(request) - return json({locale, messages}) + + const siteName = await getSetting('siteName') + + return {locale, messages, siteName} } export function Layout({children}: {children: React.ReactNode}) { @@ -74,13 +78,14 @@ export function Layout({children}: {children: React.ReactNode}) { } const AppContent = () => { + const {siteName} = useLoaderData() const {t} = useTranslation() return (
- {t('app.title')} + {siteName}
diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index a27197f..0d0859f 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -31,13 +31,23 @@ export const loader = async ({request}: LoaderFunctionArgs) => { const buttons = await prisma.actionButton.findMany({orderBy: {name: 'asc'}}) const logs = await prisma.log.findMany({orderBy: {time: 'desc'}, take: 10}) - const {lockdownMode, workerLastSeen, ttsLastSeen} = await getSettings([ - 'lockdownMode', - 'workerLastSeen', - 'ttsLastSeen' - ]) + const {lockdownMode, workerLastSeen, ttsLastSeen, siteName} = + await getSettings([ + 'lockdownMode', + 'workerLastSeen', + 'ttsLastSeen', + 'siteName' + ]) - return {sounders, lockdownMode, buttons, logs, workerLastSeen, ttsLastSeen} + return { + sounders, + lockdownMode, + buttons, + logs, + workerLastSeen, + ttsLastSeen, + siteName + } } export const meta: MetaFunction = ({matches}) => { @@ -46,14 +56,21 @@ export const meta: MetaFunction = ({matches}) => { } export default function Index() { - const {sounders, lockdownMode, buttons, logs, workerLastSeen, ttsLastSeen} = - useLoaderData() + const { + sounders, + lockdownMode, + buttons, + logs, + workerLastSeen, + ttsLastSeen, + siteName + } = useLoaderData() const {t, locale} = useTranslation() const dateLocale = locale === 'pl' ? pl : enUS useLivePageData() return ( - +

{t('dashboard.devices')}

diff --git a/app/routes/control-point.config.tsx b/app/routes/control-point.config.tsx index 33daf83..71e914a 100644 --- a/app/routes/control-point.config.tsx +++ b/app/routes/control-point.config.tsx @@ -5,10 +5,12 @@ import {getPrisma} from '~/lib/prisma.server' import {getRedis} from '~/lib/redis.server.mjs' export const loader = async ({request}: LoaderFunctionArgs) => { - const {controlPointKey, controlPointDefaultZone} = await getSettings([ - 'controlPointKey', - 'controlPointDefaultZone' - ]) + const {controlPointKey, controlPointDefaultZone, siteName} = + await getSettings([ + 'controlPointKey', + 'controlPointDefaultZone', + 'siteName' + ]) const redis = getRedis() @@ -43,6 +45,7 @@ export const loader = async ({request}: LoaderFunctionArgs) => { return {id, name} }), defaultZone: - controlPointDefaultZone !== '' ? controlPointDefaultZone : zones[0].id + controlPointDefaultZone !== '' ? controlPointDefaultZone : zones[0].id, + siteName }) } diff --git a/app/routes/settings.tsx b/app/routes/settings.tsx index 2314fae..79202f7 100644 --- a/app/routes/settings.tsx +++ b/app/routes/settings.tsx @@ -30,13 +30,19 @@ export const loader = async ({request}: LoaderFunctionArgs) => { const prisma = getPrisma() - const {ttsSpeed, enrollUrl, controlPointKey, controlPointDefaultZone} = - await getSettings([ - 'ttsSpeed', - 'enrollUrl', - 'controlPointKey', - 'controlPointDefaultZone' - ]) + const { + ttsSpeed, + enrollUrl, + controlPointKey, + controlPointDefaultZone, + siteName + } = await getSettings([ + 'ttsSpeed', + 'enrollUrl', + 'controlPointKey', + 'controlPointDefaultZone', + 'siteName' + ]) const zones = await prisma.zone.findMany({ select: {id: true, name: true}, @@ -48,7 +54,8 @@ export const loader = async ({request}: LoaderFunctionArgs) => { enrollUrl, controlPointKey, controlPointDefaultZone, - zones + zones, + siteName } } @@ -67,16 +74,19 @@ export const action = async ({request}: ActionFunctionArgs) => { const controlPointDefaultZone = formData.get('controlPointDefaultZone') as | string | undefined + const siteName = formData.get('siteName') as string | undefined invariant(enrollUrl) invariant(ttsSpeed) invariant(controlPointKey) invariant(controlPointDefaultZone) + invariant(siteName) await setSetting('enrollUrl', enrollUrl) await setSetting('ttsSpeed', ttsSpeed) await setSetting('controlPointKey', controlPointKey) await setSetting('controlPointDefaultZone', controlPointDefaultZone) + await setSetting('siteName', siteName) if (password && checkPassword && password === checkPassword) { await setSetting('password', password) @@ -86,13 +96,30 @@ export const action = async ({request}: ActionFunctionArgs) => { } const Settings = () => { - const {ttsSpeed, enrollUrl, controlPointKey, controlPointDefaultZone, zones} = - useLoaderData() + const { + ttsSpeed, + enrollUrl, + controlPointKey, + controlPointDefaultZone, + zones, + siteName + } = useLoaderData() const {t} = useTranslation() return (
+ + + Date: Mon, 3 Aug 2026 11:35:23 +0100 Subject: [PATCH 4/6] feat: log action source and push all actions into triggerAction function --- app/lib/log.server.ts | 7 +++++ app/lib/trigger-action.server.ts | 13 +++++++-- app/routes/button-api.trigger.tsx | 33 +++++++++-------------- app/routes/control-point.trigger.tsx | 12 ++++++--- app/routes/hook.$hook.tsx | 10 +++++-- app/routes/sounder-api.trigger-action.tsx | 19 +++++++++---- 6 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 app/lib/log.server.ts diff --git a/app/lib/log.server.ts b/app/lib/log.server.ts new file mode 100644 index 0000000..1b10d06 --- /dev/null +++ b/app/lib/log.server.ts @@ -0,0 +1,7 @@ +import {getPrisma} from './prisma.server' + +export const log = async (message: string) => { + const prisma = getPrisma() + + await prisma.log.create({data: {message}}) +} diff --git a/app/lib/trigger-action.server.ts b/app/lib/trigger-action.server.ts index c4e6720..1bd19d7 100644 --- a/app/lib/trigger-action.server.ts +++ b/app/lib/trigger-action.server.ts @@ -2,12 +2,21 @@ import {type Action} from '@prisma/client' import {broadcast} from './broadcast.server' import {toggleLockdown} from './lockdown.server' +import {log} from './log.server' + +export const triggerAction = async ( + action: Action, + zone: string | null | undefined, + source: string, + callbacks: {onMissingZone: (suppliedZone: string | null | undefined) => void} +) => { + await log(`🎬 Triggering action ${action.name} from ${source}`) -export const triggerAction = async (action: Action, zone: string) => { switch (action.action) { case 'broadcast': if (!zone || typeof zone !== 'string' || zone.trim() === '') { - return Response.json({error: 'missing zone'}, {status: 400}) + callbacks.onMissingZone(zone) + return } await broadcast(zone, action.data) diff --git a/app/routes/button-api.trigger.tsx b/app/routes/button-api.trigger.tsx index 745fea7..0ac1c7c 100644 --- a/app/routes/button-api.trigger.tsx +++ b/app/routes/button-api.trigger.tsx @@ -1,8 +1,7 @@ import {type ActionFunctionArgs} from '@remix-run/node' import {getPrisma} from '~/lib/prisma.server' -import {broadcast} from '~/lib/broadcast.server' -import {toggleLockdown} from '~/lib/lockdown.server' +import {triggerAction} from '~/lib/trigger-action.server' export const action = async ({request}: ActionFunctionArgs) => { const {key} = (await request.json()) as { @@ -26,23 +25,17 @@ export const action = async ({request}: ActionFunctionArgs) => { const zone = button.zoneId - switch (button.action.action) { - case 'broadcast': - if (!zone || typeof zone !== 'string' || zone.trim() === '') { - return Response.json({error: 'missing zone'}, {status: 400}) - } - - if (button.action.audioId) { - const zoneId = zone.trim() - await broadcast(zoneId, JSON.stringify([button.action.audioId])) - } - break - case 'lockdown': - await toggleLockdown() - break - default: - break - } + let response: {result: 'ok' | 'error'; error?: string} = {result: 'ok'} + let status = 200 + + await triggerAction(button.action, zone, `Button ${button.name}`, { + onMissingZone: () => { + response = {result: 'error', error: 'missing zone'} + status = 400 + + return + } + }) - return Response.json({ping: 'pong'}) + return Response.json(response, {status}) } diff --git a/app/routes/control-point.trigger.tsx b/app/routes/control-point.trigger.tsx index 018bebc..7751269 100644 --- a/app/routes/control-point.trigger.tsx +++ b/app/routes/control-point.trigger.tsx @@ -35,10 +35,16 @@ export const action = async ({request}: ActionFunctionArgs) => { return Response.json({result: 'error', error: 'No action for this pin'}) } - await triggerAction(action, data.zone) - - return Response.json({ + let response = { result: 'success', message: `${action.icon} ${action.name}` + } + + await triggerAction(action, data.zone, 'Control Point', { + onMissingZone: zone => { + response = {result: 'error', message: 'Zone does not exist'} + } }) + + return Response.json(response) } diff --git a/app/routes/hook.$hook.tsx b/app/routes/hook.$hook.tsx index 4669094..6d78127 100644 --- a/app/routes/hook.$hook.tsx +++ b/app/routes/hook.$hook.tsx @@ -25,7 +25,13 @@ export const action = async ({request, params}: ActionFunctionArgs) => { return {error: 'No zone provided.'} } - await triggerAction(webhook.action, zone) + let response = {status: 'ok'} - return {status: 'ok'} + await triggerAction(webhook.action, zone, `Webhook: ${params.hook}`, { + onMissingZone: zone => { + response = {status: 'zone not found'} + } + }) + + return Response.json(response) } diff --git a/app/routes/sounder-api.trigger-action.tsx b/app/routes/sounder-api.trigger-action.tsx index 5dc2e9a..8693cff 100644 --- a/app/routes/sounder-api.trigger-action.tsx +++ b/app/routes/sounder-api.trigger-action.tsx @@ -33,14 +33,23 @@ export const action = async ({request}: ActionFunctionArgs) => { }) if (!dbAction) { - return Response.json({error: 'action not found'}, {status: 404}) + return Response.json( + {result: 'error', error: 'action not found'}, + {status: 404} + ) } - if (!zone || typeof zone !== 'string' || zone.trim() === '') { - return Response.json({error: 'missing zone'}, {status: 400}) + let response: {result: 'error' | 'ok'; error?: string} = { + result: 'ok' } + let status = 200 - await triggerAction(dbAction, zone) + await triggerAction(dbAction, zone, `Sounder: ${sounder.name}`, { + onMissingZone: zone => { + response = {result: 'error', error: 'Zone not found.'} + status = 400 + } + }) - return Response.json({ping: 'pong'}) + return Response.json(response, {status}) } From 501eb67b5690e185faa2fbfffd9924fce9662e53 Mon Sep 17 00:00:00 2001 From: AML - A Laycock Date: Mon, 3 Aug 2026 13:28:29 +0100 Subject: [PATCH 5/6] feat: support tts 2.0.2 --- app/lib/constants.ts | 4 ++-- app/routes/sounds.add-tts.tsx | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/lib/constants.ts b/app/lib/constants.ts index 118f487..e5a5c56 100644 --- a/app/lib/constants.ts +++ b/app/lib/constants.ts @@ -2,8 +2,8 @@ export const VERSION = '1.7.1' export const RequiredVersions = { controller: VERSION, - tts: '2.0.0', - piper: '1.3.0', + tts: '2.0.2', + piper: '1.6.0', sounder: '2.4.0', button: '1.0.0', controlPoint: '1.0.0' diff --git a/app/routes/sounds.add-tts.tsx b/app/routes/sounds.add-tts.tsx index ad54a97..e3c0981 100644 --- a/app/routes/sounds.add-tts.tsx +++ b/app/routes/sounds.add-tts.tsx @@ -72,14 +72,17 @@ export const action = async ({request}: ActionFunctionArgs) => { } }) - const downloadResponse = await fetch(`${process.env.TTS_API}/piper`, { - body: JSON.stringify({ - text: tts, - length_scale: speed - }), - headers: {'Content-Type': 'application/json'}, - method: 'post' - }).catch(() => {}) + const downloadResponse = await fetch( + `${process.env.TTS_API}/piper/synthesize`, + { + body: JSON.stringify({ + text: tts, + length_scale: speed + }), + headers: {'Content-Type': 'application/json'}, + method: 'post' + } + ).catch(() => {}) const downloadStream = fs.createWriteStream( path.join(process.cwd(), 'public', 'sounds', `${sound.id}.wav`) From 1b8cab861b7aff1eff750e32d132fa157bdee6f7 Mon Sep 17 00:00:00 2001 From: AML - A Laycock Date: Tue, 4 Aug 2026 09:02:50 +0100 Subject: [PATCH 6/6] feat: improve lockdown styling --- app/routes/_index.tsx | 72 ++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 0d0859f..c3dc51f 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -109,44 +109,46 @@ export default function Index() {
-

- {t('dashboard.lockdown.message', { - status: t( - lockdownMode === '0' - ? 'dashboard.lockdown.status.disabled' - : 'dashboard.lockdown.status.enabled' - ) - })} -

- { - if ( - !confirm( - t( - lockdownMode === '0' - ? 'dashboard.lockdown.confirmEnable' - : 'dashboard.lockdown.confirmDisable' - ) +
+

+ {t('dashboard.lockdown.message', { + status: t( + lockdownMode === '0' + ? 'dashboard.lockdown.status.disabled' + : 'dashboard.lockdown.status.enabled' ) - ) { - e.preventDefault() - } - }} - > - - + + +

{t('dashboard.buttons')}