Function bodies 178 total
start_training method · python · L83-L114 (32 LOC)backend/app/utils/piper_bridge.py
def start_training(
self,
config: TrainingConfig,
on_metrics: Callable[[dict], None] | None = None,
on_log: Callable[[str], None] | None = None,
) -> subprocess.Popen:
"""Запустити тренування як subprocess."""
cmd = self.build_training_command(config)
logger.info(f"Starting training: {' '.join(cmd)}")
env = os.environ.copy()
env["PYTHONUNBUFFERED"] = "1"
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
env=env,
)
# Start monitor thread
if on_metrics or on_log:
monitor = threading.Thread(
target=self._monitor_output,
args=(process, on_metrics, on_log),
daemon=True,
)
monitor.start()
return process_monitor_output method · python · L116-L136 (21 LOC)backend/app/utils/piper_bridge.py
def _monitor_output(
self,
process: subprocess.Popen,
on_metrics: Callable[[dict], None] | None,
on_log: Callable[[str], None] | None,
):
"""Моніторинг stdout тренування, парсинг метрик."""
for line in iter(process.stdout.readline, ""):
line = line.strip()
if not line:
continue
if on_log:
on_log(line)
if on_metrics:
metrics = self._parse_metrics(line)
if metrics:
on_metrics(metrics)
process.wait()_parse_metrics method · python · L138-L165 (28 LOC)backend/app/utils/piper_bridge.py
def _parse_metrics(self, line: str) -> dict | None:
"""Парсити метрики з виводу Lightning trainer."""
# Lightning format: "Epoch X, global step Y: 'val/loss_g': Z, 'val/loss_d': W"
# Or progress bar: "Epoch 0: 50%|... loss_g=12.34, loss_d=3.21"
metrics = {}
# Parse epoch
epoch_match = re.search(r'[Ee]poch\s+(\d+)', line)
if epoch_match:
metrics["epoch"] = int(epoch_match.group(1))
# Parse step
step_match = re.search(r'(?:global.step|step)\s*[=:]\s*(\d+)', line, re.IGNORECASE)
if step_match:
metrics["step"] = int(step_match.group(1))
# Parse losses
for loss_name in ["loss_g", "loss_d", "loss_gen", "loss_disc", "loss"]:
match = re.search(rf'{loss_name}\s*[=:]\s*([\d.]+)', line, re.IGNORECASE)
if match:
metrics[loss_name] = float(match.group(1))
# Parse learning rate
lr_match = re.search(r'lr\s*[=:]\s*([\d.stop_training method · python · L167-L177 (11 LOC)backend/app/utils/piper_bridge.py
def stop_training(self, process: subprocess.Popen):
"""Зупинити тренування gracefully."""
if process.poll() is None:
logger.info("Stopping training (SIGTERM)...")
process.send_signal(signal.SIGTERM)
try:
process.wait(timeout=30)
except subprocess.TimeoutExpired:
logger.warning("Training did not stop, killing...")
process.kill()
process.wait()export_to_onnx method · python · L179-L190 (12 LOC)backend/app/utils/piper_bridge.py
def export_to_onnx(self, checkpoint_path: str, output_path: str) -> str:
"""Експортувати checkpoint в ONNX."""
cmd = [
"python3", "-m", "piper.train.export_onnx",
"--checkpoint", checkpoint_path,
"--output-file", output_path,
]
logger.info(f"Exporting: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
raise RuntimeError(f"Export failed: {result.stderr}")
return output_pathsynthesize method · python · L192-L202 (11 LOC)backend/app/utils/piper_bridge.py
def synthesize(self, onnx_path: str, config_path: str, text: str) -> bytes:
"""Синтезувати мовлення з ONNX моделі."""
from piper import PiperVoice
import io
import wave
voice = PiperVoice.load(onnx_path, config_path)
wav_buffer = io.BytesIO()
with wave.open(wav_buffer, "wb") as wav:
voice.synthesize(text, wav)
return wav_buffer.getvalue()WebSocketManager class · python · L7-L39 (33 LOC)backend/app/utils/websocket_manager.py
class WebSocketManager:
def __init__(self):
self._connections: dict[str, list[WebSocket]] = {}
async def connect(self, project_id: str, websocket: WebSocket):
await websocket.accept()
if project_id not in self._connections:
self._connections[project_id] = []
self._connections[project_id].append(websocket)
def disconnect(self, project_id: str, websocket: WebSocket):
if project_id in self._connections:
self._connections[project_id] = [
ws for ws in self._connections[project_id] if ws != websocket
]
if not self._connections[project_id]:
del self._connections[project_id]
async def send_to_project(self, project_id: str, message: dict[str, Any]):
if project_id not in self._connections:
return
dead = []
for ws in self._connections[project_id]:
try:
await ws.send_text(json.dumps(message))
Open data scored by Repobility · https://repobility.com
connect method · python · L11-L15 (5 LOC)backend/app/utils/websocket_manager.py
async def connect(self, project_id: str, websocket: WebSocket):
await websocket.accept()
if project_id not in self._connections:
self._connections[project_id] = []
self._connections[project_id].append(websocket)disconnect method · python · L17-L23 (7 LOC)backend/app/utils/websocket_manager.py
def disconnect(self, project_id: str, websocket: WebSocket):
if project_id in self._connections:
self._connections[project_id] = [
ws for ws in self._connections[project_id] if ws != websocket
]
if not self._connections[project_id]:
del self._connections[project_id]send_to_project method · python · L25-L35 (11 LOC)backend/app/utils/websocket_manager.py
async def send_to_project(self, project_id: str, message: dict[str, Any]):
if project_id not in self._connections:
return
dead = []
for ws in self._connections[project_id]:
try:
await ws.send_text(json.dumps(message))
except Exception:
dead.append(ws)
for ws in dead:
self.disconnect(project_id, ws)broadcast method · python · L37-L39 (3 LOC)backend/app/utils/websocket_manager.py
async def broadcast(self, message: dict[str, Any]):
for project_id in list(self._connections.keys()):
await self.send_to_project(project_id, message)getAudioUrl function · typescript · L53-L55 (3 LOC)frontend/src/api/youtube.ts
export function getAudioUrl(filePath: string): string {
return `/api/audio/${filePath}`
}App function · typescript · L11-L27 (17 LOC)frontend/src/App.tsx
function App() {
return (
<BrowserRouter>
<Routes>
<Route element={<AppShell />}>
<Route path="/" element={<DashboardPage />} />
<Route path="/project/:projectId/download" element={<DownloadPage />} />
<Route path="/project/:projectId/transcription" element={<TranscriptionPage />} />
<Route path="/project/:projectId/dataset" element={<DatasetPage />} />
<Route path="/project/:projectId/training" element={<TrainingPage />} />
<Route path="/project/:projectId/export" element={<ExportPage />} />
<Route path="/project/:projectId/test" element={<TestPage />} />
</Route>
</Routes>
</BrowserRouter>
)
}AppShell function · typescript · L4-L18 (15 LOC)frontend/src/components/layout/AppShell.tsx
export function AppShell() {
return (
<div className="min-h-screen bg-[hsl(var(--background))]">
<Sidebar />
<main
className="min-h-screen"
style={{ marginLeft: 'var(--sidebar-width)' }}
>
<div className="p-8 max-w-5xl mx-auto">
<Outlet />
</div>
</main>
</div>
)
}Sidebar function · typescript · L24-L109 (86 LOC)frontend/src/components/layout/Sidebar.tsx
export function Sidebar() {
const location = useLocation()
const { projectId } = useParams()
return (
<aside
className="fixed left-0 top-0 h-full flex flex-col glass border-r border-[hsl(var(--border)/.5)]"
style={{ width: 'var(--sidebar-width)', zIndex: 50 }}
>
{/* Logo */}
<div className="p-5">
<Link to="/" className="flex items-center gap-3 no-underline group">
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-[hsl(var(--primary))] to-[hsl(160_71%_40%)] flex items-center justify-center shadow-lg shadow-[hsl(var(--primary)/.2)] group-hover:shadow-[hsl(var(--primary)/.4)] transition-smooth">
<AudioWaveform size={18} className="text-white" />
</div>
<div>
<h1 className="text-sm font-bold text-[hsl(var(--foreground))]">Piper Trainer</h1>
<p className="text-[10px] text-[hsl(var(--muted-foreground))] tracking-wider uppercase">Master v0.1</p>
</div>
</LRepobility — same analyzer, your code, free for public repos · /scan/
GpuMonitor function · typescript · L4-L88 (85 LOC)frontend/src/components/training/GpuMonitor.tsx
export function GpuMonitor() {
const gpu = useGpuStatus()
if (!gpu) {
return (
<div className="rounded-2xl border border-[hsl(var(--border))] glass p-5">
<div className="flex items-center gap-2 text-[hsl(var(--muted-foreground))]">
<Cpu size={16} className="animate-pulse" />
<span className="text-sm">GPU...</span>
</div>
</div>
)
}
if (!gpu.available) {
return (
<div className="rounded-2xl border border-red-500/20 bg-red-500/5 p-5">
<div className="flex items-center gap-2 text-red-400">
<Cpu size={16} />
<span className="text-sm">{gpu.error}</span>
</div>
</div>
)
}
const vramPercent = gpu.vram_total_mb
? Math.round((gpu.vram_used_mb! / gpu.vram_total_mb) * 100)
: 0
const barColor = vramPercent > 90 ? 'from-red-500 to-red-400' : vramPercent > 70 ? 'from-yellow-500 to-orange-400' : 'from-[hsl(var(--primary))] to-[hsl(160_71%_55%)]'
return (
<diuseGpuStatus function · typescript · L5-L29 (25 LOC)frontend/src/hooks/useGpuStatus.ts
export function useGpuStatus(intervalMs = 5000) {
const [gpu, setGpu] = useState<GpuStatus | null>(null)
useEffect(() => {
let active = true
const fetch = async () => {
try {
const status = await systemApi.gpuStatus()
if (active) setGpu(status)
} catch {
if (active) setGpu({ available: false, error: 'Не вдалося отримати стан GPU' })
}
}
fetch()
const id = setInterval(fetch, intervalMs)
return () => {
active = false
clearInterval(id)
}
}, [intervalMs])
return gpu
}cn function · typescript · L4-L6 (3 LOC)frontend/src/lib/utils.ts
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}formatDuration function · typescript · L8-L14 (7 LOC)frontend/src/lib/utils.ts
export function formatDuration(seconds: number): string {
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = Math.floor(seconds % 60)
if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
return `${m}:${s.toString().padStart(2, '0')}`
}formatBytes function · typescript · L16-L22 (7 LOC)frontend/src/lib/utils.ts
export function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`
}formatDate function · typescript · L24-L32 (9 LOC)frontend/src/lib/utils.ts
export function formatDate(date: string): string {
return new Date(date).toLocaleDateString('uk-UA', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
}DashboardPage function · typescript · L9-L170 (162 LOC)frontend/src/pages/DashboardPage.tsx
export function DashboardPage() {
const { projects, loading, error, fetchProjects, createProject, deleteProject } =
useProjectStore()
const navigate = useNavigate()
const [showCreate, setShowCreate] = useState(false)
const [newName, setNewName] = useState('')
const [newLang, setNewLang] = useState('uk')
const [creating, setCreating] = useState(false)
useEffect(() => {
fetchProjects()
}, [fetchProjects])
const handleCreate = async () => {
if (!newName.trim()) return
setCreating(true)
try {
const project = await createProject({ name: newName.trim(), language: newLang })
setNewName('')
setShowCreate(false)
navigate(`/project/${project.id}/download`)
} catch { } finally { setCreating(false) }
}
const handleDelete = async (e: React.MouseEvent, id: string, name: string) => {
e.preventDefault()
e.stopPropagation()
if (!confirm(`Видалити проєкт "${name}"? Всі дані буде втрачено.`)) return
await deleteProjeDatasetPage function · typescript · L8-L227 (220 LOC)frontend/src/pages/DatasetPage.tsx
export function DatasetPage() {
const { projectId } = useParams<{ projectId: string }>()
const [datasets, setDatasets] = useState<DatasetInfo[]>([])
const [stats, setStats] = useState<DatasetStats | null>(null)
const [issues, setIssues] = useState<ValidationIssue[]>([])
const [preview, setPreview] = useState<CsvRow[]>([])
const [previewTotal, setPreviewTotal] = useState(0)
const [loading, setLoading] = useState(true)
const [preparing, setPreparing] = useState(false)
const [minDur, setMinDur] = useState(1.0)
const [maxDur, setMaxDur] = useState(15.0)
const [sampleRate, setSampleRate] = useState(22050)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (projectId) loadData()
}, [projectId])
const loadData = async () => {
if (!projectId) return
try {
const ds = await datasetsApi.list(projectId)
setDatasets(ds)
if (ds.length > 0) {
await loadDatasetDetails(ds[0].id)
}
} catch {
} fAll rows scored by the Repobility analyzer (https://repobility.com)
DownloadPage function · typescript · L8-L151 (144 LOC)frontend/src/pages/DownloadPage.tsx
export function DownloadPage() {
const { projectId } = useParams<{ projectId: string }>()
const [url, setUrl] = useState('')
const [downloading, setDownloading] = useState(false)
const [uploading, setUploading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [files, setFiles] = useState<AudioFile[]>([])
const [loading, setLoading] = useState(true)
const [playingId, setPlayingId] = useState<string | null>(null)
const audioRef = useRef<HTMLAudioElement | null>(null)
const fileInputRef = useRef<HTMLInputElement | null>(null)
useEffect(() => { if (projectId) loadFiles() }, [projectId])
const loadFiles = async () => {
if (!projectId) return
try { setFiles(await youtubeApi.listFiles(projectId)) } catch {} finally { setLoading(false) }
}
const handleDownload = async () => {
if (!projectId || !url.trim()) return
setDownloading(true); setError(null)
try {
const file = await youtubeApi.download(projectId, urExportPage function · typescript · L10-L140 (131 LOC)frontend/src/pages/ExportPage.tsx
export function ExportPage() {
const { projectId } = useParams<{ projectId: string }>()
const [checkpoints, setCheckpoints] = useState<CheckpointInfo[]>([])
const [models, setModels] = useState<ExportedModel[]>([])
const [selectedCkpt, setSelectedCkpt] = useState('')
const [exporting, setExporting] = useState(false)
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
if (projectId) loadData()
}, [projectId])
const loadData = async () => {
if (!projectId) return
try {
const [ckpts, mdls] = await Promise.all([
trainingApi.checkpoints(projectId),
modelsApi.list(projectId),
])
setCheckpoints(ckpts)
setModels(mdls)
if (ckpts.length > 0) setSelectedCkpt(ckpts[0].path)
} catch {
} finally {
setLoading(false)
}
}
const handleExport = async () => {
if (!projectId || !selectedCkpt) return
setExporting(true)
setError(TestPage function · typescript · L8-L188 (181 LOC)frontend/src/pages/TestPage.tsx
export function TestPage() {
const { projectId } = useParams<{ projectId: string }>()
const [models, setModels] = useState<ExportedModel[]>([])
const [selectedModel, setSelectedModel] = useState('')
const [text, setText] = useState('Привіт, це тестове повідомлення для перевірки якості голосової моделі.')
const [lengthScale, setLengthScale] = useState(1.0)
const [noiseScale, setNoiseScale] = useState(0.667)
const [noiseW, setNoiseW] = useState(0.8)
const [synthesizing, setSynthesizing] = useState(false)
const [error, setError] = useState<string | null>(null)
const [audioUrl, setAudioUrl] = useState<string | null>(null)
const [history, setHistory] = useState<{ text: string; url: string }[]>([])
const [loading, setLoading] = useState(true)
const audioRef = useRef<HTMLAudioElement | null>(null)
useEffect(() => {
if (projectId) loadData()
return () => { history.forEach((h) => URL.revokeObjectURL(h.url)) }
}, [projectId])
const loadData = async () =>TrainingPage function · typescript · L12-L330 (319 LOC)frontend/src/pages/TrainingPage.tsx
export function TrainingPage() {
const { projectId } = useParams<{ projectId: string }>()
const [datasets, setDatasets] = useState<DatasetInfo[]>([])
const [selectedDataset, setSelectedDataset] = useState<string>('')
const [status, setStatus] = useState<TrainingStatus | null>(null)
const [checkpoints, setCheckpoints] = useState<CheckpointInfo[]>([])
const [metricsHistory, setMetricsHistory] = useState<{ epoch: number; loss_g?: number; loss_d?: number }[]>([])
const [loading, setLoading] = useState(true)
const [starting, setStarting] = useState(false)
const [error, setError] = useState<string | null>(null)
const pollRef = useRef<number | null>(null)
const logRef = useRef<HTMLDivElement>(null)
// Config
const [batchSize, setBatchSize] = useState(4)
const [maxEpochs, setMaxEpochs] = useState(10000)
const [precision, setPrecision] = useState('32')
const [accumGrad, setAccumGrad] = useState(8)
const [mode, setMode] = useState<'scratch' | 'finetune'>('scratTranscriptionPage function · typescript · L21-L361 (341 LOC)frontend/src/pages/TranscriptionPage.tsx
export function TranscriptionPage() {
const { projectId } = useParams<{ projectId: string }>()
const [audioFiles, setAudioFiles] = useState<AudioFile[]>([])
const [selectedFile, setSelectedFile] = useState<string | null>(null)
const [segments, setSegments] = useState<Segment[]>([])
const [loading, setLoading] = useState(true)
const [transcribing, setTranscribing] = useState(false)
const [modelSize, setModelSize] = useState('small')
const [editingId, setEditingId] = useState<string | null>(null)
const [editText, setEditText] = useState('')
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
const [playingId, setPlayingId] = useState<string | null>(null)
const audioRef = useRef<HTMLAudioElement | null>(null)
useEffect(() => {
if (projectId) loadData()
}, [projectId])
const loadData = async () => {
if (!projectId) return
try {
const [files, segs] = await Promise.all([
youtubeApi.listFiles(projectId),
tran‹ prevpage 4 / 4