feat: initialize Electron project with transcription engine integration and UI shell
This commit is contained in:
@@ -72,6 +72,10 @@ function createWindow() {
|
||||
if (action === 'maximize') win.isMaximized() ? win.unmaximize() : win.maximize()
|
||||
if (action === 'close') win.close()
|
||||
})
|
||||
|
||||
ipcMain.on('open-explorer', (event, path) => {
|
||||
shell.showItemInFolder(path)
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
|
||||
@@ -6,7 +6,8 @@ const api = {
|
||||
startTranscription: (filePath, options) => ipcRenderer.invoke('start-transcription', { filePath, options }),
|
||||
onTranscriptionProgress: (callback) => ipcRenderer.on('transcription-progress', (_, progress) => callback(progress)),
|
||||
onTranscriptionData: (callback) => ipcRenderer.on('transcription-data', (_, data) => callback(data)),
|
||||
detectHardware: () => ipcRenderer.invoke('detect-hardware')
|
||||
detectHardware: () => ipcRenderer.invoke('detect-hardware'),
|
||||
openExplorer: (path) => ipcRenderer.send('open-explorer', path)
|
||||
}
|
||||
|
||||
if (process.contextIsolated) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { motion, AnimatePresence } from 'framer-motion'
|
||||
import logo from './assets/logo.png'
|
||||
|
||||
import CustomSelect from './components/CustomSelect'
|
||||
import ResultModal from './components/ResultModal'
|
||||
|
||||
const languages = [
|
||||
{ id: 'auto', name: 'AUTO (Recommended)' },
|
||||
@@ -49,6 +50,7 @@ function App() {
|
||||
model: 'small',
|
||||
format: 'srt'
|
||||
})
|
||||
const [showResult, setShowResult] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.api) {
|
||||
@@ -109,7 +111,7 @@ function App() {
|
||||
try {
|
||||
const result = await window.api.startTranscription(file.path, config)
|
||||
if (result.success) {
|
||||
alert('Transcription Complete! Output: ' + result.outputPath)
|
||||
setShowResult(result.outputPath)
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error: ' + error.message)
|
||||
@@ -310,6 +312,13 @@ function App() {
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{/* Custom Result Modal */}
|
||||
<ResultModal
|
||||
isOpen={!!showResult}
|
||||
path={showResult}
|
||||
onClose={() => setShowResult(null)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { CheckCircle2, FolderOpen, X } from 'lucide-react'
|
||||
|
||||
export default function ResultModal({ isOpen, path, onClose }) {
|
||||
const handleOpenFolder = () => {
|
||||
if (window.api && window.api.openExplorer) {
|
||||
window.api.openExplorer(path)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<div className="modal-overlay">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
className="result-modal glass"
|
||||
>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
<X size={20} />
|
||||
</button>
|
||||
|
||||
<div className="modal-content">
|
||||
<div className="success-icon-container">
|
||||
<div className="success-glow"></div>
|
||||
<CheckCircle2 size={64} className="text-primary-cyan" />
|
||||
</div>
|
||||
|
||||
<h2>Transcription Complete!</h2>
|
||||
<p className="modal-subtitle">Your subtitle file is ready for use.</p>
|
||||
|
||||
<div className="path-container glass">
|
||||
<div className="path-label">OUTPUT PATH:</div>
|
||||
<div className="path-text">{path}</div>
|
||||
</div>
|
||||
|
||||
<div className="modal-actions">
|
||||
<button className="btn-secondary" onClick={onClose}>
|
||||
CLOSE
|
||||
</button>
|
||||
<button className="btn-primary-small" onClick={handleOpenFolder}>
|
||||
<FolderOpen size={18} />
|
||||
OPEN FOLDER
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
@@ -465,3 +465,136 @@ body {
|
||||
transform: translateY(-5px);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
/* Result Modal Styles */
|
||||
.modal-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.result-modal {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
padding: 40px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
border-color: rgba(0, 242, 255, 0.2);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.success-icon-container {
|
||||
margin-bottom: 25px;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.success-glow {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--primary-cyan);
|
||||
filter: blur(30px);
|
||||
opacity: 0.2;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.result-modal h2 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
background: linear-gradient(90deg, #fff, var(--primary-cyan));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.modal-subtitle {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.path-container {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
text-align: left;
|
||||
margin-bottom: 30px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.path-label {
|
||||
font-size: 10px;
|
||||
color: var(--primary-cyan);
|
||||
font-weight: 700;
|
||||
margin-bottom: 5px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.path-text {
|
||||
font-size: 13px;
|
||||
color: #ddd;
|
||||
word-break: break-all;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.btn-primary-small {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
background: linear-gradient(135deg, var(--primary-cyan), var(--primary-magenta));
|
||||
color: #0b0c14;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary-small:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 242, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 12px 25px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user