TranStudio / index.html
bitsnaps's picture
add a chatbot
d033737 verified
<!DOCTYPE html>
<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>