Spaces:
Paused
Paused
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>TranStudio</title> | |
| <link rel="stylesheet" href="/static/css/buefy.min.css"> | |
| <!-- we'll use cdn link for materialdesignicons here --> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css"> | |
| <!-- <link rel="stylesheet" href="/static/css/materialdesignicons.min.css"> --> | |
| <link rel="stylesheet" href="/static/css/style.css"> | |
| <script> | |
| window.process = { env: { NODE_ENV: 'production' } }; | |
| </script> | |
| <script src="/static/js/vue.global.prod.js"></script> | |
| <script src="/static/js/buefy.min.js"></script> | |
| </head> | |
| <body> | |
| <div id="app" class="container"> | |
| <b-navbar> | |
| <template #start> | |
| <b-navbar-item @click="toggleSidebar"> | |
| <b-icon :icon="showSidebar ? 'menu-open' : 'menu'"></b-icon> | |
| </b-navbar-item> | |
| <b-navbar-item tag="router-link" :to="{ path: '/' }"> | |
| <a href="/">TranStudio</a> | |
| </b-navbar-item> | |
| </template> | |
| <template #end> | |
| <!-- <b-navbar-dropdown :label="isAuthenticated ? : 'Account'"> --> | |
| <template v-if="isAuthenticated"> | |
| <b-navbar-item v-if="isAdmin" @click="showAdminPanel = true"> | |
| <b-icon icon="shield-account"></b-icon> | |
| <span class="ml-1">Admin</span> | |
| </b-navbar-item> | |
| <b-navbar-item @click="showResumeUploadModal"> | |
| <b-icon icon="upload"></b-icon> | |
| <span class="ml-1">Upload</span> | |
| </b-navbar-item> | |
| <b-navbar-item @click="showAudioFilesList"> | |
| <b-icon icon="folder"></b-icon> | |
| <span class="ml-1">My Files</span> | |
| </b-navbar-item> | |
| <b-button | |
| type="is-light" | |
| icon-left="clock-outline" | |
| @click="showCreditsInfo"> | |
| Credits | |
| </b-button> | |
| <b-navbar-item @click="logout">Logout {{ username }}</b-navbar-item> | |
| </template> | |
| <template v-else> | |
| <b-navbar-item @click="showLoginModal = true">Login</b-navbar-item> | |
| <b-navbar-item @click="showSignupModal = true">Sign Up</b-navbar-item> | |
| </template> | |
| <!-- </b-navbar-dropdown> --> | |
| </template> | |
| </b-navbar> | |
| <div class="columns mb-6 pb-6"> | |
| <!-- Sidebar --> | |
| <div class="column is-one-quarter" v-if="showSidebar"> | |
| <b-menu> | |
| <b-menu-list :label="`Previous Transcriptions (${transcriptions.length})`"> | |
| <b-menu-item v-for="transcription in transcriptions" | |
| class="is-flex is-justify-content-space-between is-align-items-center" | |
| :key="transcription.id" | |
| @click="loadTranscription(transcription)" :label="transcription.name"> | |
| <div class="is-flex is-justify-content-space-between is-align-items-center" style="width: 100%"> | |
| <span> | |
| {{transcription?.audio_file}} | |
| </span> | |
| <div class="buttons are-small"> | |
| <b-button | |
| type="is-info" | |
| size="is-small" | |
| icon-left="download" | |
| @click.stop="downloadTranscription(transcription)"> | |
| </b-button> | |
| <b-button | |
| type="is-danger" | |
| size="is-small" | |
| icon-left="delete" | |
| @click.stop="deleteTranscription(transcription.id)"> | |
| </b-button> | |
| </div> | |
| </div> | |
| </b-menu-item> | |
| </b-menu-list> | |
| </b-menu> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="column"> | |
| <!-- <b-field label="Select AI Model"> | |
| <b-select v-model="selectedModel"> | |
| <option value="whisper-large-v3-turbo">Groq - Whisper Large v3 Turbo</option> | |
| <option value="openai-whisper-large-v3-turbo">Huggingface - OpenAI Whisper Large v3 Turbo</option> | |
| </b-select> | |
| </b-field> --> | |
| <b-field label="Format"> | |
| <b-select v-model="responseFormat"> | |
| <option value="verbose_json">Verbose (with timestamps)</option> | |
| <option value="json">Standard</option> | |
| <option value="text">Simple</option> | |
| </b-select> | |
| </b-field> | |
| <b-field label="Language"> | |
| <b-select v-model="selectedLanguage"> | |
| <option value="auto">Auto Detect</option> | |
| <option value="en">English</option> | |
| <option value="es">Spanish</option> | |
| <option value="ar">Arabic</option> | |
| <option value="fr">French</option> | |
| <option value="de">German</option> | |
| <option value="it">Italian</option> | |
| </b-select> | |
| </b-field> | |
| <b-field label="Temperature"> | |
| <b-slider v-model="temperature" :min="0" :max="1" :step="0.1"></b-slider> | |
| <span class="tag is-primary">{{ temperature }}</span> | |
| </b-field> | |
| <b-field label="Chunk Size (in minutes)"> | |
| <b-slider v-model="chunkSize" :min="5" :max="30" :step="1"></b-slider> | |
| <span class="tag is-primary">{{ chunkSize }}</span> | |
| </b-field> | |
| <b-field label="Overlap (in seconds)"> | |
| <b-slider v-model="overlap" :min="1" :max="60" :step="1"></b-slider> | |
| <span class="tag is-primary">{{ overlap }}</span> | |
| </b-field> | |
| <b-field label="Selection" v-if="totalDuration"> | |
| <b-slider v-model="selection" :step="1" type="is-info" :tooltip="false"> | |
| <!-- <template v-for="n in totalDuration" :key="n"> | |
| <b-slider-tick :value="n">{{ n }}</b-slider-tick> | |
| </template> --> | |
| </b-slider> | |
| </b-field> | |
| <div class="selection-info" v-if="totalDuration"> | |
| <span class="tag is-info"> | |
| Start: {{ formatTime(selection[0] * totalDuration / 100) }} | |
| </span> | |
| <span class="tag is-info ml-2"> | |
| End: {{ formatTime(selection[1] * totalDuration / 100) }} | |
| </span> | |
| </div> | |
| <b-field label="System Prompt (Optional)"> | |
| <b-input v-model="systemPrompt" maxlength="256" type="textarea" placeholder="Provide context or specify how to spell unfamiliar words (limited to 224 tokens only in English)."></b-input> | |
| </b-field> | |
| <b-upload v-model="audioFile" accept="audio/*" @change="handleAudioUpload" multiple> | |
| <a class="button is-primary"> | |
| <b-icon icon="upload"></b-icon> | |
| <span>Click to upload</span> | |
| </a> | |
| </b-upload> | |
| <b-field v-if="audioFile.length"> | |
| <span class="file-name"> | |
| {{ audioFile[0].name }} | |
| </span> | |
| </b-field> | |
| </b-field> | |
| <!-- <div v-if="audioUrl" class="audio-controls mb-4"> | |
| <audio ref="audioPlayer" :src="audioUrl"></audio> | |
| <b-button @click="togglePlayPause"> | |
| {{ isPlaying ? 'Pause' : 'Play' }} | |
| </b-button> | |
| </div> --> | |
| <b-field class="mt-2" label="Transcription"> | |
| <b-input v-model="transcriptionText" type="textarea" rows="5"></b-input> | |
| </b-field> | |
| <b-field class="mt-2" label="Segments" v-if="segments.length && responseFormat === 'verbose_json'"> | |
| <div class="segments-container"> | |
| <div v-for="(segment, index) in segments" :key="segment.id" | |
| class="segment-box" | |
| :class="{'is-active': activeSegment === index}" | |
| @click="playSegment(segment)"> | |
| <div class="segment-time"> | |
| {{ formatTime(segment.start) }} - {{ formatTime(segment.end) }} | |
| </div> | |
| <div class="segment-text"> | |
| <b-input v-model="segment.text" type="textarea" rows="2"></b-input> | |
| </div> | |
| <div class="segment-actions"> | |
| <b-button type="is-info" size="is-small" @click.stop="copySegmentText(segment.text)"> | |
| <b-icon icon="content-copy"></b-icon> | |
| </b-button> | |
| <b-button type="is-danger" size="is-small" @click.stop="deleteSegment(index)"> | |
| <b-icon icon="delete"></b-icon> | |
| </b-button> | |
| </div> | |
| </div> | |
| </div> | |
| </b-field> | |
| <b-button type="is-primary" class="mr-2" @click="processTranscription" :disabled="!audioUrl" :loading="isProcessing">Process</b-button> | |
| <b-button type="is-success" class="mr-2" @click="saveTranscription" :disabled="!transcriptionText" :loading="isProcessing">Save</b-button> | |
| <b-button type="is-info" class="mr-2" @click="downloadCurrentTranscription" :disabled="!transcriptionText"> | |
| <b-icon icon="download"></b-icon> | |
| <span>Download</span> | |
| </b-button> | |
| <b-button class="control-btn" @click="togglePlayer" :disabled="!audioUrl"> | |
| <b-icon :icon="showPlayer ? 'menu-down' : 'menu-up'"></b-icon> | |
| </b-button> | |
| </div> | |
| </div><!-- .columns --> | |
| <div class="columns mt-2"> | |
| <div class="audio-player" v-if="showPlayer"> | |
| <div class="player-controls" v-if="audioUrl"> | |
| <b-button class="control-btn" @click="togglePlay"> | |
| <b-icon :icon="isPlaying ? 'pause' : 'play'"></b-icon> | |
| </b-button> | |
| <b-button class="control-btn" @click="stopAudio"> | |
| <b-icon icon="stop"></b-icon> | |
| </b-button> | |
| <b-slider v-model="volume" :min="0" :max="1" :step="0.1" @input="updateVolume" size="is-small"> | |
| <template #indicator> | |
| <span class="tag is-primary is-small">{{ volume.toFixed(1) }}</span> | |
| </template> | |
| </b-slider> | |
| <b-button class="control-btn" @click="toggleMute"> | |
| <b-icon :icon="isMuted ? 'volume-off' : 'volume-high'"></b-icon> | |
| </b-button> | |
| <b-button class="control-btn" @click="togglePlayer"> | |
| <b-icon :icon="showPlayer ? 'menu-down' : 'menu-up'"></b-icon> | |
| </b-button> | |
| </div><!-- .player-controls --> | |
| <div class="waveform-container" v-if="segments.length"> | |
| <div class="waveform" ref="waveform"> | |
| <div v-for="(segment, index) in segments" | |
| :key="segment.id" | |
| class="segment-marker" | |
| :style="{ left: `${(segment.start / totalDuration) * 100}%`, width: `${((segment.end - segment.start) / totalDuration) * 100}%` }" | |
| @click="playSegment(segment)"> | |
| <div class="segment-tooltip"> | |
| {{ formatTime(segment.start) }} - {{ formatTime(segment.end) }} | |
| </div> | |
| </div> | |
| <div class="progress-waveform" :style="{ width: `${(currentTime / totalDuration) * 100}%` }"></div> | |
| </div> | |
| </div><!-- .waveform-container --> | |
| <div class="current-time"> | |
| {{ formatTime(currentTime) }} / {{ formatTime(totalDuration) }} | |
| </div> | |
| </div><!-- .audio-player --> | |
| </div> | |
| <!-- Login Modal --> | |
| <b-modal v-model="showLoginModal" has-modal-card trap-focus> | |
| <div class="modal-card"> | |
| <header class="modal-card-head"> | |
| <p class="modal-card-title">Login</p> | |
| </header> | |
| <section class="modal-card-body"> | |
| <b-field label="Username"> | |
| <b-input type="text" v-model="loginForm.username" placeholder="Enter your username"></b-input> | |
| </b-field> | |
| <b-field label="Password"> | |
| <b-input type="password" v-model="loginForm.password" password-reveal></b-input> | |
| </b-field> | |
| </section> | |
| <footer class="modal-card-foot"> | |
| <b-button type="is-primary" @click="handleLogin" :loading="isLoading">Login</b-button> | |
| <b-button @click="showLoginModal = false">Cancel</b-button> | |
| </footer> | |
| </div> | |
| </b-modal> | |
| <!-- Signup Modal --> | |
| <b-modal v-model="showSignupModal" has-modal-card trap-focus> | |
| <div class="modal-card"> | |
| <header class="modal-card-head"> | |
| <p class="modal-card-title">Sign Up</p> | |
| </header> | |
| <section class="modal-card-body"> | |
| <b-field label="Username"> | |
| <b-input v-model="signupForm.username" placeholder="Enter username"></b-input> | |
| </b-field> | |
| <b-field label="Email"> | |
| <b-input type="email" v-model="signupForm.email" placeholder="Enter your email"></b-input> | |
| </b-field> | |
| <b-field label="Password"> | |
| <b-input type="password" v-model="signupForm.password" password-reveal></b-input> | |
| </b-field> | |
| <b-field label="Confirm Password"> | |
| <b-input type="password" v-model="signupForm.confirmPassword" password-reveal></b-input> | |
| </b-field> | |
| </section> | |
| <footer class="modal-card-foot"> | |
| <b-button type="is-primary" @click="handleSignup" :loading="isLoading">Sign Up</b-button> | |
| <b-button @click="showSignupModal = false">Cancel</b-button> | |
| </footer> | |
| </div> | |
| </b-modal> | |
| <!-- Admin Panel Modal --> | |
| <b-modal v-model="showAdminPanel" has-modal-card trap-focus full-screen> | |
| <div class="modal-card"> | |
| <header class="modal-card-head"> | |
| <p class="modal-card-title">Admin Panel</p> | |
| </header> | |
| <section class="modal-card-body"> | |
| <!-- Add tabs for different admin functions --> | |
| <b-tabs v-model="activeAdminTab"> | |
| <!-- User Management Tab --> | |
| <b-tab-item label="User Management" icon="account-group"> | |
| <div class="block"> | |
| <b-field grouped> | |
| <b-input placeholder="Search users..." v-model="userSearchQuery" expanded></b-input> | |
| <b-button type="is-primary" icon-left="magnify">Search</b-button> | |
| </b-field> | |
| </div> | |
| <b-table | |
| :data="filteredUsers" | |
| :loading="loadingUsers" | |
| :paginated="true" | |
| :per-page="5" | |
| :mobile-cards="true"> | |
| <!-- Existing user table columns --> | |
| <b-table-column field="id" label="ID" width="40" numeric v-slot="props"> | |
| {{ props.row.id }} | |
| </b-table-column> | |
| <!-- ... other existing columns ... --> | |
| <b-table-column field="username" label="Username" v-slot="props"> | |
| {{ props.row.username }} | |
| </b-table-column> | |
| <b-table-column field="email" label="Email" v-slot="props"> | |
| {{ props.row.email }} | |
| </b-table-column> | |
| <b-table-column field="is_admin" label="Admin" v-slot="props"> | |
| <b-icon | |
| :icon="props.row.is_admin ? 'check' : 'close'" | |
| :type="props.row.is_admin ? 'is-success' : 'is-danger'"> | |
| </b-icon> | |
| </b-table-column> | |
| <b-table-column field="disabled" label="Status" v-slot="props"> | |
| <b-tag :type="props.row.disabled ? 'is-danger' : 'is-success'"> | |
| {{ props.row.disabled ? 'Disabled' : 'Active' }} | |
| </b-tag> | |
| </b-table-column> | |
| <b-table-column label="Actions" v-slot="props"> | |
| <div class="buttons"> | |
| <b-button | |
| size="is-small" | |
| :type="props.row.disabled ? 'is-success' : 'is-warning'" | |
| :icon-left="props.row.disabled ? 'account-check' : 'account-cancel'" | |
| @click="toggleUserStatus(props.row)" | |
| :disabled="props.row.is_admin && props.row.id === currentUser.id"> | |
| {{ props.row.disabled ? 'Enable' : 'Disable' }} | |
| </b-button> | |
| <b-button | |
| size="is-small" | |
| type="is-danger" | |
| icon-left="delete" | |
| @click="deleteUser(props.row)" | |
| :disabled="props.row.id === currentUser.id"> | |
| Delete | |
| </b-button> | |
| </div> | |
| </b-table-column> | |
| <template #empty> | |
| <div class="has-text-centered">No users found</div> | |
| </template> | |
| </b-table> | |
| </b-tab-item> | |
| <!-- Credit Management Tab --> | |
| <b-tab-item label="Credit Management" icon="clock-outline"> | |
| <div class="block"> | |
| <b-field grouped> | |
| <b-input placeholder="Search users..." v-model="creditSearchQuery" expanded></b-input> | |
| <b-button type="is-primary" icon-left="magnify">Search</b-button> | |
| </b-field> | |
| </div> | |
| <b-table | |
| :data="filteredCredits" | |
| :loading="loadingCredits" | |
| :paginated="true" | |
| :per-page="5" | |
| :mobile-cards="true"> | |
| <b-table-column field="user_id" label="ID" width="40" numeric v-slot="props"> | |
| {{ props.row.user_id }} | |
| </b-table-column> | |
| <b-table-column field="username" label="Username" v-slot="props"> | |
| {{ props.row.username }} | |
| </b-table-column> | |
| <b-table-column field="minutes_used" label="Minutes Used" numeric v-slot="props"> | |
| {{ props.row.minutes_used }} | |
| </b-table-column> | |
| <b-table-column field="minutes_quota" label="Quota" numeric v-slot="props"> | |
| {{ props.row.minutes_quota }} | |
| </b-table-column> | |
| <b-table-column field="minutes_remaining" label="Remaining" numeric v-slot="props"> | |
| <b-tag :type="getQuotaTagType(props.row)"> | |
| {{ props.row.minutes_remaining }} | |
| </b-tag> | |
| </b-table-column> | |
| <b-table-column field="last_updated" label="Last Updated" v-slot="props"> | |
| {{ new Date(props.row.last_updated).toLocaleString() }} | |
| </b-table-column> | |
| <b-table-column label="Actions" v-slot="props"> | |
| <div class="buttons"> | |
| <b-button | |
| size="is-small" | |
| type="is-info" | |
| icon-left="pencil" | |
| @click="editUserCredits(props.row)"> | |
| Edit | |
| </b-button> | |
| <b-button | |
| size="is-small" | |
| type="is-warning" | |
| icon-left="refresh" | |
| @click="resetUserQuota(props.row)"> | |
| Reset Quota | |
| </b-button> | |
| </div> | |
| </b-table-column> | |
| <template #empty> | |
| <div class="has-text-centered">No credit data found</div> | |
| </template> | |
| </b-table> | |
| </b-tab-item> | |
| </b-tabs> | |
| </section> | |
| <footer class="modal-card-foot"> | |
| <b-button @click="refreshAdminData" type="is-info" icon-left="refresh">Refresh</b-button> | |
| <b-button @click="showAdminPanel = false">Close</b-button> | |
| </footer> | |
| </div> | |
| </b-modal> | |
| <!-- Add Credit Edit Modal --> | |
| <b-modal v-model="showCreditEditModal" has-modal-card trap-focus> | |
| <div class="modal-card"> | |
| <header class="modal-card-head"> | |
| <p class="modal-card-title">Edit User Credits</p> | |
| </header> | |
| <section class="modal-card-body"> | |
| <div v-if="selectedUserCredit"> | |
| <p class="mb-3"><strong>User:</strong> {{ selectedUserCredit.username }}</p> | |
| <b-field label="Minutes Used"> | |
| <b-numberinput v-model="editCreditForm.minutes_used" min="0" step="1"></b-numberinput> | |
| </b-field> | |
| <b-field label="Minutes Quota"> | |
| <b-numberinput v-model="editCreditForm.minutes_quota" min="0" step="1"></b-numberinput> | |
| </b-field> | |
| <div class="notification is-info is-light mt-4"> | |
| <p>Adjusting credits will affect the user's usage tracking and may impact billing.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <footer class="modal-card-foot"> | |
| <b-button type="is-primary" @click="saveUserCredits" :loading="savingCredits">Save</b-button> | |
| <b-button @click="showCreditEditModal = false">Cancel</b-button> | |
| </footer> | |
| </div> | |
| </b-modal> | |
| <!-- Upload Audio Modal --> | |
| <b-modal v-model="showUploadModal" has-modal-card trap-focus> | |
| <div class="modal-card"> | |
| <header class="modal-card-head"> | |
| <p class="modal-card-title">Upload Audio File</p> | |
| </header> | |
| <section class="modal-card-body"> | |
| <b-field label="Select Audio File"> | |
| <b-upload v-model="resumableFile" | |
| @input="prepareChunkedUpload" | |
| accept="audio/*" | |
| expanded | |
| drag-drop> | |
| <div class="content has-text-centered"> | |
| <p> | |
| <b-icon icon="upload" size="is-large"></b-icon> | |
| </p> | |
| <p>Drop your audio file here or click to upload</p> | |
| </div> | |
| </b-upload> | |
| </b-field> | |
| <div v-if="resumableFile"> | |
| <p class="has-text-weight-bold">{{ resumableFile.name }}</p> | |
| <p>Size: {{ formatFileSize(resumableFile.size) }}</p> | |
| <b-progress v-if="uploadInProgress" | |
| :value="uploadProgress.toFixed(1)" | |
| type="is-info" | |
| show-value | |
| size="is-medium" | |
| format="percent" | |
| class="mt-4 mb-4"></b-progress> | |
| </div> | |
| <div class="upload-stats mt-2" v-if="uploadInProgress"> | |
| <p> | |
| <b-icon icon="file-upload-outline" size="is-small"></b-icon> | |
| Uploaded: {{ formatFileSize(currentChunkIndex * chunkUploadSize) }} / {{ formatFileSize(resumableFile.size) }} | |
| </p> | |
| <p v-if="uploadSpeed"> | |
| <b-icon icon="speedometer" size="is-small"></b-icon> | |
| Speed: {{ uploadSpeed }} KB/s | |
| </p> | |
| <p v-if="estimatedTimeRemaining"> | |
| <b-icon icon="clock-outline" size="is-small"></b-icon> | |
| Time remaining: {{ estimatedTimeRemaining }} | |
| </p> | |
| </div> | |
| <div v-if="uploadPaused" class="notification is-warning"> | |
| Upload paused. Click Resume to continue. | |
| </div> | |
| </section> | |
| <footer class="modal-card-foot"> | |
| <b-button v-if="!uploadInProgress && resumableFile" | |
| type="is-primary" | |
| @click="startChunkedUpload" | |
| :loading="!uploadId" | |
| :disabled="!uploadId || uploadInProgress"> | |
| Start Upload | |
| </b-button> | |
| <b-button v-if="uploadInProgress && !uploadPaused" | |
| type="is-warning" | |
| @click="pauseUpload"> | |
| Pause | |
| </b-button> | |
| <b-button v-if="uploadPaused" | |
| type="is-info" | |
| @click="resumeUpload"> | |
| Resume | |
| </b-button> | |
| <b-button v-if="uploadInProgress" | |
| type="is-danger" | |
| @click="cancelUpload"> | |
| Cancel | |
| </b-button> | |
| <b-button @click="closeUploadModal">Close</b-button> | |
| </footer> | |
| </div> | |
| </b-modal> | |
| <!-- Uploaded files Modal --> | |
| <b-modal v-model="showAudioFilesModal" has-modal-card trap-focus> | |
| <div class="modal-card"> | |
| <header class="modal-card-head"> | |
| <p class="modal-card-title">Your Audio Files</p> | |
| </header> | |
| <section class="modal-card-body"> | |
| <b-field grouped> | |
| <b-field expanded> | |
| <b-input placeholder="Search files..." v-model="audioFileSearchQuery" icon="magnify"></b-input> | |
| </b-field> | |
| <b-field> | |
| <b-button type="is-primary" icon-left="refresh" @click="loadUploadedAudioFiles" :loading="loadingAudioFiles"> | |
| Refresh | |
| </b-button> | |
| </b-field> | |
| </b-field> | |
| <b-table | |
| :data="filteredAudioFiles" | |
| :loading="loadingAudioFiles" | |
| :paginated="uploadedAudioFiles.length > 10" | |
| :per-page="10" | |
| detailed | |
| detail-key="filename" | |
| aria-next-label="Next page" | |
| aria-previous-label="Previous page" | |
| aria-page-label="Page" | |
| aria-current-label="Current page"> | |
| <b-table-column field="filename" label="Filename" v-slot="props"> | |
| {{ props.row.filename }} | |
| </b-table-column> | |
| <b-table-column field="size" label="Size" v-slot="props"> | |
| {{ formatFileSize(props.row.size) }} | |
| </b-table-column> | |
| <b-table-column field="uploaded_at" label="Uploaded" v-slot="props"> | |
| {{ new Date(props.row.uploaded_at).toLocaleString() }} | |
| </b-table-column> | |
| <b-table-column v-if="isAdmin" field="username" label="User" v-slot="props"> | |
| {{ props.row.username || 'Unknown' }} | |
| </b-table-column> | |
| <b-table-column label="Actions" v-slot="props"> | |
| <div class="buttons"> | |
| <b-button | |
| size="is-small" | |
| type="is-info" | |
| icon-left="play" | |
| @click="selectAudioFile(props.row)"> | |
| Select | |
| </b-button> | |
| <b-button | |
| size="is-small" | |
| type="is-danger" | |
| icon-left="delete" | |
| @click="deleteAudioFile(props.row)"> | |
| Delete | |
| </b-button> | |
| </div> | |
| </b-table-column> | |
| <template #detail="props"> | |
| <div class="content"> | |
| <audio controls :src="getAudioUrl(props.row)" style="width: 100%"></audio> | |
| </div> | |
| </template> | |
| </b-table> | |
| </section> | |
| <footer class="modal-card-foot"> | |
| <b-button @click="showResumeUploadModal" type="is-primary" icon-left="upload"> | |
| Upload New File | |
| </b-button> | |
| <b-button @click="showAudioFilesModal = false">Close</b-button> | |
| </footer> | |
| </div> | |
| </b-modal> | |
| <!-- Show Credit Modal --> | |
| <b-modal v-model="showCreditsModal" has-modal-card trap-focus> | |
| <div class="modal-card"> | |
| <header class="modal-card-head"> | |
| <p class="modal-card-title">Your Transcription Credits</p> | |
| </header> | |
| <section class="modal-card-body"> | |
| <div class="content"> | |
| <div class="box"> | |
| <h4 class="title is-4">Usage Summary</h4> | |
| <p> | |
| <b-icon icon="clock-outline" size="is-small"></b-icon> | |
| <strong>Total Minutes Used:</strong> {{ currentUserCredit.minutes_used }} | |
| </p> | |
| <p> | |
| <b-icon icon="timer-outline" size="is-small"></b-icon> | |
| <strong>Minutes Quota:</strong> {{ currentUserCredit.minutes_quota }} | |
| </p> | |
| <p> | |
| <b-icon icon="timer-sand" size="is-small"></b-icon> | |
| <strong>Minutes Remaining:</strong> | |
| <b-tag :type="getQuotaTagType(currentUserCredit)"> | |
| {{ currentUserCredit.minutes_remaining }} | |
| </b-tag> | |
| </p> | |
| <p v-if="currentUserCredit.last_updated"> | |
| <b-icon icon="calendar-clock" size="is-small"></b-icon> | |
| <strong>Last Updated:</strong> {{ new Date(currentUserCredit.last_updated).toLocaleString() }} | |
| </p> | |
| </div> | |
| <div class="notification is-info is-light"> | |
| <p>Your usage is tracked to provide you with accurate billing information.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <footer class="modal-card-foot"> | |
| <b-button @click="showCreditsModal = false">Close</b-button> | |
| </footer> | |
| </div> | |
| </b-modal> | |
| </div><!-- #app --> | |
| <script src="/static/js/howler.min.js"></script> | |
| <script src="/static/js/app.js"></script> | |
| <script type="module"> | |
| import Chatbot from "https://cdn.jsdelivr.net/npm/flowise-embed/dist/web.js" | |
| Chatbot.init({ | |
| chatflowid: "dd9ac7c1-1d2a-4ef1-a23b-5fe6aca4d1d5", | |
| apiHost: "https://bitsnaps-flowise.hf.space", | |
| chatflowConfig: { | |
| /* Chatflow Config */ | |
| }, | |
| observersConfig: { | |
| /* Observers Config */ | |
| }, | |
| theme: { | |
| customCSS: ``, | |
| chatWindow: { | |
| showTitle: true, | |
| showAgentMessages: true, | |
| title: 'Support Assistant', | |
| titleAvatarSrc: 'https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/svg/google-messages.svg', | |
| welcomeMessage: 'Hello!', | |
| // errorMessage: 'This is a custom error message', | |
| backgroundColor: '#ffffff', | |
| // backgroundImage: 'enter image path or link', | |
| height: 700, | |
| width: 400, | |
| fontSize: 16, | |
| starterPrompts: [ | |
| "I got an 429 error, what does this mean?", | |
| "What are the rate limits?" | |
| ], | |
| starterPromptFontSize: 15, | |
| clearChatOnReload: false, | |
| // sourceDocsTitle: 'Sources:', | |
| renderHTML: true, | |
| textInput: { | |
| placeholder: 'Type your question here...', //Are you having an issue? | |
| backgroundColor: '#ffffff', | |
| textColor: '#303235', | |
| sendButtonColor: '#3B81F6', | |
| maxChars: 50, | |
| maxCharsWarningMessage: 'You exceeded the characters limit. Please input less than 50 characters.', | |
| autoFocus: true, | |
| // sendMessageSound: true, | |
| // sendSoundLocation: 'send_message.mp3', | |
| // receiveMessageSound: true, | |
| // receiveSoundLocation: 'receive_message.mp3' | |
| }, | |
| footer: { | |
| textColor: '#303235', | |
| text: 'Powered by', | |
| company: 'CorpoSense', | |
| companyLink: 'https://corposense.com' | |
| } | |
| } | |
| } | |
| }) | |
| </script> | |
| </body> | |
| </html> |