fox/app/Livewire/FoxHud.php

130 lines
4.3 KiB
PHP

<?php
namespace App\Livewire;
use App\Events\FoxAction;
use App\Events\FoxMessage;
use App\Jobs\CreateTemplateJob;
use App\Jobs\HandleUserMessage;
use App\Models\Message;
use App\Services\FoxAgent;
use App\Services\IntentDetector;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Fox HUD')]
#[Layout('layouts.hud')]
class FoxHud extends Component
{
public string $input = '';
public string $status = 'ready'; // ready | listening | thinking | speaking
/** Vom HUD-JS bei jeder Aktion gesetzt — Kontext für Fox: was ist gerade offen */
public array $hudState = [];
public function send(IntentDetector $intents): void
{
$text = trim($this->input);
if ($text === '') {
return;
}
$this->input = '';
$this->status = 'thinking';
// 1. Intent prüfen - wenn Map-Action erkennbar, sofort broadcasten
$intent = $intents->detect($text);
if ($intent) {
broadcast(new FoxAction($intent));
}
$userMessage = app(FoxAgent::class)->recordUserMessage($text, [
'hud_state' => $this->hudState,
]);
// 2. Volltreffer (map / poi via IntentDetector) → kein LLM-Roundtrip nötig.
// Spart 5-30s Wartezeit; das HUD hat den Spruch schon gesprochen.
if ($intent && ($intent['action'] ?? '') === 'create_template') {
$stub = Message::create([
'role' => Message::ROLE_ASSISTANT,
'content' => $intent['text'],
'type' => Message::TYPE_TEXT,
'metadata' => ['action' => 'create_template', 'fast_path' => true],
]);
broadcast(new FoxMessage($stub));
CreateTemplateJob::dispatch($intent['description'], $userMessage);
$this->status = 'ready';
return;
}
if ($intent && in_array($intent['action'] ?? '', ['map', 'poi', 'transit', 'route', 'route_info', 'close_map', 'place_info', 'place_more', 'system_status', 'reminder', 'map_dynamic', 'desk_card', 'desk_down', 'brain'], true)) {
$stub = Message::create([
'role' => Message::ROLE_ASSISTANT,
'content' => $intent['text'] ?? ($intent['label'] ?? ''),
'type' => Message::TYPE_TEXT,
'metadata' => [
'action' => $intent['action'],
'city' => $intent['city'] ?? null,
'category' => $intent['category'] ?? null,
'cuisine' => $intent['cuisine'] ?? null,
'audio_url' => null,
'fast_path' => true,
],
]);
broadcast(new FoxMessage($stub));
$this->status = 'ready';
return;
}
// 3. Sonst: kurzer "Einen Moment..."-Spruch + LLM-Job dispatchen.
$lower = mb_strtolower($text);
$isQuestion = str_contains($lower, 'empfehl')
|| str_contains($lower, 'vorschlag')
|| str_contains($lower, 'kannst du')
|| str_contains($lower, 'welche')
|| str_contains($lower, 'was ist')
|| str_contains($lower, 'wie ')
|| str_contains($text, '?');
if ($isQuestion) {
$phrases = [
'Einen Moment, ich schau mir das an.',
'Lass mich kurz nachdenken.',
'Klar, gib mir eine Sekunde.',
'Bin gleich für dich da.',
'Ich kümmer mich drum.',
'Schau ich mir an.',
'Sekunde, melde mich gleich.',
'Geht klar, einen kurzen Moment.',
];
broadcast(new FoxAction([
'action' => 'announce',
'text' => $phrases[array_rand($phrases)],
]));
}
HandleUserMessage::dispatch($userMessage);
}
#[On('echo:fox,.message.created')]
public function onMessage(): void
{
$this->status = 'ready';
}
public function render()
{
$homeKey = mb_strtolower(config('services.weather.city', 'wien'));
$cities = config('cities', []);
return view('livewire.fox-hud', [
'cities' => $cities,
'homeCity' => $cities[$homeKey] ?? ($cities['wien'] ?? null),
]);
}
}