feat: implement main dashboard UI with file upload, configuration, and transcription progress tracking
This commit is contained in:
+24
-24
@@ -1,11 +1,11 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import {
|
import {
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
Box,
|
Box,
|
||||||
ListMusic,
|
ListMusic,
|
||||||
Settings,
|
Settings,
|
||||||
X,
|
X,
|
||||||
Minus,
|
Minus,
|
||||||
Square,
|
Square,
|
||||||
UploadCloud,
|
UploadCloud,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@@ -56,7 +56,7 @@ function App() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.api.detectHardware().then(setHardware)
|
window.api.detectHardware().then(setHardware)
|
||||||
|
|
||||||
window.api.onTranscriptionProgress((p) => {
|
window.api.onTranscriptionProgress((p) => {
|
||||||
setProgress(p)
|
setProgress(p)
|
||||||
})
|
})
|
||||||
@@ -105,7 +105,7 @@ function App() {
|
|||||||
if (!file) return
|
if (!file) return
|
||||||
setIsTranscribing(true)
|
setIsTranscribing(true)
|
||||||
setProgress(0)
|
setProgress(0)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await window.api.startTranscription(file.path, config)
|
const result = await window.api.startTranscription(file.path, config)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -133,12 +133,12 @@ function App() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="header">
|
<header className="header">
|
||||||
<div className="logo-section">
|
<div className="logo-section">
|
||||||
<img src={logo} alt="Voicext Logo" style={{ height: '32px' }} />
|
<img src={logo} alt="Voicext Logo" style={{ height: '128px' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="nav-tabs">
|
<nav className="nav-tabs">
|
||||||
{['dashboard', 'models', 'queue', 'settings'].map((tab) => (
|
{['dashboard', 'models', 'queue', 'settings'].map((tab) => (
|
||||||
<div
|
<div
|
||||||
key={tab}
|
key={tab}
|
||||||
className={`nav-item ${activeTab === tab ? 'active' : ''}`}
|
className={`nav-item ${activeTab === tab ? 'active' : ''}`}
|
||||||
onClick={() => setActiveTab(tab)}
|
onClick={() => setActiveTab(tab)}
|
||||||
@@ -156,14 +156,14 @@ function App() {
|
|||||||
{/* Content Area */}
|
{/* Content Area */}
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{activeTab === 'dashboard' && (
|
{activeTab === 'dashboard' && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="dashboard"
|
key="dashboard"
|
||||||
initial={{ opacity: 0, scale: 0.98 }}
|
initial={{ opacity: 0, scale: 0.98 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
exit={{ opacity: 0, scale: 1.02 }}
|
exit={{ opacity: 0, scale: 1.02 }}
|
||||||
className="dashboard-grid"
|
className="dashboard-grid"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`drop-zone glass ${dragActive ? 'drag-active' : ''}`}
|
className={`drop-zone glass ${dragActive ? 'drag-active' : ''}`}
|
||||||
onDragEnter={handleDrag}
|
onDragEnter={handleDrag}
|
||||||
onDragLeave={handleDrag}
|
onDragLeave={handleDrag}
|
||||||
@@ -176,8 +176,8 @@ function App() {
|
|||||||
<div className="progress-circle">
|
<div className="progress-circle">
|
||||||
<svg width="120" height="120">
|
<svg width="120" height="120">
|
||||||
<circle cx="60" cy="60" r="54" fill="none" stroke="rgba(255,255,255,0.1)" strokeWidth="8" />
|
<circle cx="60" cy="60" r="54" fill="none" stroke="rgba(255,255,255,0.1)" strokeWidth="8" />
|
||||||
<motion.circle
|
<motion.circle
|
||||||
cx="60" cy="60" r="54" fill="none"
|
cx="60" cy="60" r="54" fill="none"
|
||||||
stroke="url(#gradient)" strokeWidth="8"
|
stroke="url(#gradient)" strokeWidth="8"
|
||||||
strokeDasharray="339.29"
|
strokeDasharray="339.29"
|
||||||
initial={{ strokeDashoffset: 339.29 }}
|
initial={{ strokeDashoffset: 339.29 }}
|
||||||
@@ -206,8 +206,8 @@ function App() {
|
|||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<h2 style={{ fontSize: '20px', color: 'var(--primary-cyan)' }}>{file.name}</h2>
|
<h2 style={{ fontSize: '20px', color: 'var(--primary-cyan)' }}>{file.name}</h2>
|
||||||
<p>File Ready for Transcription</p>
|
<p>File Ready for Transcription</p>
|
||||||
<button
|
<button
|
||||||
className="btn-secondary"
|
className="btn-secondary"
|
||||||
onClick={(e) => { e.stopPropagation(); setFile(null); }}
|
onClick={(e) => { e.stopPropagation(); setFile(null); }}
|
||||||
style={{ marginTop: '10px', background: 'none', border: '1px solid rgba(255,255,255,0.1)', color: '#666', padding: '5px 15px', borderRadius: '20px', cursor: 'pointer' }}
|
style={{ marginTop: '10px', background: 'none', border: '1px solid rgba(255,255,255,0.1)', color: '#666', padding: '5px 15px', borderRadius: '20px', cursor: 'pointer' }}
|
||||||
>
|
>
|
||||||
@@ -233,30 +233,30 @@ function App() {
|
|||||||
<Monitor size={14} />
|
<Monitor size={14} />
|
||||||
Quick Configuration
|
Quick Configuration
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
label="Language:"
|
label="Language:"
|
||||||
options={languages}
|
options={languages}
|
||||||
value={config.language}
|
value={config.language}
|
||||||
onChange={(val) => setConfig({ ...config, language: val })}
|
onChange={(val) => setConfig({ ...config, language: val })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
label="Model:"
|
label="Model:"
|
||||||
options={models}
|
options={models}
|
||||||
value={config.model}
|
value={config.model}
|
||||||
onChange={(val) => setConfig({ ...config, model: val })}
|
onChange={(val) => setConfig({ ...config, model: val })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
label="Format:"
|
label="Format:"
|
||||||
options={formats}
|
options={formats}
|
||||||
value={config.format}
|
value={config.format}
|
||||||
onChange={(val) => setConfig({ ...config, format: val })}
|
onChange={(val) => setConfig({ ...config, format: val })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
style={{ marginTop: '20px', opacity: file ? 1 : 0.5, cursor: file ? 'pointer' : 'not-allowed' }}
|
style={{ marginTop: '20px', opacity: file ? 1 : 0.5, cursor: file ? 'pointer' : 'not-allowed' }}
|
||||||
disabled={!file || isTranscribing}
|
disabled={!file || isTranscribing}
|
||||||
onClick={startTranscription}
|
onClick={startTranscription}
|
||||||
@@ -269,7 +269,7 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'models' && (
|
{activeTab === 'models' && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="models"
|
key="models"
|
||||||
initial={{ opacity: 0, x: 20 }}
|
initial={{ opacity: 0, x: 20 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
|||||||
Reference in New Issue
Block a user