fox/storage/framework/views/80b6b6e2aade9d6018b1a52c346...

243 lines
12 KiB
PHP
Executable File

<div
x-data="foxChat()"
x-init="init()"
class="grid grid-rows-[1fr_auto] gap-3 h-full p-3"
>
<div class="glass overflow-hidden flex flex-col">
<div class="px-5 py-3 border-b border-white/10 flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-emerald-400 shadow-[0_0_8px_rgb(52_211_153)]"></span>
<span class="text-sm text-white/70">Fox ist online</span>
</div>
<div class="text-xs text-white/40">
<?php echo e($messages->count()); ?> Nachrichten
</div>
</div>
<div
x-ref="feed"
class="flex-1 overflow-y-auto px-5 py-6 space-y-4"
>
<?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if BLOCK]><![endif]--><?php endif; ?><?php $__empty_1 = true; $__currentLoopData = $messages; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $msg): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if BLOCK]><![endif]--><?php endif; ?><?php if($msg->role === 'user'): ?>
<div class="flex justify-end">
<div class="max-w-[78%] bg-white/8 border border-white/10 rounded-2xl rounded-tr-sm px-4 py-3 text-white">
<div class="whitespace-pre-wrap break-words"><?php echo e($msg->content); ?></div>
<div class="text-[10px] text-white/40 mt-1.5 text-right">
<?php echo e($msg->created_at?->format('H:i')); ?>
</div>
</div>
</div>
<?php elseif($msg->role === 'tool'): ?>
<div class="flex justify-center">
<div class="text-xs text-white/50 italic px-3 py-1.5 rounded-full bg-white/5 border border-white/10">
⚙ <?php echo e($msg->content); ?>
<?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if BLOCK]><![endif]--><?php endif; ?><?php if(data_get($msg->metadata, 'url')): ?>
<a href="<?php echo e(data_get($msg->metadata, 'url')); ?>" target="_blank" class="text-primary underline ml-1">öffnen</a>
<?php endif; ?><?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if ENDBLOCK]><![endif]--><?php endif; ?>
</div>
</div>
<?php else: ?>
<div class="flex justify-start">
<div class="flex gap-3 max-w-[78%]">
<div class="w-8 h-8 rounded-lg bg-primary/20 border border-primary/40 flex-shrink-0 flex items-center justify-center text-primary text-sm font-bold">F</div>
<div>
<?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if BLOCK]><![endif]--><?php endif; ?><?php if($msg->type === 'proactive'): ?>
<div class="badge-proactive mb-1.5">
<svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-6m0 0V6m0 6h6m-6 0H6"/></svg>
Fox meldet sich
</div>
<?php endif; ?><?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if ENDBLOCK]><![endif]--><?php endif; ?>
<div class="bg-primary/10 border border-primary/30 rounded-2xl rounded-tl-sm px-4 py-3">
<div class="whitespace-pre-wrap break-words text-white"><?php echo e($msg->content); ?></div>
<div class="text-[10px] text-white/40 mt-1.5">
<?php echo e($msg->created_at?->format('H:i')); ?>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?><?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if ENDBLOCK]><![endif]--><?php endif; ?>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<div class="text-center text-white/40 py-16">
<div class="text-6xl mb-3">🦊</div>
<div class="text-sm">Sag etwas, oder warte — Fox meldet sich von selbst.</div>
</div>
<?php endif; ?><?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if ENDBLOCK]><![endif]--><?php endif; ?>
<?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if BLOCK]><![endif]--><?php endif; ?><?php if($foxIsThinking): ?>
<div class="flex justify-start">
<div class="flex gap-3">
<div class="w-8 h-8 rounded-lg bg-primary/20 border border-primary/40 flex-shrink-0 flex items-center justify-center text-primary text-sm font-bold">F</div>
<div class="bg-primary/10 border border-primary/30 rounded-2xl rounded-tl-sm px-4 py-3 flex items-center gap-1">
<span class="typing-dot"></span>
<span class="typing-dot"></span>
<span class="typing-dot"></span>
</div>
</div>
</div>
<?php endif; ?><?php if(\Livewire\Mechanisms\ExtendBlade\ExtendBlade::isRenderingLivewireComponent()): ?><!--[if ENDBLOCK]><![endif]--><?php endif; ?>
</div>
</div>
<form wire:submit="send" class="glass p-3 flex items-end gap-2">
<button
type="button"
x-on:mousedown="startRecording()"
x-on:mouseup="stopRecording()"
x-on:touchstart.prevent="startRecording()"
x-on:touchend.prevent="stopRecording()"
class="w-12 h-12 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center hover:bg-primary/20 hover:border-primary/40 transition flex-shrink-0"
x-bind:class="recording && 'ptt-ring bg-primary/30 border-primary'"
title="Push-to-Talk: gedrückt halten oder Strg+Leertaste"
>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z"/>
</svg>
</button>
<textarea
wire:model="draft"
x-ref="input"
x-on:keydown.enter.prevent="if (!$event.shiftKey) $wire.send()"
rows="1"
placeholder="Schreib was, oder halte Strg+Leertaste..."
class="input resize-none max-h-32"
></textarea>
<button type="submit" class="btn-primary h-12 w-12 p-0">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 12L21 4.5l-7.5 16.5-2.25-7.5L3.75 12z"/>
</svg>
</button>
</form>
</div>
<?php
$__assetKey = '1118371926-0';
ob_start();
?>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('foxChat', () => ({
recording: false,
mediaRecorder: null,
chunks: [],
audioQueue: [],
audioPlaying: false,
init() {
this.scrollToBottom();
this.$wire.on('message-sent', () => this.scrollToBottom());
// Auto-scroll bei neuen Broadcasts. Audio wird in bootstrap.js global gehandelt
// damit der Listener nicht bei jedem Re-Render verdoppelt wird.
window.Echo?.channel('fox').listen('.message.created', () => {
this.$nextTick(() => this.scrollToBottom());
});
// Globaler Push-to-Talk Shortcut: Strg+Leertaste
window.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.code === 'Space' && !this.recording) {
e.preventDefault();
this.startRecording();
}
});
window.addEventListener('keyup', (e) => {
if (e.code === 'Space' && this.recording) {
e.preventDefault();
this.stopRecording();
}
});
},
playNextAudio() {
if (this.audioPlaying || this.audioQueue.length === 0) return;
this.audioPlaying = true;
const url = this.audioQueue.shift();
const audio = new Audio(url);
audio.onended = () => {
this.audioPlaying = false;
this.playNextAudio();
};
audio.play().catch(() => { this.audioPlaying = false; });
},
scrollToBottom() {
this.$nextTick(() => {
const feed = this.$refs.feed;
if (feed) feed.scrollTop = feed.scrollHeight;
});
},
async startRecording() {
if (this.recording) return;
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.chunks = [];
this.mediaRecorder = new MediaRecorder(stream);
this.mediaRecorder.ondataavailable = (e) => this.chunks.push(e.data);
this.mediaRecorder.onstop = () => this.uploadRecording();
this.mediaRecorder.start();
this.recording = true;
} catch (err) {
console.error('Mikrofon nicht verfügbar:', err);
}
},
stopRecording() {
if (!this.recording || !this.mediaRecorder) return;
this.mediaRecorder.stop();
this.mediaRecorder.stream.getTracks().forEach(t => t.stop());
this.recording = false;
},
async uploadRecording() {
const blob = new Blob(this.chunks, { type: 'audio/webm' });
const fd = new FormData();
fd.append('audio', blob, 'fox-input.webm');
const res = await fetch('/fox/voice', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content },
body: fd,
});
const data = await res.json();
if (data.text) {
this.$wire.dispatch('voice-transcribed', { text: data.text });
}
if (data.audio_url) {
new Audio(data.audio_url).play().catch(() => {});
}
},
audioFromEvent(payload) {
const url = payload?.metadata?.audio_url;
if (url) new Audio(url).play().catch(() => {});
},
}));
});
</script>
<?php
$__output = ob_get_clean();
// If the asset has already been loaded anywhere during this request, skip it...
if (in_array($__assetKey, \Livewire\Features\SupportScriptsAndAssets\SupportScriptsAndAssets::$alreadyRunAssetKeys)) {
// Skip it...
} else {
\Livewire\Features\SupportScriptsAndAssets\SupportScriptsAndAssets::$alreadyRunAssetKeys[] = $__assetKey;
// Check if we're in a Livewire component or not and store the asset accordingly...
if (isset($this)) {
\Livewire\store($this)->push('assets', $__output, $__assetKey);
} else {
\Livewire\Features\SupportScriptsAndAssets\SupportScriptsAndAssets::$nonLivewireAssets[$__assetKey] = $__output;
}
}
?>
<?php /**PATH /var/www/resources/views/livewire/chat.blade.php ENDPATH**/ ?>