diff --git a/src/lib/components/gen-price-panel.svelte b/src/lib/components/gen-price-panel.svelte deleted file mode 100644 index 013dda6..0000000 --- a/src/lib/components/gen-price-panel.svelte +++ /dev/null @@ -1,246 +0,0 @@ - - -
-
-
-

{label}

-

- Generate and push {mode === 'price' ? 'price' : 'price-slot'} profile files to Machine -

-
-
- - - {#if isOurs && status === 'complete' && files.length > 0} - - {/if} - - -
-
- - {#if isOurs && status === 'receiving'} -
- 0 - ? (files.length / $genLayoutBatch.total_files) * 100 - : 0} - max={100} - class="h-2" - /> -

- Receiving files: {files.length} / {$genLayoutBatch.total_files} -

-
- {/if} - - {#if pushing} -
- 0 ? (pushProgress.current / pushProgress.total) * 100 : 0} - max={100} - class="h-2" - /> -

- Pushing to Android: {pushProgress.current} / {pushProgress.total} -

-
- {/if} - - {#if isOurs && status === 'complete' && files.length > 0} -
-

Generated {files.length} file(s):

-
- {#each files as file} -
- {file.file.split('/').pop()} -
- {/each} -
-
- {/if} - - {#if isOurs && status === 'error'} -
-

{$genLayoutBatch.error}

-
- {/if} -
- - - - - Reboot Machine? - - New {mode} prices only take effect after a full device reboot. The machine - will restart — this takes a few minutes. - - -
- - -
-
-
diff --git a/src/lib/core/handlers/messageHandler.ts b/src/lib/core/handlers/messageHandler.ts index aa3350e..1bd2932 100644 --- a/src/lib/core/handlers/messageHandler.ts +++ b/src/lib/core/handlers/messageHandler.ts @@ -261,23 +261,14 @@ const handlers: Record void> = { // Handle gen-service responses if (from === 'gen-service') { switch (level) { - case 'batch_start': { - const genRef = p.ref ?? 'gen-layout'; + case 'batch_start': handleGenLayoutBatchStart({ batch_id: p.batch_id, total_files: p.total_files, - total_size_bytes: p.total_size_bytes, - ref: genRef + total_size_bytes: p.total_size_bytes }); - const genLabel = - genRef === 'gen-price' - ? 'Gen Price' - : genRef === 'gen-priceslot' - ? 'Gen PriceSlot' - : 'Gen Layout'; - addNotification(`INFO:${genLabel} started (${p.total_files} files)`); + addNotification(`INFO:Gen Layout started (${p.total_files} files)`); break; - } case 'file': handleGenLayoutFile({ batch_id: p.batch_id, @@ -296,11 +287,11 @@ const handlers: Record void> = { batch_id: p.batch_id, total_files: p.total_files }); - addNotification('INFO:Generation complete'); + addNotification('INFO:Gen Layout complete'); break; case 'ERROR': handleGenLayoutError(msg); - addNotification(`ERR:Generation error: ${msg}`); + addNotification(`ERR:Gen Layout error: ${msg}`); break; default: logger.info('[GenService] Received:', level, msg); diff --git a/src/lib/core/services/sheetService.ts b/src/lib/core/services/sheetService.ts index 7cb0d05..2034fb7 100644 --- a/src/lib/core/services/sheetService.ts +++ b/src/lib/core/services/sheetService.ts @@ -325,7 +325,7 @@ export async function requestGenLayout(country: string): Promise { }; } - setGenLayoutGenerating('gen-layout'); + setGenLayoutGenerating(); logger.info('[sheetService] Sending gen-layout request for country:', country); @@ -344,79 +344,6 @@ export async function requestGenLayout(country: string): Promise { }); } -/** - * Trigger gen-service to (re)generate the machine price profile from Grist. - * Backend: taobin_admin app.py `gen-price` branch -> gen_price_supra.gen_price. - * Produces profile__master.json, commits to recipe-repo, streams files back - * (ref='gen-price') for the frontend to push to the machine. - */ -export async function requestGenPrice(country: string): Promise { - const curr_user = get(auth); - - let user_info: any = {}; - if (curr_user) { - user_info = { - displayName: curr_user.displayName, - email: curr_user.email, - uid: curr_user.uid - }; - } - - setGenLayoutGenerating('gen-price'); - - logger.info('[sheetService] Sending gen-price request for country:', country); - - return await sendMessage({ - type: 'command', - payload: { - user_info, - srv_name: 'gen-service', - values: { - file_layout: 'sheet', - file_desc: 'sheet', - country: country, - param: 'gen-price-supra_app' - } - } - }); -} - -/** - * Trigger gen-service to (re)generate the per-slot price profiles from Grist. - * Backend: taobin_admin app.py `gen-priceslot` branch -> gen_price_supra.gen_priceslot. - * Produces profile__slot_.json (ref='gen-priceslot'). - */ -export async function requestGenPriceSlot(country: string): Promise { - const curr_user = get(auth); - - let user_info: any = {}; - if (curr_user) { - user_info = { - displayName: curr_user.displayName, - email: curr_user.email, - uid: curr_user.uid - }; - } - - setGenLayoutGenerating('gen-priceslot'); - - logger.info('[sheetService] Sending gen-priceslot request for country:', country); - - return await sendMessage({ - type: 'command', - payload: { - user_info, - srv_name: 'gen-service', - values: { - file_layout: 'sheet', - file_desc: 'sheet', - country: country, - param: 'gen-priceslot-supra_app' - } - } - }); -} - /** * Request price data from sheet for specific product codes * NOTE: Can only send once per type (price). Use hasSheetPriceBeenSent to check. diff --git a/src/lib/core/stores/genLayoutStore.ts b/src/lib/core/stores/genLayoutStore.ts index ccf811c..d0a5e7c 100644 --- a/src/lib/core/stores/genLayoutStore.ts +++ b/src/lib/core/stores/genLayoutStore.ts @@ -7,20 +7,12 @@ export interface GenLayoutFile { file_index: number; } -// Kind of gen-service batch. Distinguishes which files were generated so the -// push logic can map them to the correct machine path. -// gen-layout -> layout files under taobin_project/ -// gen-price -> profile__master.json -> /sdcard/coffeevending/profile/ -// gen-priceslot -> profile__slot_.json -> .../price/ -export type GenKind = 'gen-layout' | 'gen-price' | 'gen-priceslot'; - export interface GenLayoutBatch { batch_id: string; total_files: number; total_size_bytes: number; status: 'idle' | 'generating' | 'receiving' | 'complete' | 'error'; files: GenLayoutFile[]; - ref: GenKind; error?: string; } @@ -37,8 +29,7 @@ const initialState: GenLayoutBatch = { total_files: 0, total_size_bytes: 0, status: 'idle', - files: [], - ref: 'gen-layout' + files: [] }; export const genLayoutBatch = writable(initialState); @@ -61,17 +52,15 @@ export function handleGenLayoutBatchStart(payload: { batch_id: string; total_files: number; total_size_bytes: number; - ref?: GenKind; }) { genLayoutBatch.set({ batch_id: payload.batch_id, total_files: payload.total_files, total_size_bytes: payload.total_size_bytes, status: 'receiving', - files: [], - ref: payload.ref ?? get(genLayoutBatch).ref ?? 'gen-layout' + files: [] }); - logger.info('[GenLayout] Batch started:', payload.batch_id, 'ref:', payload.ref, 'total files:', payload.total_files); + logger.info('[GenLayout] Batch started:', payload.batch_id, 'total files:', payload.total_files); } export function handleGenLayoutFile(payload: { @@ -90,10 +79,7 @@ export function handleGenLayoutFile(payload: { if (payload.is_chunked) { const fileIndex = payload.file_index; - // Backend sends part_index 1-based (1..N); normalize to 0-based so it - // matches the 0..totalParts-1 assembly loop below. Without this, part 1 - // was stored empty and the last chunk was dropped (files > 1MB corrupted). - const partIndex = (payload.part_index ?? 1) - 1; + const partIndex = payload.part_index ?? 0; const totalParts = payload.total_parts ?? 1; // Get or create tracker for this file @@ -256,10 +242,9 @@ export function resetGenLayoutBatch() { chunkedFiles.clear(); } -export function setGenLayoutGenerating(ref: GenKind = 'gen-layout') { +export function setGenLayoutGenerating() { genLayoutBatch.update((batch) => ({ ...batch, - ref, status: 'generating' })); } diff --git a/src/routes/(authed)/sheet/price/[country]/+page.svelte b/src/routes/(authed)/sheet/price/[country]/+page.svelte index 96a1a8a..3667b69 100644 --- a/src/routes/(authed)/sheet/price/[country]/+page.svelte +++ b/src/routes/(authed)/sheet/price/[country]/+page.svelte @@ -24,7 +24,6 @@ import * as Table from '$lib/components/ui/table/index.js'; import Spinner from '$lib/components/ui/spinner/spinner.svelte'; import { Plus, RefreshCw, Save } from '@lucide/svelte/icons'; - import GenPricePanel from '$lib/components/gen-price-panel.svelte'; let selectedCountry = $state($page.params.country || get(departmentStore) || ''); let search = $state(''); @@ -212,12 +211,6 @@ - {#if selectedCountry} -
- -
- {/if} -
diff --git a/src/routes/(authed)/tools/video-mainpage/+page.svelte b/src/routes/(authed)/tools/video-mainpage/+page.svelte index 010fa54..4848f50 100644 --- a/src/routes/(authed)/tools/video-mainpage/+page.svelte +++ b/src/routes/(authed)/tools/video-mainpage/+page.svelte @@ -10,7 +10,6 @@ import Badge from '$lib/components/ui/badge/badge.svelte'; import Spinner from '$lib/components/ui/spinner/spinner.svelte'; import Progress from '$lib/components/ui/progress/progress.svelte'; - import { Checkbox } from '$lib/components/ui/checkbox/index.js'; import { Upload, X, @@ -23,8 +22,7 @@ CalendarDays, Clock, ChevronDown, - ImageIcon, - FlaskConical + ImageIcon } from '@lucide/svelte/icons'; import * as adb from '$lib/core/adb/adb'; import { env } from '$env/dynamic/public'; @@ -34,7 +32,6 @@ const CREATE_ENDPOINT = '/api/video-mainpage'; const LIST_ENDPOINT = '/api/video-mainpage/list'; const UPDATE_ENDPOINT = '/api/video-mainpage/update'; - const TESTMODE_ENDPOINT = '/api/video-mainpage/testmode'; const MACHINE_PROJECT_DIR = '/sdcard/coffeevending/taobin_project'; const GET_IMAGE = env.PUBLIC_GET_IMAGE; const DURATION_TRIM = 4; // brewing play seconds = video length − 4 @@ -106,26 +103,6 @@ let pushProgress = $state({ percent: 0, name: '', active: false }); let isAdbConnected = $derived(Boolean(AdbInstance.instance)); - // ── test mode (machine-local date/time override) ───────────────────────── - // '' = unset (leave that clock field on the real value). - let testMonth = $state(''); - let testDay = $state(''); - let testHour = $state(''); - let testMinute = $state(''); - let testEnabled = $state(true); // emits `Var TestMode = 1` so the gate fires - let applyingTest = $state(false); - const hasTestValues = $derived( - [testMonth, testDay, testHour, testMinute].some((v) => v !== '') - ); - - // Dropdown options. Hour/minute are zero-padded for display; the value is the - // plain number string (the backend parses it with int()). - const MONTH_OPTS = Array.from({ length: 12 }, (_, i) => String(i + 1)); - const DAY_OPTS = Array.from({ length: 31 }, (_, i) => String(i + 1)); - const HOUR_OPTS = Array.from({ length: 24 }, (_, i) => String(i)); - const MINUTE_OPTS = Array.from({ length: 60 }, (_, i) => String(i)); - const pad2 = (s: string) => s.padStart(2, '0'); - // ── list ──────────────────────────────────────────────────────────────── let managed = $state([]); let readonlyList = $state([]); @@ -309,49 +286,6 @@ } } - // Inject (or, when `clear`, remove) the TestMode date/time override in the - // header of the web scripts and push them to the connected machine over ADB. - // Never touches git/FTP — this override is machine-local by design. - async function applyTestMode(clear = false) { - if (!AdbInstance.instance) return addNotification('ERR:Connect a machine first'); - const body = clear - ? {} - : { - enabled: testEnabled, - month: testMonth, - day: testDay, - hour: testHour, - minute: testMinute - }; - applyingTest = true; - pushProgress = { percent: 0, name: '', active: true }; - try { - const res = await fetch(`${TESTMODE_ENDPOINT}?country=${encodeURIComponent(country)}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body) - }); - const result = await res.json().catch(() => ({})); - if (!res.ok) throw new Error(result?.message || result?.detail || 'Test-mode apply failed'); - await pushScripts(result?.content?.scripts ?? []); - if (result?.cleared) { - testMonth = testDay = testHour = testMinute = ''; - testEnabled = false; - addNotification(`INFO:Test mode cleared on machine (${country})`); - } else { - const o = result?.override ?? {}; - addNotification( - `INFO:Test mode pushed to machine — ${o.month ?? '–'}/${o.day ?? '–'} ${o.hour ?? '–'}:${o.minute ?? '–'} (${country})` - ); - } - } catch (error) { - addNotification(`ERR:${error instanceof Error ? error.message : 'Test-mode failed'}`); - } finally { - applyingTest = false; - pushProgress = { percent: 0, name: '', active: false }; - } - } - async function handleSubmit() { const user = $auth; if (!user) return addNotification('ERR:Not logged in'); @@ -402,15 +336,9 @@ ); await pushScripts(result?.content?.scripts ?? []); - if (result?.duplicate) { - addNotification( - `WARN:"${name.trim()}" already exists as brewing_adv${result.n} — pushed to machine, no duplicate created` - ); - } else { - if (result?.sftp?.error) - addNotification(`WARN:Uploaded but FTP sync failed: ${result.sftp.error}`); - addNotification(`INFO:Added "${name.trim()}" as brewing_adv${result.n} (${country})`); - } + if (result?.sftp?.error) + addNotification(`WARN:Uploaded but FTP sync failed: ${result.sftp.error}`); + addNotification(`INFO:Added "${name.trim()}" as brewing_adv${result.n} (${country})`); clearMain(); clearBrewing(); clearBrewingTxt(); @@ -781,100 +709,6 @@ - - - - - Test mode - - - Override the machine clock (AdvSystem*) to preview - date-gated videos. Gated by TestMode = 1 and pushed to - the connected machine only — never committed or synced. Fill only what you need. - - - -
-
- - - {testMonth || '—'} - - - {#each MONTH_OPTS as m} - {m} - {/each} - - -
-
- - - {testDay || '—'} - - - {#each DAY_OPTS as d} - {d} - {/each} - - -
-
- - - {testHour === '' ? '—' : pad2(testHour)} - - - {#each HOUR_OPTS as h} - {pad2(h)} - {/each} - - -
-
- - - {testMinute === '' ? '—' : pad2(testMinute)} - - - {#each MINUTE_OPTS as mi} - {pad2(mi)} - {/each} - - -
-
- -
- - - {#if !isAdbConnected} - Connect a machine to apply. - {/if} -
-
-
-
diff --git a/src/routes/api/video-mainpage/testmode/+server.ts b/src/routes/api/video-mainpage/testmode/+server.ts deleted file mode 100644 index 385fc97..0000000 --- a/src/routes/api/video-mainpage/testmode/+server.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { json, error } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { env } from '$env/dynamic/public'; - -const API_BASE = env.PUBLIC_POST_IMAGE; - -// Return the country's web video scripts with a TestMode date/time override -// injected (or removed). The caller pushes the result to a machine over ADB — -// nothing is committed or synced. POST because the Kong route is POST-only. -export const POST: RequestHandler = async ({ url, request }) => { - try { - const country = (url.searchParams.get('country') || 'tha').toLowerCase(); - const body = await request.json().catch(() => ({})); - const response = await fetch( - `${API_BASE}/video/mainpage/testmode/${encodeURIComponent(country)}`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body) - } - ); - if (!response.ok) { - const e = await response.json().catch(() => ({ detail: response.statusText })); - throw error(response.status, e.detail || 'Test-mode apply failed'); - } - return json(await response.json()); - } catch (err) { - console.error('[Video MainPage TestMode Proxy] Error:', err); - if (err && typeof err === 'object' && 'status' in err) throw err; - throw error(500, err instanceof Error ? err.message : 'Internal server error'); - } -};