diff --git a/app/api/voice/tools/smart-home/route.ts b/app/api/voice/tools/smart-home/route.ts new file mode 100644 index 0000000..6fa9809 --- /dev/null +++ b/app/api/voice/tools/smart-home/route.ts @@ -0,0 +1,56 @@ +export const dynamic = 'force-dynamic' +export const runtime = 'nodejs' + +import { NextResponse } from 'next/server' +import { isBearerAuthorized, unauthorized, internalHeaders } from '@/lib/voice-tools' + +export async function GET(req: Request) { + if (!isBearerAuthorized(req)) return unauthorized() + + const baseUrl = `http://localhost:${process.env.PORT || '3000'}` + const r = await fetch(`${baseUrl}/api/ha`, { + cache: 'no-store', + headers: internalHeaders(), + }).catch(() => null) + + if (!r || !r.ok) { + return NextResponse.json({ error: 'ha_unavailable' }, { status: 502 }) + } + + const data = await r.json() + const sensors = data.sensors || {} + const states = data.states || {} + const purifier = states['fan.air_purifier'] || {} + const purifierAttrs = purifier.attributes || {} + + return NextResponse.json({ + temperature: sensors.temperature ?? null, + humidity: sensors.humidity ?? null, + pm25: sensors.pm25 ?? null, + air_purifier: { + state: purifier.state || 'unknown', + preset_mode: purifierAttrs.preset_mode || null, + available_modes: purifierAttrs.preset_modes || ['Auto', 'Night', 'High'], + }, + }) +} + +export async function POST(req: Request) { + if (!isBearerAuthorized(req)) return unauthorized() + + const body = await req.json() + const baseUrl = `http://localhost:${process.env.PORT || '3000'}` + + const r = await fetch(`${baseUrl}/api/ha`, { + method: 'POST', + cache: 'no-store', + headers: { ...internalHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }).catch(() => null) + + if (!r || !r.ok) { + return NextResponse.json({ error: 'ha_unavailable' }, { status: 502 }) + } + + return NextResponse.json(await r.json()) +} diff --git a/lib/tools/_registry.ts b/lib/tools/_registry.ts index 2303725..26ca1d2 100644 --- a/lib/tools/_registry.ts +++ b/lib/tools/_registry.ts @@ -12,6 +12,7 @@ import { tool as transport } from './transport' import { tools as calendarTools } from './calendar' import { tools as timerTools } from './timers' import { tool as notes } from './notes' +import { tools as smartHomeTools } from './smart-home' const ALL_TOOLS: VoiceTool[] = [ weather, @@ -19,6 +20,7 @@ const ALL_TOOLS: VoiceTool[] = [ ...calendarTools, ...timerTools, notes, + ...smartHomeTools, ] export const TOOL_SCHEMAS = ALL_TOOLS.map((t) => t.schema) diff --git a/lib/tools/smart-home.ts b/lib/tools/smart-home.ts new file mode 100644 index 0000000..c2b609f --- /dev/null +++ b/lib/tools/smart-home.ts @@ -0,0 +1,80 @@ +import type { VoiceTool } from './_types' +import { tabletGet, tabletJson } from './_http' + +const getSmartHomeState: VoiceTool = { + schema: { + type: 'function', + function: { + name: 'get_smart_home_state', + description: + 'Получить текущее состояние умного дома: температура и влажность в квартире, ' + + 'уровень PM2.5 (качество воздуха), состояние очистителя воздуха. ' + + 'Используй для вопросов вроде «какая температура дома», «как качество воздуха», ' + + '«работает ли очиститель».', + parameters: { + type: 'object', + properties: {}, + }, + }, + }, + + async execute(_args) { + return tabletGet('/api/voice/tools/smart-home') + }, +} + +const controlAirPurifier: VoiceTool = { + schema: { + type: 'function', + function: { + name: 'control_air_purifier', + description: + 'Управление очистителем воздуха: включить, выключить или установить режим работы. ' + + 'Режимы: Auto (автоматический), Night (ночной/тихий), High (максимальный). ' + + 'Используй для команд вроде «включи очиститель», «поставь ночной режим», «выключи очиститель».', + parameters: { + type: 'object', + properties: { + action: { + type: 'string', + enum: ['turn_on', 'turn_off', 'set_mode'], + description: 'Действие: включить, выключить или установить режим', + }, + mode: { + type: 'string', + description: 'Режим работы при action=set_mode: Auto, Night или High', + }, + }, + required: ['action'], + }, + }, + }, + + async execute(args) { + const action = String(args?.action || '') + const mode = args?.mode as string | undefined + + let payload: Record + + if (action === 'turn_on') { + payload = { domain: 'fan', service: 'turn_on', entity_id: 'fan.air_purifier' } + } else if (action === 'turn_off') { + payload = { domain: 'fan', service: 'turn_off', entity_id: 'fan.air_purifier' } + } else if (action === 'set_mode') { + if (!mode) return { error: 'mode required for set_mode action' } + payload = { + domain: 'fan', + service: 'set_preset_mode', + entity_id: 'fan.air_purifier', + preset_mode: mode, + } + } else { + return { error: } + } + + await tabletJson('POST', '/api/voice/tools/smart-home', payload) + return { success: true, action, ...(mode ? { mode } : {}) } + }, +} + +export const tools: VoiceTool[] = [getSmartHomeState, controlAirPurifier]