All checks were successful
Deploy / deploy (push) Successful in 1m52s
- промпт: перечислены ВСЕ 15+ tools, разрешено уверенно управлять домом - route.ts: живой контекст (время + снимок устройств из HA) в системный промпт для LLM-пути (defensive, с таймаутом) - новые tools: control_climate (кондей/термостат), control_tv, set_scene (night/movie/morning/away), all_off, ha_service (универсальный HA с whitelist) - env: CLIMATE_ENTITY, TV_ENTITY (см. RUNBOOK)
101 lines
3.7 KiB
TypeScript
101 lines
3.7 KiB
TypeScript
import type { VoiceTool } from './_types'
|
||
import { tabletJson } from './_http'
|
||
|
||
/**
|
||
* Сцены (макросы над несколькими устройствами) и «выключить всё».
|
||
* Entity-конфиг переиспользует те же переменные, что control_light/tv/climate.
|
||
*/
|
||
|
||
function lightIds(): string[] {
|
||
const def = ['light.bedside', 'light.reading']
|
||
try {
|
||
const raw = process.env.LIGHT_ENTITIES_JSON
|
||
if (raw) {
|
||
const m = JSON.parse(raw) as Record<string, string>
|
||
const ids = Object.entries(m).filter(([k]) => k !== 'default').map(([, v]) => v)
|
||
return ids.length ? Array.from(new Set(ids)) : def
|
||
}
|
||
} catch {}
|
||
return def
|
||
}
|
||
const tvId = () => process.env.TV_ENTITY || 'media_player.tv'
|
||
const purifierId = 'fan.air_purifier'
|
||
|
||
async function ha(domain: string, service: string, entity_id: string, extra: Record<string, any> = {}) {
|
||
try {
|
||
await tabletJson('POST', '/api/voice/tools/smart-home', { domain, service, entity_id, ...extra })
|
||
} catch { /* устройство может быть mock — не валим сцену */ }
|
||
}
|
||
|
||
async function lightsOff() { await Promise.all(lightIds().map(id => ha('light', 'turn_off', id))) }
|
||
async function lightsOn(pct?: number) {
|
||
await Promise.all(lightIds().map(id => ha('light', 'turn_on', id, pct != null ? { brightness_pct: pct } : {})))
|
||
}
|
||
|
||
const setScene: VoiceTool = {
|
||
schema: {
|
||
type: 'function',
|
||
function: {
|
||
name: 'set_scene',
|
||
description:
|
||
'Активировать сцену — одной командой меняет несколько устройств. ' +
|
||
'night (ночь: свет выкл, очиститель в ночной режим), movie (кино: свет приглушить, ТВ вкл), ' +
|
||
'morning (утро: свет вкл, очиститель авто), away (ушёл: всё выключить). ' +
|
||
'Используй для «включи сцену ночь», «режим кино», «я ушёл».',
|
||
parameters: {
|
||
type: 'object',
|
||
properties: {
|
||
scene: { type: 'string', enum: ['night', 'movie', 'morning', 'away'], description: 'Какая сцена' },
|
||
},
|
||
required: ['scene'],
|
||
},
|
||
},
|
||
},
|
||
|
||
async execute(args) {
|
||
const scene = String(args?.scene || '')
|
||
switch (scene) {
|
||
case 'night':
|
||
await lightsOff()
|
||
await ha('fan', 'set_preset_mode', purifierId, { preset_mode: 'Night' })
|
||
break
|
||
case 'movie':
|
||
await lightsOn(20)
|
||
await ha('media_player', 'turn_on', tvId())
|
||
await ha('fan', 'set_preset_mode', purifierId, { preset_mode: 'Night' })
|
||
break
|
||
case 'morning':
|
||
await lightsOn()
|
||
await ha('fan', 'set_preset_mode', purifierId, { preset_mode: 'Auto' })
|
||
break
|
||
case 'away':
|
||
await lightsOff()
|
||
await ha('media_player', 'turn_off', tvId())
|
||
await ha('fan', 'turn_off', purifierId)
|
||
break
|
||
default:
|
||
return { error: `unknown scene: ${scene}` }
|
||
}
|
||
return { success: true, scene }
|
||
},
|
||
}
|
||
|
||
const allOff: VoiceTool = {
|
||
schema: {
|
||
type: 'function',
|
||
function: {
|
||
name: 'all_off',
|
||
description: 'Выключить всё: весь свет, телевизор, очиститель. Для «выключи всё», «отключи всё дома».',
|
||
parameters: { type: 'object', properties: {} },
|
||
},
|
||
},
|
||
async execute() {
|
||
await lightsOff()
|
||
await ha('media_player', 'turn_off', tvId())
|
||
await ha('fan', 'turn_off', purifierId)
|
||
return { success: true, turned_off: 'lights, tv, purifier' }
|
||
},
|
||
}
|
||
|
||
export const tools: VoiceTool[] = [setScene, allOff]
|