Prepare feature branch for PR

This commit is contained in:
Omer Celik
2026-02-22 21:29:55 +00:00
parent 162cf4fb84
commit 831a50cf61
10 changed files with 1331 additions and 11 deletions
File diff suppressed because it is too large Load Diff
+7 -6
View File
@@ -1,5 +1,5 @@
import { Link, useMatchRoute } from '@tanstack/react-router';
import { Box, BookOpen, Loader2, Mic, Server, Speaker, Volume2 } from 'lucide-react';
import { Link, useMatchRoute, useRouterState } from '@tanstack/react-router';
import { BookOpen, BookText, Box, Loader2, Mic, Server, Speaker, Volume2 } from 'lucide-react';
import voiceboxLogo from '@/assets/voicebox-logo.png';
import { cn } from '@/lib/utils/cn';
import { useGenerationStore } from '@/stores/generationStore';
@@ -12,6 +12,7 @@ interface SidebarProps {
const tabs = [
{ id: 'main', path: '/', icon: Volume2, label: 'Generate' },
{ id: 'stories', path: '/stories', icon: BookOpen, label: 'Stories' },
{ id: 'audiobook', path: '/audiobook', icon: BookText, label: 'Audiobook' },
{ id: 'voices', path: '/voices', icon: Mic, label: 'Voices' },
{ id: 'audio', path: '/audio', icon: Speaker, label: 'Audio' },
{ id: 'models', path: '/models', icon: Box, label: 'Models' },
@@ -23,6 +24,9 @@ export function Sidebar({ isMacOS }: SidebarProps) {
const audioUrl = usePlayerStore((state) => state.audioUrl);
const isPlayerVisible = !!audioUrl;
const matchRoute = useMatchRoute();
const pathname = useRouterState({
select: (state) => state.location.pathname,
});
return (
<div
@@ -41,10 +45,7 @@ export function Sidebar({ isMacOS }: SidebarProps) {
{tabs.map((tab) => {
const Icon = tab.icon;
// For index route, use exact match; for others, use default matching
const isActive =
tab.path === '/'
? matchRoute({ to: '/', exact: true })
: matchRoute({ to: tab.path });
const isActive = tab.path === '/' ? pathname === '/' : matchRoute({ to: tab.path });
return (
<Link
+26
View File
@@ -125,6 +125,32 @@
text-orientation: mixed;
letter-spacing: 0.1em;
}
.scrollbar-visible {
scrollbar-width: thin;
-ms-overflow-style: auto;
scrollbar-color: #d8ab4f #2b2b2b;
}
.scrollbar-visible::-webkit-scrollbar {
display: block;
width: 10px;
height: 10px;
}
.scrollbar-visible::-webkit-scrollbar-track {
background: #2b2b2b;
}
.scrollbar-visible::-webkit-scrollbar-thumb {
background: #d8ab4f;
border-radius: 9999px;
border: 2px solid #131313;
}
.scrollbar-visible::-webkit-scrollbar-thumb:hover {
background: #e2b85e;
}
}
@keyframes fadeInScale {
+2 -1
View File
@@ -34,6 +34,7 @@ export interface GenerationRequest {
language: LanguageCode;
seed?: number;
model_size?: '1.7B' | '0.6B';
instruct?: string;
}
export interface GenerationResponse {
@@ -96,7 +97,7 @@ export interface ModelStatus {
model_name: string;
display_name: string;
downloaded: boolean;
downloading: boolean; // True if download is in progress
downloading: boolean; // True if download is in progress
size_mb?: number;
loaded: boolean;
}
+99
View File
@@ -0,0 +1,99 @@
export interface TextChunk {
id: string;
text: string;
charCount: number;
wordCount: number;
}
function normalizeText(text: string): string {
return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
}
function splitParagraphIntoSentences(paragraph: string): string[] {
const trimmed = paragraph.trim();
if (!trimmed) {
return [];
}
const matches = trimmed.match(/[^.!?]+[.!?]+(?:["')\]]+)?|[^.!?]+$/g);
if (!matches || matches.length === 0) {
return [trimmed];
}
return matches.map((sentence) => sentence.trim()).filter(Boolean);
}
export function chunkText(
rawText: string,
targetChunkSize: number,
maxChunkSize: number,
): TextChunk[] {
const text = normalizeText(rawText);
if (!text) {
return [];
}
const safeTarget = Math.max(200, Math.min(targetChunkSize, maxChunkSize));
const paragraphs = text
.split(/\n{2,}/)
.map((paragraph) => paragraph.trim())
.filter(Boolean);
const chunks: string[] = [];
let current = '';
const pushCurrent = () => {
const normalized = current.trim();
if (!normalized) {
return;
}
chunks.push(normalized);
current = '';
};
for (const paragraph of paragraphs) {
const sentences = splitParagraphIntoSentences(paragraph);
for (const sentence of sentences) {
// Keep sentence integrity. If one sentence exceeds maxChunkSize,
// keep it as a single oversized chunk and let UI ask for manual edit.
if (sentence.length > maxChunkSize) {
pushCurrent();
chunks.push(sentence);
continue;
}
if (!current) {
current = sentence;
continue;
}
const candidate = `${current} ${sentence}`;
if (candidate.length <= safeTarget) {
current = candidate;
continue;
}
if (candidate.length <= maxChunkSize && current.length < Math.floor(safeTarget * 0.75)) {
current = candidate;
continue;
}
pushCurrent();
current = sentence;
}
if (current.length >= Math.floor(safeTarget * 0.8)) {
pushCurrent();
}
}
pushCurrent();
return chunks.map((chunkTextValue, index) => ({
id: `chunk-${index + 1}`,
text: chunkTextValue,
charCount: chunkTextValue.length,
wordCount: chunkTextValue.split(/\s+/).filter(Boolean).length,
}));
}
+10
View File
@@ -1,5 +1,6 @@
import { createRootRoute, createRoute, createRouter, Outlet } from '@tanstack/react-router';
import { AppFrame } from '@/components/AppFrame/AppFrame';
import { AudiobookTab } from '@/components/AudiobookTab/AudiobookTab';
import { AudioTab } from '@/components/AudioTab/AudioTab';
import { MainEditor } from '@/components/MainEditor/MainEditor';
import { ModelsTab } from '@/components/ModelsTab/ModelsTab';
@@ -10,6 +11,7 @@ import { Toaster } from '@/components/ui/toaster';
import { VoicesTab } from '@/components/VoicesTab/VoicesTab';
import { useModelDownloadToast } from '@/lib/hooks/useModelDownloadToast';
import { MODEL_DISPLAY_NAMES, useRestoreActiveTasks } from '@/lib/hooks/useRestoreActiveTasks';
// Simple platform check that works in both web and Tauri
const isMacOS = () => navigator.platform.toLowerCase().includes('mac');
@@ -86,6 +88,13 @@ const storiesRoute = createRoute({
component: StoriesTab,
});
// Audiobook route
const audiobookRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/audiobook',
component: AudiobookTab,
});
// Voices route
const voicesRoute = createRoute({
getParentRoute: () => rootRoute,
@@ -118,6 +127,7 @@ const serverRoute = createRoute({
const routeTree = rootRoute.addChildren([
indexRoute,
storiesRoute,
audiobookRoute,
voicesRoute,
audioRoute,
modelsRoute,