feat(call): enhance CallPage component with improved voicemail settings, greeting upload functionality, and UI refinements for better user experience

This commit is contained in:
2026-01-01 17:35:07 -06:00
parent 40147dea9e
commit cd17fb22bf

View File

@@ -1,6 +1,7 @@
<template>
<div class="flex flex-col w-full h-full bg-gray-100 dark:bg-zinc-950" :class="{ dark: config?.theme === 'dark' }">
<div class="mx-auto w-full max-w-xl p-4 flex-1 flex flex-col">
<div class="w-full h-full overflow-y-auto">
<div class="mx-auto w-full max-w-xl p-4 flex-1 flex flex-col min-h-full">
<!-- Tabs -->
<div class="flex border-b border-gray-200 dark:border-zinc-800 mb-6 shrink-0">
<button
@@ -216,8 +217,12 @@
class="mt-4 p-4 text-left bg-gray-200 dark:bg-zinc-800 rounded-lg text-sm text-gray-600 dark:text-zinc-300"
>
<div class="grid grid-cols-2 gap-2">
<div>TX: {{ activeCall.tx_packets }} ({{ formatBytes(activeCall.tx_bytes) }})</div>
<div>RX: {{ activeCall.rx_packets }} ({{ formatBytes(activeCall.rx_bytes) }})</div>
<div>
TX: {{ activeCall.tx_packets }} ({{ formatBytes(activeCall.tx_bytes) }})
</div>
<div>
RX: {{ activeCall.rx_packets }} ({{ formatBytes(activeCall.rx_bytes) }})
</div>
</div>
</div>
</div>
@@ -279,7 +284,9 @@
<p class="text-sm font-semibold text-gray-900 dark:text-white truncate">
{{ entry.remote_identity_name || "Unknown" }}
</p>
<span class="text-[10px] text-gray-500 dark:text-zinc-500 font-mono ml-2">
<span
class="text-[10px] text-gray-500 dark:text-zinc-500 font-mono ml-2"
>
{{ entry.timestamp ? formatDateTime(entry.timestamp * 1000) : "" }}
</span>
</div>
@@ -450,10 +457,12 @@
<div class="text-xs text-amber-800 dark:text-amber-200">
<p class="font-bold mb-1">Dependencies Missing</p>
<p v-if="!voicemailStatus.has_espeak">
Voicemail requires `espeak-ng` to generate greetings. Please install it on your system.
Voicemail requires `espeak-ng` to generate greetings. Please install it on your
system.
</p>
<p v-if="!voicemailStatus.has_ffmpeg" :class="{ 'mt-1': !voicemailStatus.has_espeak }">
Voicemail requires `ffmpeg` to process audio files. Please install it on your system.
Voicemail requires `ffmpeg` to process audio files. Please install it on your
system.
</p>
</div>
</div>
@@ -462,7 +471,9 @@
<!-- Enabled Toggle -->
<div class="flex items-center justify-between">
<div>
<div class="text-sm font-semibold text-gray-900 dark:text-white">Enable Voicemail</div>
<div class="text-sm font-semibold text-gray-900 dark:text-white">
Enable Voicemail
</div>
<div class="text-xs text-gray-500 dark:text-zinc-400">
Accept calls automatically and record messages
</div>
@@ -485,7 +496,8 @@
<!-- Greeting Text -->
<div class="space-y-2">
<label class="text-xs font-bold text-gray-500 dark:text-zinc-400 uppercase tracking-tighter"
<label
class="text-xs font-bold text-gray-500 dark:text-zinc-400 uppercase tracking-tighter"
>Greeting Message</label
>
<textarea
@@ -498,6 +510,7 @@
<p class="text-[10px] text-gray-500 dark:text-zinc-500">
This text will be converted to speech using eSpeak NG.
</p>
<div class="flex gap-2">
<button
:disabled="
!voicemailStatus.has_espeak ||
@@ -512,18 +525,58 @@
>
{{ isGeneratingGreeting ? "Generating..." : "Save & Generate" }}
</button>
</div>
</div>
</div>
<!-- Custom Greeting Upload -->
<div class="space-y-2">
<label
class="text-xs font-bold text-gray-500 dark:text-zinc-400 uppercase tracking-tighter"
>Custom Audio Greeting</label
>
<div class="flex items-center gap-3">
<input
ref="greetingUpload"
type="file"
accept="audio/*"
class="hidden"
@change="uploadGreeting"
/>
<button
v-if="voicemailStatus.has_espeak && voicemailStatus.has_ffmpeg"
class="text-[10px] bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 px-3 py-1 rounded-full font-bold hover:bg-blue-200 dark:hover:bg-blue-900/50 transition-colors flex items-center gap-1"
:disabled="!voicemailStatus.has_ffmpeg || isUploadingGreeting"
class="text-xs bg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-zinc-300 px-4 py-2 rounded-lg font-bold hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors disabled:opacity-50 flex items-center gap-2"
@click="$refs.greetingUpload.click()"
>
<MaterialDesignIcon icon-name="upload" class="size-4" />
{{ isUploadingGreeting ? "Uploading..." : "Upload Audio File" }}
</button>
<div v-if="voicemailStatus.has_greeting" class="flex items-center gap-2">
<button
class="text-xs bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400 px-4 py-2 rounded-lg font-bold hover:bg-red-200 dark:hover:bg-red-900/50 transition-colors flex items-center gap-2"
@click="deleteGreeting"
>
<MaterialDesignIcon icon-name="delete" class="size-4" />
Remove Greeting
</button>
<button
class="text-xs bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 px-4 py-2 rounded-lg font-bold hover:bg-blue-200 dark:hover:bg-blue-900/50 transition-colors flex items-center gap-2"
@click="playGreeting"
>
<MaterialDesignIcon
:icon-name="isPlayingGreeting ? 'stop' : 'play'"
class="size-3"
class="size-4"
/>
{{ isPlayingGreeting ? "Stop Preview" : "Preview Greeting" }}
{{ isPlayingGreeting ? "Stop Preview" : "Preview" }}
</button>
</div>
<div v-else class="text-[10px] text-gray-500 dark:text-zinc-500 italic">
No custom greeting uploaded (default text will be used)
</div>
</div>
<p class="text-[10px] text-gray-500 dark:text-zinc-500">
Supports MP3, OGG, WAV, M4A, FLAC. Will be converted to Opus.
</p>
</div>
<!-- Delays -->
@@ -571,6 +624,7 @@
</div>
</div>
</div>
</div>
</template>
<script>
@@ -600,8 +654,10 @@ export default {
has_espeak: false,
has_ffmpeg: false,
is_recording: false,
has_greeting: false,
},
isGeneratingGreeting: false,
isUploadingGreeting: false,
playingVoicemailId: null,
audioPlayer: null,
isPlayingGreeting: false,
@@ -752,12 +808,47 @@ export default {
try {
await window.axios.post("/api/v1/telephone/voicemail/generate-greeting");
ToastUtils.success("Greeting generated successfully");
await this.getVoicemailStatus();
} catch (e) {
ToastUtils.error(e.response?.data?.message || "Failed to generate greeting");
} finally {
this.isGeneratingGreeting = false;
}
},
async uploadGreeting(event) {
const file = event.target.files[0];
if (!file) return;
this.isUploadingGreeting = true;
const formData = new FormData();
formData.append("file", file);
try {
await window.axios.post("/api/v1/telephone/voicemail/greeting/upload", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
ToastUtils.success("Greeting uploaded successfully");
await this.getVoicemailStatus();
} catch (e) {
ToastUtils.error(e.response?.data?.message || "Failed to upload greeting");
} finally {
this.isUploadingGreeting = false;
event.target.value = "";
}
},
async deleteGreeting() {
if (!confirm("Are you sure you want to delete your custom greeting?")) return;
try {
await window.axios.delete("/api/v1/telephone/voicemail/greeting");
ToastUtils.success("Greeting deleted");
await this.getVoicemailStatus();
} catch (e) {
ToastUtils.error("Failed to delete greeting");
}
},
async playVoicemail(voicemail) {
if (this.playingVoicemailId === voicemail.id) {
this.audioPlayer.pause();