All checks were successful
Deploy / deploy (push) Successful in 3m43s
- lib/agent-local.ts + route.ts: команды-действия исполняет локальная intent-служба (под AGENT_URL, с fallback на LLM); инфо-запросы остаются у LLM - lib/tools/lights.ts: голосовое управление светом через HA (control_light) - app/globals.css + page.tsx: космическая indigo/aurora тема (токены, kiosk-safe) - agent/: sidecar-служба (Dockerfile, predict.py), веса монтируются томом - RUNBOOK.md: вывод в продакшен на мини-ПК
103 lines
4.3 KiB
TypeScript
103 lines
4.3 KiB
TypeScript
import type { VoiceTool } from './_types'
|
||
import { tabletJson } from './_http'
|
||
|
||
/**
|
||
* Управление светом (лампами) через Home Assistant.
|
||
*
|
||
* Голосовой агент (tablet-agent) распознаёт интенты light_on/light_off и слот
|
||
* device («свет у кровати», «лампу для чтения»). Здесь имя лампы сопоставляется
|
||
* с entity_id Home Assistant.
|
||
*
|
||
* Реальные entity_id задаются переменной окружения LIGHT_ENTITIES_JSON (в
|
||
* tablet.env), например:
|
||
* LIGHT_ENTITIES_JSON={"bedside":"light.yeelight_bedside","reading":"light.reading_lamp","default":"light.bedside"}
|
||
* Ключи — короткие имена (см. DEVICE_ALIASES ниже). Пока переменная не задана,
|
||
* используются заглушки light.* — команды проходят как mock (HA вернёт success).
|
||
*/
|
||
|
||
// Короткое имя лампы → entity_id. Переопределяется LIGHT_ENTITIES_JSON.
|
||
const DEFAULT_ENTITIES: Record<string, string> = {
|
||
bedside: 'light.bedside', // «Свет у кровати»
|
||
reading: 'light.reading', // «Лампа для чтения»
|
||
default: 'light.bedside', // если лампа не названа — эта
|
||
}
|
||
|
||
function lightEntities(): Record<string, string> {
|
||
const raw = process.env.LIGHT_ENTITIES_JSON
|
||
if (raw) {
|
||
try {
|
||
return { ...DEFAULT_ENTITIES, ...JSON.parse(raw) }
|
||
} catch {
|
||
// некорректный JSON — падаем на дефолты
|
||
}
|
||
}
|
||
return DEFAULT_ENTITIES
|
||
}
|
||
|
||
// Разговорные имена ламп → короткий ключ. Слот device приходит куском текста.
|
||
const DEVICE_ALIASES: Array<[RegExp, string]> = [
|
||
[/крова|спальн|ночник|прикроват/i, 'bedside'],
|
||
[/чтени|настольн|рабоч/i, 'reading'],
|
||
]
|
||
|
||
function resolveEntity(device?: string): string {
|
||
const map = lightEntities()
|
||
if (device) {
|
||
for (const [re, key] of DEVICE_ALIASES) {
|
||
if (re.test(device) && map[key]) return map[key]
|
||
}
|
||
}
|
||
return map.default || 'light.bedside'
|
||
}
|
||
|
||
const controlLight: VoiceTool = {
|
||
schema: {
|
||
type: 'function',
|
||
function: {
|
||
name: 'control_light',
|
||
description:
|
||
'Управление светом (лампами): включить или выключить. ' +
|
||
'Лампы: «свет у кровати», «лампа для чтения». Если лампа не названа — ' +
|
||
'используется лампа по умолчанию. Используй для команд вроде «включи свет», ' +
|
||
'«выключи лампу для чтения», «зажги свет у кровати».',
|
||
parameters: {
|
||
type: 'object',
|
||
properties: {
|
||
action: {
|
||
type: 'string',
|
||
enum: ['turn_on', 'turn_off'],
|
||
description: 'Включить или выключить',
|
||
},
|
||
device: {
|
||
type: 'string',
|
||
description:
|
||
'Какая лампа: «свет у кровати», «лампа для чтения». ' +
|
||
'Опционально — без неё берётся лампа по умолчанию.',
|
||
},
|
||
brightness_pct: {
|
||
type: 'number',
|
||
description: 'Яркость 0-100 (опционально, только при turn_on)',
|
||
},
|
||
},
|
||
required: ['action'],
|
||
},
|
||
},
|
||
},
|
||
|
||
async execute(args) {
|
||
const action = String(args?.action || '')
|
||
if (action !== 'turn_on' && action !== 'turn_off') {
|
||
return { error: `unknown action: ${action}` }
|
||
}
|
||
const entity_id = resolveEntity(args?.device as string | undefined)
|
||
const payload: Record<string, any> = { domain: 'light', service: action, entity_id }
|
||
if (action === 'turn_on' && typeof args?.brightness_pct === 'number') {
|
||
payload.brightness_pct = Math.max(0, Math.min(100, Math.round(args.brightness_pct)))
|
||
}
|
||
await tabletJson('POST', '/api/voice/tools/smart-home', payload)
|
||
return { success: true, action, entity_id, ...(args?.device ? { device: args.device } : {}) }
|
||
},
|
||
}
|
||
|
||
export const tools: VoiceTool[] = [controlLight]
|