diff --git a/workflows/u/admin/alexa_speak.script.yaml b/workflows/u/admin/alexa_speak.script.yaml index 091b41b..fcfa041 100644 --- a/workflows/u/admin/alexa_speak.script.yaml +++ b/workflows/u/admin/alexa_speak.script.yaml @@ -6,10 +6,11 @@ schema: type: object properties: device: - type: string + type: object description: '' default: null - originalType: string + format: dynselect-device + originalType: DynSelect_device text: type: string description: '' diff --git a/workflows/u/admin/alexa_speak.ts b/workflows/u/admin/alexa_speak.ts index a25942c..cf6a39c 100644 --- a/workflows/u/admin/alexa_speak.ts +++ b/workflows/u/admin/alexa_speak.ts @@ -1,20 +1,69 @@ +/** + * alexa_speak.ts + * 指定した Echo デバイスにテキストを読み上げさせる Windmill スクリプト + * + * パラメータ: + * device - ドロップダウンから選択するデバイス(内部的にはシリアル番号) + * text - 読み上げるテキスト + */ + +const ALEXA_API_URL = "http://alexa_api:3500"; + +type DeviceOption = { value: string; label: string }; + +const FALLBACK_DEVICE_OPTIONS: DeviceOption[] = [ + { value: "G0922H085165007R", label: "プレハブ (G0922H085165007R)" }, + { value: "G8M2DB08522600RL", label: "リビングエコー1 (G8M2DB08522600RL)" }, + { value: "G8M2DB08522503WF", label: "リビングエコー2 (G8M2DB08522503WF)" }, + { value: "G0922H08525302K5", label: "オフィスの右エコー (G0922H08525302K5)" }, + { value: "G0922H08525302J9", label: "オフィスの左エコー (G0922H08525302J9)" }, + { value: "G8M2HN08534302XH", label: "寝室のエコー (G8M2HN08534302XH)" }, +]; + +// Windmill Dynamic Select: 引数名 `device` に対応する `DynSelect_device` と `device()` を定義 +export type DynSelect_device = string; + +export async function device(): Promise { + try { + const res = await fetch(`${ALEXA_API_URL}/devices`); + if (!res.ok) return FALLBACK_DEVICE_OPTIONS; + + const devices = (await res.json()) as Array<{ + name?: string; + serial?: string; + family?: string; + }>; + + const options = devices + .filter((d) => d.family === "ECHO" && d.serial) + .map((d) => ({ + value: d.serial as string, + label: `${d.name ?? d.serial} (${d.serial})`, + })) + .sort((a, b) => a.label.localeCompare(b.label, "ja")); + + return options.length > 0 ? options : FALLBACK_DEVICE_OPTIONS; + } catch { + return FALLBACK_DEVICE_OPTIONS; + } +} + export async function main( - device: string, + device: DynSelect_device, text: string, ): Promise<{ ok: boolean; device: string; text: string }> { - const ALEXA_API_URL = "http://alexa_api:3500"; - const res = await fetch(`${ALEXA_API_URL}/speak`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ device, text }), // ← SSMLなし、素のテキスト + body: JSON.stringify({ device, text }), }); if (!res.ok) { const body = await res.json().catch(() => ({})); - throw new Error(`alexa-api error ${res.status}: ${JSON.stringify(body)}`); + throw new Error( + `alexa-api error ${res.status}: ${JSON.stringify(body)}` + ); } return await res.json(); } - diff --git a/workflows/wmill-lock.yaml b/workflows/wmill-lock.yaml index 05d5407..3f5bb31 100644 --- a/workflows/wmill-lock.yaml +++ b/workflows/wmill-lock.yaml @@ -19,7 +19,7 @@ locks: f/weather/weather_sync__flow+気象データ取得・同期.py: 86c9953ec7346601eaa13c681e2db5c01c9a5b4b45a3c47e8667ad3c47557029 g/all/setup_app__app+__app_hash: d71add32e14e552d1a4c861c972a50d9598b07c0af201bbadec5b59bbd99d7e3 g/all/setup_app__app+change_account.deno.ts: 3c592cac27e9cdab0de6ae19270bcb08c7fa54355ad05253a12de2351894346b - u/admin/alexa_speak: 38de2eb567f82488e6f819c82dc7d83f41d83f015fac0c944d98dcc6b33a29a1 + u/admin/alexa_speak: e5bef63ab682e903715056cf24b4a94e87a14d4db60d8d29cd7c579359b56c72 u/admin/hub_sync: aaf9fd803fa229f3029d1bb02bbe3cc422fce680cad39c4eec8dd1da115de102 u/akiracraftwork/hourly_chime__flow+__flow_hash: 79974bee69ff196e45a08b74e9539d8a3b50885ef0abba6907a00530809984fa u/akiracraftwork/hourly_chime__flow+a.ts: b27320279be1d14184a210632e15d0e89d701243545d2d73cdd20e11dd413c53