Skip to main content
Glama

ElevenLabs MCP Server

+page.svelte11 kB
<script lang="ts"> import { onMount, onDestroy } from 'svelte'; import type { JobHistory } from '$lib/client'; let jobs: JobHistory[] = []; let currentlyPlaying: string | null = null; let audioElement: HTMLAudioElement | null = null; let loading = true; let error: string | null = null; let expandedJobId: string | null = null; async function loadJobs() { try { loading = true; error = null; const response = await fetch('/api/history'); if (!response.ok) { throw new Error('Failed to load job history'); } jobs = await response.json(); } catch (e) { error = e instanceof Error ? e.message : 'Failed to load job history'; } finally { loading = false; } } async function deleteJob(jobId: string) { if (!confirm('Are you sure you want to delete this job?')) { return; } try { const response = await fetch(`/api/history?id=${jobId}`, { method: 'DELETE' }); if (response.ok) { jobs = jobs.filter(job => job.id !== jobId); } else { const data = await response.json(); error = data.error || 'Failed to delete job'; } } catch (e) { error = e instanceof Error ? e.message : 'Failed to delete job'; } } function formatDate(dateStr: string) { return new Date(dateStr).toLocaleString(); } function getStatusColor(status: string) { switch (status) { case 'completed': return 'text-green-600'; case 'failed': return 'text-red-600'; case 'processing': return 'text-blue-600'; default: return 'text-gray-600'; } } function toggleExpand(jobId: string) { expandedJobId = expandedJobId === jobId ? null : jobId; } function getChevronClass(jobId: string) { return expandedJobId === jobId ? 'rotate-180' : ''; } function handlePlay(jobId: string, event: Event) { event.stopPropagation(); if (!audioElement) { audioElement = new Audio(); audioElement.addEventListener('ended', () => { currentlyPlaying = null; }); } if (currentlyPlaying === jobId) { audioElement.pause(); audioElement.currentTime = 0; currentlyPlaying = null; } else { if (currentlyPlaying) { audioElement.pause(); } audioElement.src = `/api/download?id=${jobId}`; audioElement.play().catch(err => { console.error('Error playing audio:', err); currentlyPlaying = null; }); currentlyPlaying = jobId; } } onDestroy(() => { if (audioElement) { audioElement.pause(); audioElement = null; } }); onMount(() => { loadJobs(); }); </script> <main> <h2>Voiceover History</h2> <p class="page-description">View and manage your previously generated voiceover jobs.</p> {#if error} <div class="error"> {error} </div> {/if} {#if loading} <div class="loading-container"> <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div> </div> {:else if jobs.length === 0} <div class="empty-state"> No voiceover jobs found </div> {:else} <div class="history-table"> <table> <thead> <tr> <th class="w-8"></th> <th>ID</th> <th>Status</th> <th>Created</th> <th>Progress</th> </tr> </thead> <tbody> {#each jobs as job} <tr class="result-row" class:expanded={expandedJobId === job.id} on:click={() => toggleExpand(job.id)} > <td class="w-8 px-2 text-center"> <svg class="w-2.5 h-2.5 inline-block transition-transform duration-200 text-gray-400 {getChevronClass(job.id)}" fill="none" stroke="currentColor" viewBox="0 0 24 24" > <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7" /> </svg> </td> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"> {job.id.slice(0, 8)}... </td> <td class="px-6 py-4 whitespace-nowrap text-sm"> <span class={getStatusColor(job.status)}> {job.status} </span> {#if job.error} <span class="text-red-600 text-xs block"> {job.error} </span> {/if} </td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {formatDate(job.created_at)} </td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {job.completed_parts} / {job.total_parts} parts </td> </tr> {#if expandedJobId === job.id} <tr class="details-row"> <td colspan="5"> <div class="details-content"> <div class="actions"> <button on:click|stopPropagation={() => deleteJob(job.id)} class="button button-danger" > Delete </button> {#if job.output_file} <button on:click|stopPropagation={(e) => handlePlay(job.id, e)} class="button button-secondary" class:playing={currentlyPlaying === job.id} > {currentlyPlaying === job.id ? 'Stop' : 'Play'} </button> <a href={`/api/download?id=${job.id}`} download={`voiceover-${job.id}.mp3`} class="button button-primary" on:click|stopPropagation={() => {}} > Download </a> {/if} </div> <div class="script-content"> <strong>Script:</strong> {#each job.script_parts as part} <div class="script-part"> {#if part.actor} <span class="actor">{part.actor}:</span> {/if} {part.text} {#if part.voice_id} <span class="voice-id">(Voice: {part.voice_id})</span> {/if} </div> {/each} </div> </div> </td> </tr> {/if} {/each} </tbody> </table> </div> {/if} </main> <style> main { max-width: 800px; margin: 0 auto; padding: var(--spacing-8); } h2 { margin-bottom: var(--spacing-2); color: var(--color-text); font-size: var(--font-size-2xl); text-align: center; } .page-description { text-align: center; color: var(--color-text-light); margin-bottom: var(--spacing-8); } .loading-container { display: flex; justify-content: center; align-items: center; height: 8rem; } .empty-state { text-align: center; color: var(--color-text-light); padding: var(--spacing-8); } .error { color: var(--color-error); padding: var(--spacing-4); background: #fef2f2; border: 1px solid #fee2e2; border-radius: var(--border-radius-base); margin-bottom: var(--spacing-4); } .history-table { background: var(--color-surface); padding: var(--spacing-6); border-radius: var(--border-radius-lg); box-shadow: var(--shadow-base); overflow-x: auto; } table { width: 100%; border-collapse: collapse; } th { text-align: left; padding: var(--spacing-4) var(--spacing-6); font-size: var(--font-size-sm); font-weight: 500; color: var(--color-text-light); background: var(--color-background); text-transform: uppercase; letter-spacing: 0.05em; } td { padding: var(--spacing-4) var(--spacing-6); font-size: var(--font-size-sm); color: var(--color-text); border-bottom: 1px solid var(--border-color); } .result-row { cursor: pointer; transition: background-color 0.2s ease; } .result-row:hover { background: var(--color-background); } .result-row.expanded { background: var(--color-background); } .details-row td { padding: var(--spacing-4); background: var(--color-background); border-bottom: 1px solid var(--border-color); } .details-content { padding: var(--spacing-2) var(--spacing-4); } .actions { margin-bottom: var(--spacing-4); display: flex; gap: var(--spacing-2); } .script-content { padding: var(--spacing-2) 0; } .script-part { margin: var(--spacing-2) 0; padding-left: var(--spacing-4); } .actor { color: var(--color-primary); font-weight: 500; margin-right: var(--spacing-2); } .voice-id { color: var(--color-text-light); font-size: var(--font-size-xs); margin-left: var(--spacing-2); } .button { padding: var(--spacing-1) var(--spacing-3); border: none; border-radius: var(--border-radius-base); font-size: var(--font-size-sm); font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: all var(--transition-base); min-width: 70px; } .button:hover { transform: translateY(-1px); opacity: 0.9; } .button-primary { background: var(--color-primary); color: var(--color-surface); } .button-danger { background: var(--color-error); color: var(--color-surface); } .button-secondary { background: var(--color-secondary, #4a5568); color: var(--color-surface); } .button.playing { background: var(--color-text-light); animation: pulse 2s infinite; } @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.7; } 100% { opacity: 1; } } @media (max-width: 640px) { main { padding: var(--spacing-4); } h2 { font-size: var(--font-size-xl); margin-bottom: var(--spacing-2); } .page-description { font-size: var(--font-size-sm); margin-bottom: var(--spacing-6); } .history-table { padding: var(--spacing-2); } th, td { padding: var(--spacing-3) var(--spacing-4); } } .rotate-180 { transform: rotate(180deg); } </style>

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mamertofabian/elevenlabs-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server