130 lines
4.3 KiB
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),
|
|
]);
|
|
}
|
|
}
|