Merge branch 'master' into 'dev'

# Conflicts:
#   src/lib/core/handlers/adbPayloadHandler.ts
#   src/lib/core/handlers/messageHandler.ts
#   src/routes/(authed)/tools/brew/+page.svelte
This commit is contained in:
Pakin Tadatangsakul 2026-06-10 08:19:51 +00:00
commit 58b496d5c8
9 changed files with 304 additions and 42 deletions

View file

@ -1,3 +1,4 @@
import { get } from 'svelte/store';
import { updateMachineStatus } from '../stores/machineInfoStore';
import { addNotification } from '../stores/noti';
import {
@ -6,6 +7,7 @@ import {
} from '../services/androidRecipeExportService';
import { handleIncomingMessages } from './messageHandler';
import { setMenuSaved, setMenuSaveError } from '../stores/menuSaveStore';
import { recipeFromMachineQuery } from '../stores/recipeStore';
type AdbPayload = { type: string; payload: any };
@ -104,9 +106,21 @@ async function handleAdbPayload(raw_payload: string) {
let plist = payload.payload.split('/');
let pd = plist[0] ?? '';
let total_time = plist[1] ?? '';
let mode_ref = plist[2] ?? '';
// update recipe data store
console.log('brewing finish', pd, 'total time', total_time);
// update recipe from brew now
let recipeDevSnapshot = get(recipeFromMachineQuery) ?? {};
let recipe01Snap = recipeDevSnapshot['recipe'];
if (recipe01Snap) {
recipe01Snap[pd].total_time =
mode_ref != 'sim' ? total_time : recipe01Snap[pd].total_time;
recipeDevSnapshot['recipe'] = recipe01Snap;
recipeFromMachineQuery.set(recipeDevSnapshot);
}
}
break;

View file

@ -2,6 +2,7 @@ import { get, writable } from 'svelte/store';
import { addNotification, notiStore } from '../stores/noti';
import {
currentRecipeVersionsSelector,
lastRequestSheetPrice,
materialFromServerQuery,
priceRecipeData,
recipeData,
@ -9,6 +10,8 @@ import {
recipeLoading,
recipeOverviewData,
recipeStreamMeta,
streamingRawData,
streamingRawMeta,
toppingGroupFromServerQuery,
toppingListFromServerQuery
} from '../stores/recipeStore';
@ -36,13 +39,16 @@ import { type RecipeVersion } from '$lib/models/recipe_version.model';
import { goto } from '$app/navigation';
import { socketAlreadySendHeartbeat, socketConnectionOfflineCount } from '../stores/websocketStore';
import type { RecipePrice } from '$lib/models/price.model';
import { sendMessage } from './ws_messageSender';
import { sendCommandRequest, sendMessage } from './ws_messageSender';
import { auth as authStore } from '../stores/auth';
import { v4 as uuidv4 } from 'uuid';
import { handleSheetResponseFromNoti } from './sheetNotiHandler';
export const messages = writable<string[]>([]);
type WSMessage = { type: string; payload: any };
// MAXIMUM LIMIT = 1814355
const handlers: Record<string, (payload: any) => void> = {
chat: (p) => messages.update((m) => [...m, p]),
ping: (p) => console.log('ping from server'),
@ -311,6 +317,9 @@ const handlers: Record<string, (payload: any) => void> = {
}
// Default notification handling
let from_service = p.from ?? '';
let ref_service = p.ref ?? '';
if (target) {
let currentUsername = auth.currentUser?.displayName;
if (currentUsername && currentUsername === target) {
@ -353,11 +362,91 @@ const handlers: Record<string, (payload: any) => void> = {
console.log('get price length: ', content.length);
let current_price = get(priceRecipeData);
let lastRequestPriceInstance = get(lastRequestSheetPrice);
let saved_product_code_to_get_from_sheet = [];
let current_meta = get(recipeStreamMeta);
lastRequestPriceInstance[current_meta?.country ?? 'unknown'] = {};
for (const c of content) {
current_price[c.ProductCode] = c.NewPrice + (c.StringParam ? `,${c.StringParam}` : '');
lastRequestPriceInstance[current_meta?.country ?? 'unknown'][c.ProductCode] = '';
saved_product_code_to_get_from_sheet.push({
product_code: c.ProductCode
});
}
priceRecipeData.set(current_price);
console.log('check length', saved_product_code_to_get_from_sheet.length);
// set command request to stream mode so
let request_id = uuidv4();
lastRequestPriceInstance[request_id] = current_meta?.country ?? '';
let current_streaming_instance = get(streamingRawData);
current_streaming_instance[request_id] = '';
streamingRawData.set(current_streaming_instance);
sendCommandRequest('sheet', {
country: current_meta?.country ?? '',
content: saved_product_code_to_get_from_sheet,
param: 'price',
stream: true,
request_id
});
lastRequestSheetPrice.set(lastRequestPriceInstance);
},
raw_stream: (p) => {
let streamRawInstance = get(streamingRawData);
let sub_type = p.sub_type;
let request_id = p.request_id;
let size_per_chunk = p.size_per_chunk;
let total_chunks = p.total_chunks;
let idx = p.idx;
switch (sub_type) {
case 'price':
streamingRawMeta.set({
id: request_id,
total_size: total_chunks,
chunk_size: size_per_chunk,
progress: 0
});
break;
case 'chunk_price':
streamingRawMeta.set({
id: request_id,
total_size: total_chunks,
chunk_size: size_per_chunk,
progress: idx
});
let raw_payload = p.raw ?? '';
streamRawInstance[request_id] += raw_payload;
streamingRawData.set(streamRawInstance);
break;
case 'end_price':
let lastRequestPriceInstance = get(lastRequestSheetPrice);
let country = lastRequestPriceInstance[request_id];
try {
let raw_payload = JSON.parse(streamRawInstance[request_id]);
let ref_from_raw = raw_payload.payload.ref ?? '';
let from_service_raw = raw_payload.payload.from ?? '';
let parsed_payload = raw_payload.payload ?? '';
if (from_service_raw == 'sheet-service') {
handleSheetResponseFromNoti(parsed_payload, ref_from_raw, country);
delete streamRawInstance[request_id];
streamingRawData.set(streamRawInstance);
}
} catch (e) {
console.log(`end price process error: ${e}`);
}
break;
default:
}
},
heartbeat: (p) => {
socketConnectionOfflineCount.set(0);
@ -395,5 +484,14 @@ export function handleIncomingMessages(raw: string) {
addNotification('ERR:No response from server');
return;
}
// raw streaming type
if (msg.type.startsWith('raw_stream')) {
// convert
let sub_type = msg.type.replace('raw_stream_', '');
msg.payload.sub_type = sub_type;
msg.type = 'raw_stream';
}
handlers[msg.type]?.(msg.payload);
}

View file

@ -0,0 +1,79 @@
import { get } from 'svelte/store';
import { lastRequestSheetPrice } from '../stores/recipeStore';
export interface PayloadFromSheet {
header: string[];
key: string;
payload?: GristCell[];
}
export interface GristCell {
cells?: {
coord: {
col: number;
row: number;
};
value: string;
}[];
row_index?: number;
}
const PRICE_SHEET_DEFINITION_BY_COUNTRY: any = {
ltu: {
expect_header: ['Name', 'Price in Euro'],
get_header_idx: (x: string[]) => {
let result = [];
for (const header of PRICE_SHEET_DEFINITION_BY_COUNTRY['ltu'].expect_header) {
let found = x.findIndex((y) => y == header);
result.push(found);
}
return result;
}
}
};
export function handleSheetResponseFromNoti(raw_payload: any, ref: string, country?: string) {
switch (ref) {
case 'price':
let price_contents: PayloadFromSheet[] = raw_payload.content;
console.log(`price content length: ${price_contents.length}`);
let header_idx = PRICE_SHEET_DEFINITION_BY_COUNTRY[country ?? 'unknown'].get_header_idx(
price_contents[0].header
);
console.log(`header idx: ${header_idx}`);
let lastRequestSheetInstance = get(lastRequestSheetPrice);
let products = lastRequestSheetInstance[country ?? 'unknown'];
for (let c of price_contents) {
let curr_product_code = c.key;
// price idx should be last
let price_idx = header_idx[header_idx.length - 1];
let price_rows = c.payload;
if (!price_rows) {
continue;
}
// get last because last row will always override
let expected_row = price_rows[price_rows.length - 1];
if (expected_row != undefined && expected_row.cells != undefined) {
let price_col = expected_row.cells[price_idx];
products[curr_product_code] = price_col;
console.log(`[handleSheetPrice][country] ${curr_product_code} --> ${price_col}`);
} else {
console.log(
`[handleSheetPrice][country] ${curr_product_code} not found cell, ${JSON.stringify(price_rows)}`
);
}
}
lastRequestSheetInstance[country ?? 'unknown'] = products;
lastRequestSheetPrice.set({
...lastRequestSheetInstance,
products
});
break;
default:
}
}

View file

@ -23,6 +23,17 @@ export const recipeOverviewData = writable<RecipeOverview[] | null>(null);
export const materialData = writable<Material | undefined>();
// price from recipe repo
export const priceRecipeData = writable<{ [key: string]: any }>({});
export const lastRequestSheetPrice = writable<{ [key: string]: any }>({});
// Streaming raw
export const streamingRawData = writable<{ [key: string]: any }>({});
export const streamingRawMeta = writable<{
id: string;
total_size: number;
chunk_size: number;
progress: number;
country?: string;
} | null>(null);
// machine recipe
export const recipeFromMachine = writable<any>(null);

View file

@ -10,6 +10,7 @@ import { permission } from './permissions';
let socket: WebSocket | null = null;
let reconnectTimeout: any;
let socketCheck: any;
const ENABLE_WS_DEBUG: boolean = false;
export const socketConnectionOfflineCount = writable<number>(0);
@ -95,7 +96,7 @@ export function connectToWebsocket(id_token?: string) {
console.log(socket);
// heartbeat 10s
setInterval(() => {
socketCheck = setInterval(() => {
if (get(socketAlreadySendHeartbeat) > 0) {
let heartbeat_may_offline_count = get(socketConnectionOfflineCount);
@ -143,6 +144,8 @@ export function connectToWebsocket(id_token?: string) {
socketStore.set(null);
socket = null;
clearInterval(socketCheck);
if (auth.currentUser && !socket) {
console.log('try reconnect websocket ...');
// retry again

View file

@ -36,9 +36,10 @@ export type OutMessage =
| {
type: 'save_recipe';
payload: {
user: string;
user_info: any;
country: string;
values: any;
plugins?: string;
};
}
| {