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 === 'maximize') win.isMaximized() ? win.unmaximize() : win.maximize()
|
||||||
if (action === 'close') win.close()
|
if (action === 'close') win.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.on('open-explorer', (event, path) => {
|
||||||
|
shell.showItemInFolder(path)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ const api = {
|
|||||||
startTranscription: (filePath, options) => ipcRenderer.invoke('start-transcription', { filePath, options }),
|
startTranscription: (filePath, options) => ipcRenderer.invoke('start-transcription', { filePath, options }),
|
||||||
onTranscriptionProgress: (callback) => ipcRenderer.on('transcription-progress', (_, progress) => callback(progress)),
|
onTranscriptionProgress: (callback) => ipcRenderer.on('transcription-progress', (_, progress) => callback(progress)),
|
||||||
onTranscriptionData: (callback) => ipcRenderer.on('transcription-data', (_, data) => callback(data)),
|
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) {
|
if (process.contextIsolated) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { motion, AnimatePresence } from 'framer-motion'
|
|||||||
import logo from './assets/logo.png'
|
import logo from './assets/logo.png'
|
||||||
|
|
||||||
import CustomSelect from './components/CustomSelect'
|
import CustomSelect from './components/CustomSelect'
|
||||||
|
import ResultModal from './components/ResultModal'
|
||||||
|
|
||||||
const languages = [
|
const languages = [
|
||||||
{ id: 'auto', name: 'AUTO (Recommended)' },
|
{ id: 'auto', name: 'AUTO (Recommended)' },
|
||||||
@@ -49,6 +50,7 @@ function App() {
|
|||||||
model: 'small',
|
model: 'small',
|
||||||
format: 'srt'
|
format: 'srt'
|
||||||
})
|
})
|
||||||
|
const [showResult, setShowResult] = useState(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!window.api) {
|
if (!window.api) {
|
||||||
@@ -109,7 +111,7 @@ function App() {
|
|||||||
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) {
|
||||||
alert('Transcription Complete! Output: ' + result.outputPath)
|
setShowResult(result.outputPath)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('Error: ' + error.message)
|
alert('Error: ' + error.message)
|
||||||
@@ -310,6 +312,13 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
{/* Custom Result Modal */}
|
||||||
|
<ResultModal
|
||||||
|
isOpen={!!showResult}
|
||||||
|
path={showResult}
|
||||||
|
onClose={() => setShowResult(null)}
|
||||||
|
/>
|
||||||
</div>
|
</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);
|
transform: translateY(-5px);
|
||||||
transition: all 0.3s;
|
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