Fox: /fox/message returns audio_url
Route now waits synchronously for FoxAgent response and returns reply + audio_url. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
e3ba405548
commit
f0a1ab0911
|
|
@ -329,6 +329,13 @@ class FoxAgent
|
|||
calculate: Rechnen, Umrechnen, Kalkulieren
|
||||
remind: Erinnerung setzen
|
||||
brain: Fox Brain / Gedächtnis-Visualisierung öffnen
|
||||
close_map / close_poi / close_transit / close_status: Aktives Panel schließen
|
||||
spotify_play / spotify_pause / spotify_next / spotify_prev: Spotify steuern
|
||||
mail_open: Mail App öffnen
|
||||
open_app: Mac App öffnen → app=App-Name
|
||||
screenshot: Screenshot machen
|
||||
save_desktop: Desktop speichern
|
||||
volume_up / volume_down: Lautstärke anpassen
|
||||
response: Normale Antwort wenn keine andere Action passt
|
||||
|
||||
BEISPIELE:
|
||||
|
|
@ -347,6 +354,18 @@ class FoxAgent
|
|||
"was weißt du" → action=brain
|
||||
"dein Gedächtnis" → action=brain
|
||||
"memory" → action=brain
|
||||
"spiel musik" → action=spotify_play
|
||||
"nächster song" → action=spotify_next
|
||||
"vorheriger song" → action=spotify_prev
|
||||
"musik pause" → action=spotify_pause
|
||||
"öffne mail" → action=mail_open
|
||||
"öffne finder" → action=open_app, app="Finder"
|
||||
"öffne safari" → action=open_app, app="Safari"
|
||||
"mach einen screenshot" → action=screenshot
|
||||
"lauter" → action=volume_up
|
||||
"leiser" → action=volume_down
|
||||
"schließe die karte" → action=close_map
|
||||
"schließe die restaurants" → action=close_poi
|
||||
|
||||
BEI action=code:
|
||||
- code: VOLLSTÄNDIGER ausführbarer Code, nicht nur Erklärung
|
||||
|
|
@ -389,6 +408,9 @@ class FoxAgent
|
|||
'map', 'poi', 'weather', 'transit', 'route', 'recommend', 'response',
|
||||
'close_map', 'close_poi', 'close_transit', 'close_status',
|
||||
'web_search', 'open_url', 'code', 'calculate', 'remind', 'brain',
|
||||
'spotify_play', 'spotify_pause', 'spotify_next', 'spotify_prev',
|
||||
'mail_open', 'open_app', 'screenshot', 'save_desktop',
|
||||
'volume_up', 'volume_down',
|
||||
]),
|
||||
new StringSchema('city', 'Kleingeschriebener Stadtname (leer wenn nicht relevant)'),
|
||||
new StringSchema('category', 'POI-Kategorie bei action=poi: restaurant, parking, pharmacy, hotel, doctor, bank, supermarket'),
|
||||
|
|
@ -401,8 +423,9 @@ class FoxAgent
|
|||
new StringSchema('language', 'Programmiersprache bei action=code (php, js, python, etc.)'),
|
||||
new StringSchema('result', 'Berechnetes Ergebnis bei action=calculate'),
|
||||
new StringSchema('description', 'Beschreibung was der Code macht, bei action=code'),
|
||||
new StringSchema('app', 'App Name bei action=open_app'),
|
||||
],
|
||||
requiredFields: ['action', 'text', 'city', 'category', 'place', 'to', 'url', 'query', 'code', 'language', 'result', 'description'],
|
||||
requiredFields: ['action', 'text', 'city', 'category', 'place', 'to', 'url', 'query', 'code', 'language', 'result', 'description', 'app'],
|
||||
);
|
||||
|
||||
// Provider-Chain: Ollama (Mac, qwen2.5:32b) → OpenAI (gpt-4o-mini) → Heuristik.
|
||||
|
|
@ -657,6 +680,19 @@ class FoxAgent
|
|||
]));
|
||||
}
|
||||
|
||||
// Desktop-Actions (Spotify, Mail, App, System) — für Electron Mac Agent
|
||||
if (in_array($action, [
|
||||
'spotify_play', 'spotify_pause', 'spotify_next', 'spotify_prev',
|
||||
'mail_open', 'open_app', 'screenshot', 'save_desktop',
|
||||
'volume_up', 'volume_down',
|
||||
], true)) {
|
||||
broadcast(new FoxAction([
|
||||
'action' => $action,
|
||||
'text' => $reply['text'] ?? '',
|
||||
'app' => $reply['app'] ?? null,
|
||||
]));
|
||||
}
|
||||
|
||||
$message = Message::create([
|
||||
'role' => Message::ROLE_ASSISTANT,
|
||||
'content' => $reply['text'] ?: '...',
|
||||
|
|
|
|||
|
|
@ -500,6 +500,24 @@ document.addEventListener('alpine:init', () => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Spotify
|
||||
if (payload.action === 'spotify_play') { window.foxBridge?.macCommand('spotify_play'); if (payload.text) this.notify(payload.text); return; }
|
||||
if (payload.action === 'spotify_pause') { window.foxBridge?.macCommand('spotify_pause'); if (payload.text) this.notify(payload.text); return; }
|
||||
if (payload.action === 'spotify_next') { window.foxBridge?.macCommand('spotify_next'); if (payload.text) this.notify(payload.text); return; }
|
||||
if (payload.action === 'spotify_prev') { window.foxBridge?.macCommand('spotify_prev'); if (payload.text) this.notify(payload.text); return; }
|
||||
|
||||
// Desktop Actions
|
||||
if (payload.action === 'mail_open') { window.foxBridge?.macCommand('mail_open'); if (payload.text) this.notify(payload.text); return; }
|
||||
if (payload.action === 'volume_up') { window.foxBridge?.macCommand('volume_up'); if (payload.text) this.notify(payload.text); return; }
|
||||
if (payload.action === 'volume_down') { window.foxBridge?.macCommand('volume_down'); if (payload.text) this.notify(payload.text); return; }
|
||||
if (payload.action === 'screenshot') { window.foxBridge?.macCommand('screenshot'); if (payload.text) this.notify(payload.text); return; }
|
||||
if (payload.action === 'save_desktop') { window.foxBridge?.macCommand('save_desktop'); if (payload.text) this.notify(payload.text); return; }
|
||||
if (payload.action === 'open_app' && payload.app) {
|
||||
window.foxBridge?.macCommand('app:' + payload.app);
|
||||
if (payload.text) this.notify(payload.text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (['map','food','weather','transit','poi'].includes(payload.action)) {
|
||||
let city = payload.data;
|
||||
if (!city && payload.city) {
|
||||
|
|
@ -562,6 +580,8 @@ document.addEventListener('alpine:init', () => {
|
|||
ml.style.visibility = 'visible';
|
||||
ml.classList.add('show');
|
||||
}
|
||||
document.getElementById('fox-center')?.classList.add('hide');
|
||||
document.getElementById('fox-mini')?.classList.add('show');
|
||||
|
||||
// 200ms warten damit der Browser die Layout-Änderungen committed hat,
|
||||
// dann erst resize() (sonst rechnet MapLibre auf alter Container-Größe).
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
<div class="grid-layer"></div>
|
||||
<div class="scanlines"></div>
|
||||
|
||||
<div id="map-layer" class="map-wrap" wire:ignore><div id="map"></div></div>
|
||||
<div id="map-layer" class="map-wrap" wire:ignore
|
||||
x-bind:style="mapVisible ? 'opacity:1;pointer-events:all;display:block' : 'opacity:0;pointer-events:none'"><div id="map" wire:ignore></div></div>
|
||||
|
||||
{{-- Fox Brain Layer --}}
|
||||
<div id="brain-hover-label" class="brain-hover-label"></div>
|
||||
|
|
@ -61,7 +62,7 @@
|
|||
<div class="eg eg-r" :class="(edgeGlow || isSpeaking) && 'on'"></div>
|
||||
|
||||
<div id="fox-center" class="fox-center" wire:ignore.self
|
||||
:class="{'is-thinking': $wire.status === 'thinking'}"
|
||||
:class="{'is-thinking': $wire.status === 'thinking', 'hide': mapVisible}"
|
||||
x-on:click="mapVisible && closeMap()">
|
||||
<div class="fox-jarvis"
|
||||
:class="{ 'is-speaking': isSpeaking }">
|
||||
|
|
@ -131,7 +132,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="fox-mini" x-on:click="closeMap()" wire:ignore.self>
|
||||
<button type="button" class="fox-mini" x-on:click="closeMap()" wire:ignore.self
|
||||
:class="{ 'show': mapVisible }">
|
||||
<div class="mini-rings">
|
||||
<div class="mini-ring mr1"></div>
|
||||
<div class="mini-ring mr2"></div>
|
||||
|
|
|
|||
|
|
@ -35,3 +35,22 @@ Route::prefix('fox')->name('fox.')->group(function () {
|
|||
Route::get('/brain', [FoxBrainController::class, 'index'])->name('brain');
|
||||
Route::get('/brain/graph', [FoxBrainController::class, 'graph'])->name('brain.graph');
|
||||
});
|
||||
|
||||
Route::post('/fox/message', function (\Illuminate\Http\Request $request) {
|
||||
$text = $request->input('text', '');
|
||||
if (empty(trim($text))) {
|
||||
return response()->json(['error' => 'Kein Text'], 400);
|
||||
}
|
||||
|
||||
$userMessage = app(\App\Services\FoxAgent::class)
|
||||
->recordUserMessage($text, ['source' => 'electron']);
|
||||
|
||||
$assistantMessage = app(\App\Services\FoxAgent::class)
|
||||
->processUserMessage($userMessage);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'ok',
|
||||
'reply' => $assistantMessage->content,
|
||||
'audio_url' => $assistantMessage->metadata['audio_url'] ?? null,
|
||||
]);
|
||||
})->middleware('web');
|
||||
|
|
|
|||
Loading…
Reference in New Issue