WIP: Add Gradio interface for Kokoro TTS application with input, model, and output components

This commit is contained in:
remsky 2025-01-01 17:34:01 -07:00
parent a672fbc798
commit 1163beae3a
14 changed files with 712 additions and 0 deletions

15
ui/Dockerfile Normal file
View file

@ -0,0 +1,15 @@
FROM python:3.10-slim
WORKDIR /app/ui
# Install dependencies
RUN pip install gradio==5.9.1 requests==2.32.3
# Create necessary directories
RUN mkdir -p data/inputs data/outputs
# Copy the application files
COPY . .
# Run the Gradio app
CMD ["python", "app.py"]

BIN
ui/GUIBanner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

9
ui/app.py Normal file
View file

@ -0,0 +1,9 @@
from lib.interface import create_interface
if __name__ == "__main__":
demo = create_interface()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
show_error=True
)

View file

@ -0,0 +1,151 @@
The Time Traveller (for so it will be convenient to speak of him) was expounding a recondite matter to us. His pale grey eyes shone and twinkled, and his usually pale face was flushed and animated. The fire burnt brightly, and the soft radiance of the incandescent lights in the lilies of silver caught the bubbles that flashed and passed in our glasses. Our chairs, being his patents, embraced and caressed us rather than submitted to be sat upon, and there was that luxurious after-dinner atmosphere, when thought runs gracefully free of the trammels of precision. And he put it to us in this way—marking the points with a lean forefinger—as we sat and lazily admired his earnestness over this new paradox (as we thought it) and his fecundity.
“You must follow me carefully. I shall have to controvert one or two ideas that are almost universally accepted. The geometry, for instance, they taught you at school is founded on a misconception.”
“Is not that rather a large thing to expect us to begin upon?” said Filby, an argumentative person with red hair.
“I do not mean to ask you to accept anything without reasonable ground for it. You will soon admit as much as I need from you. You know of course that a mathematical line, a line of thickness nil, has no real existence. They taught you that? Neither has a mathematical plane. These things are mere abstractions.”
“That is all right,” said the Psychologist.
“Nor, having only length, breadth, and thickness, can a cube have a real existence.”
“There I object,” said Filby. “Of course a solid body may exist. All real things—”
“So most people think. But wait a moment. Can an instantaneous cube exist?”
“Dont follow you,” said Filby.
“Can a cube that does not last for any time at all, have a real existence?”
Filby became pensive. “Clearly,” the Time Traveller proceeded, “any real body must have extension in four directions: it must have Length, Breadth, Thickness, and—Duration. But through a natural infirmity of the flesh, which I will explain to you in a moment, we incline to overlook this fact. There are really four dimensions, three which we call the three planes of Space, and a fourth, Time. There is, however, a tendency to draw an unreal distinction between the former three dimensions and the latter, because it happens that our consciousness moves intermittently in one direction along the latter from the beginning to the end of our lives.”
“That,” said a very young man, making spasmodic efforts to relight his cigar over the lamp; “that . . . very clear indeed.”
“Now, it is very remarkable that this is so extensively overlooked,” continued the Time Traveller, with a slight accession of cheerfulness. “Really this is what is meant by the Fourth Dimension, though some people who talk about the Fourth Dimension do not know they mean it. It is only another way of looking at Time. There is no difference between Time and any of the three dimensions of Space except that our consciousness moves along it. But some foolish people have got hold of the wrong side of that idea. You have all heard what they have to say about this Fourth Dimension?”
“I have not,” said the Provincial Mayor.
“It is simply this. That Space, as our mathematicians have it, is spoken of as having three dimensions, which one may call Length, Breadth, and Thickness, and is always definable by reference to three planes, each at right angles to the others. But some philosophical people have been asking why three dimensions particularly—why not another direction at right angles to the other three?—and have even tried to construct a Four-Dimensional geometry. Professor Simon Newcomb was expounding this to the New York Mathematical Society only a month or so ago. You know how on a flat surface, which has only two dimensions, we can represent a figure of a three-dimensional solid, and similarly they think that by models of three dimensions they could represent one of four—if they could master the perspective of the thing. See?”
“I think so,” murmured the Provincial Mayor; and, knitting his brows, he lapsed into an introspective state, his lips moving as one who repeats mystic words. “Yes, I think I see it now,” he said after some time, brightening in a quite transitory manner.
“Well, I do not mind telling you I have been at work upon this geometry of Four Dimensions for some time. Some of my results are curious. For instance, here is a portrait of a man at eight years old, another at fifteen, another at seventeen, another at twenty-three, and so on. All these are evidently sections, as it were, Three-Dimensional representations of his Four-Dimensioned being, which is a fixed and unalterable thing.
“Scientific people,” proceeded the Time Traveller, after the pause required for the proper assimilation of this, “know very well that Time is only a kind of Space. Here is a popular scientific diagram, a weather record. This line I trace with my finger shows the movement of the barometer. Yesterday it was so high, yesterday night it fell, then this morning it rose again, and so gently upward to here. Surely the mercury did not trace this line in any of the dimensions of Space generally recognised? But certainly it traced such a line, and that line, therefore, we must conclude, was along the Time-Dimension.”
“But,” said the Medical Man, staring hard at a coal in the fire, “if Time is really only a fourth dimension of Space, why is it, and why has it always been, regarded as something different? And why cannot we move in Time as we move about in the other dimensions of Space?”
The Time Traveller smiled. “Are you so sure we can move freely in Space? Right and left we can go, backward and forward freely enough, and men always have done so. I admit we move freely in two dimensions. But how about up and down? Gravitation limits us there.”
“Not exactly,” said the Medical Man. “There are balloons.”
“But before the balloons, save for spasmodic jumping and the inequalities of the surface, man had no freedom of vertical movement.”
“Still they could move a little up and down,” said the Medical Man.
“Easier, far easier down than up.”
“And you cannot move at all in Time, you cannot get away from the present moment.”
“My dear sir, that is just where you are wrong. That is just where the whole world has gone wrong. We are always getting away from the present moment. Our mental existences, which are immaterial and have no dimensions, are passing along the Time-Dimension with a uniform velocity from the cradle to the grave. Just as we should travel down if we began our existence fifty miles above the earths surface.”
“But the great difficulty is this,” interrupted the Psychologist. You can move about in all directions of Space, but you cannot move about in Time.”
“That is the germ of my great discovery. But you are wrong to say that we cannot move about in Time. For instance, if I am recalling an incident very vividly I go back to the instant of its occurrence: I become absent-minded, as you say. I jump back for a moment. Of course we have no means of staying back for any length of Time, any more than a savage or an animal has of staying six feet above the ground. But a civilised man is better off than the savage in this respect. He can go up against gravitation in a balloon, and why should he not hope that ultimately he may be able to stop or accelerate his drift along the Time-Dimension, or even turn about and travel the other way?”
“Oh, this,” began Filby, “is all—”
“Why not?” said the Time Traveller.
“Its against reason,” said Filby.
“What reason?” said the Time Traveller.
“You can show black is white by argument,” said Filby, “but you will never convince me.”
“Possibly not,” said the Time Traveller. “But now you begin to see the object of my investigations into the geometry of Four Dimensions. Long ago I had a vague inkling of a machine—”
“To travel through Time!” exclaimed the Very Young Man.
“That shall travel indifferently in any direction of Space and Time, as the driver determines.”
Filby contented himself with laughter.
“But I have experimental verification,” said the Time Traveller.
“It would be remarkably convenient for the historian,” the Psychologist suggested. “One might travel back and verify the accepted account of the Battle of Hastings, for instance!”
“Dont you think you would attract attention?” said the Medical Man. “Our ancestors had no great tolerance for anachronisms.”
“One might get ones Greek from the very lips of Homer and Plato,” the Very Young Man thought.
“In which case they would certainly plough you for the Little-go. The German scholars have improved Greek so much.”
“Then there is the future,” said the Very Young Man. “Just think! One might invest all ones money, leave it to accumulate at interest, and hurry on ahead!”
“To discover a society,” said I, “erected on a strictly communistic basis.”
“Of all the wild extravagant theories!” began the Psychologist.
“Yes, so it seemed to me, and so I never talked of it until—”
“Experimental verification!” cried I. “You are going to verify that?”
“The experiment!” cried Filby, who was getting brain-weary.
“Lets see your experiment anyhow,” said the Psychologist, “though its all humbug, you know.”
The Time Traveller smiled round at us. Then, still smiling faintly, and with his hands deep in his trousers pockets, he walked slowly out of the room, and we heard his slippers shuffling down the long passage to his laboratory.
The Psychologist looked at us. “I wonder what hes got?”
“Some sleight-of-hand trick or other,” said the Medical Man, and Filby tried to tell us about a conjuror he had seen at Burslem, but before he had finished his preface the Time Traveller came back, and Filbys anecdote collapsed.
II.
The Machine
The thing the Time Traveller held in his hand was a glittering metallic framework, scarcely larger than a small clock, and very delicately made. There was ivory in it, and some transparent crystalline substance. And now I must be explicit, for this that follows—unless his explanation is to be accepted—is an absolutely unaccountable thing. He took one of the small octagonal tables that were scattered about the room, and set it in front of the fire, with two legs on the hearthrug. On this table he placed the mechanism. Then he drew up a chair, and sat down. The only other object on the table was a small shaded lamp, the bright light of which fell upon the model. There were also perhaps a dozen candles about, two in brass candlesticks upon the mantel and several in sconces, so that the room was brilliantly illuminated. I sat in a low arm-chair nearest the fire, and I drew this forward so as to be almost between the Time Traveller and the fireplace. Filby sat behind him, looking over his shoulder. The Medical Man and the Provincial Mayor watched him in profile from the right, the Psychologist from the left. The Very Young Man stood behind the Psychologist. We were all on the alert. It appears incredible to me that any kind of trick, however subtly conceived and however adroitly done, could have been played upon us under these conditions.
The Time Traveller looked at us, and then at the mechanism. “Well?” said the Psychologist.
“This little affair,” said the Time Traveller, resting his elbows upon the table and pressing his hands together above the apparatus, “is only a model. It is my plan for a machine to travel through time. You will notice that it looks singularly askew, and that there is an odd twinkling appearance about this bar, as though it was in some way unreal.” He pointed to the part with his finger. “Also, here is one little white lever, and here is another.”
The Medical Man got up out of his chair and peered into the thing. “Its beautifully made,” he said.
“It took two years to make,” retorted the Time Traveller. Then, when we had all imitated the action of the Medical Man, he said: “Now I want you clearly to understand that this lever, being pressed over, sends the machine gliding into the future, and this other reverses the motion. This saddle represents the seat of a time traveller. Presently I am going to press the lever, and off the machine will go. It will vanish, pass into future Time, and disappear. Have a good look at the thing. Look at the table too, and satisfy yourselves there is no trickery. I dont want to waste this model, and then be told Im a quack.”
There was a minutes pause perhaps. The Psychologist seemed about to speak to me, but changed his mind. Then the Time Traveller put forth his finger towards the lever. “No,” he said suddenly. “Lend me your hand.” And turning to the Psychologist, he took that individuals hand in his own and told him to put out his forefinger. So that it was the Psychologist himself who sent forth the model Time Machine on its interminable voyage. We all saw the lever turn. I am absolutely certain there was no trickery. There was a breath of wind, and the lamp flame jumped. One of the candles on the mantel was blown out, and the little machine suddenly swung round, became indistinct, was seen as a ghost for a second perhaps, as an eddy of faintly glittering brass and ivory; and it was gone—vanished! Save for the lamp the table was bare.
Everyone was silent for a minute. Then Filby said he was damned.
The Psychologist recovered from his stupor, and suddenly looked under the table. At that the Time Traveller laughed cheerfully. “Well?” he said, with a reminiscence of the Psychologist. Then, getting up, he went to the tobacco jar on the mantel, and with his back to us began to fill his pipe.
We stared at each other. “Look here,” said the Medical Man, “are you in earnest about this? Do you seriously believe that that machine has travelled into time?”
“Certainly,” said the Time Traveller, stooping to light a spill at the fire. Then he turned, lighting his pipe, to look at the Psychologists face. (The Psychologist, to show that he was not unhinged, helped himself to a cigar and tried to light it uncut.) “What is more, I have a big machine nearly finished in there”—he indicated the laboratory—“and when that is put together I mean to have a journey on my own account.”
“You mean to say that that machine has travelled into the future?” said Filby.
“Into the future or the past—I dont, for certain, know which.”
After an interval the Psychologist had an inspiration. “It must have gone into the past if it has gone anywhere,” he said.
“Why?” said the Time Traveller.
“Because I presume that it has not moved in space, and if it travelled into the future it would still be here all this time, since it must have travelled through this time.”
“But,” said I, “If it travelled into the past it would have been visible when we came first into this room; and last Thursday when we were here; and the Thursday before that; and so forth!”
“Serious objections,” remarked the Provincial Mayor, with an air of impartiality, turning towards the Time Traveller.
“Not a bit,” said the Time Traveller, and, to the Psychologist: “You think. You can explain that. Its presentation below the threshold, you know, diluted presentation.”
“Of course,” said the Psychologist, and reassured us. “Thats a simple point of psychology. I should have thought of it. Its plain enough, and helps the paradox delightfully. We cannot see it, nor can we appreciate this machine, any more than we can the spoke of a wheel spinning, or a bullet flying through the air. If it is travelling through time fifty times or a hundred times faster than we are, if it gets through a minute while we get through a second, the impression it creates will of course be only one-fiftieth or one-hundredth of what it would make if it were not travelling in time. Thats plain enough.” He passed his hand through the space in which the machine had been. “You see?” he said, laughing.
We sat and stared at the vacant table for a minute or so. Then the Time Traveller asked us what we thought of it all.
“It sounds plausible enough tonight,” said the Medical Man; “but wait until tomorrow. Wait for the common sense of the morning.”
“Would you like to see the Time Machine itself?” asked the Time Traveller. And therewith, taking the lamp in his hand, he led the way down the long, draughty corridor to his laboratory. I remember vividly the flickering light, his queer, broad head in silhouette, the dance of the shadows, how we all followed him, puzzled but incredulous, and how there in the laboratory we beheld a larger edition of the little mechanism which we had seen vanish from before our eyes. Parts were of nickel, parts of ivory, parts had certainly been filed or sawn out of rock crystal. The thing was generally complete, but the twisted crystalline bars lay unfinished upon the bench beside some
The Time Traveller Returns
I think that at that time none of us quite believed in the Time Machine. The fact is, the Time Traveller was one of those men who are too clever to be believed: you never felt that you saw all round him; you always suspected some subtle reserve, some ingenuity in ambush, behind his lucid frankness. Had Filby shown the model and explained the matter in the Time Travellers words, we should have shown him far less scepticism. For we should have perceived his motives: a pork-butcher could understand Filby. But the Time Traveller had more than a touch of whim among his elements, and we distrusted him. Things that would have made the fame of a less clever man seemed tricks in his hands. It is a mistake to do things too easily. The serious people who took him seriously never felt quite sure of his deportment; they were somehow aware that trusting their reputations for judgment with him was like furnishing a nursery with eggshell china. So I dont think any of us said very much about time travelling in the interval between that Thursday and the next, though its odd potentialities ran, no doubt, in most of our minds: its plausibility, that is, its practical incredibleness, the curious possibilities of anachronism and of utter confusion it suggested. For my own part, I was particularly preoccupied with the trick of the model. That I remember discussing with the Medical Man, whom I met on Friday at the Linnæan. He said he had seen a similar thing at Tübingen, and laid considerable stress on the blowing-out of the candle. But how the trick was done he could not explai

0
ui/lib/__init__.py Normal file
View file

75
ui/lib/api.py Normal file
View file

@ -0,0 +1,75 @@
import requests
from typing import Tuple, List, Optional
import os
import datetime
from .config import API_URL, OUTPUTS_DIR
def check_api_status() -> Tuple[bool, List[str]]:
"""Check TTS service status and get available voices."""
try:
response = requests.get(f"{API_URL}/v1/audio/voices", timeout=5)
response.raise_for_status()
voices = response.json().get("voices", [])
if voices:
return True, voices
print("No voices found in response")
return False, []
except requests.exceptions.Timeout:
print("API request timed out")
return False, []
except requests.exceptions.RequestException as e:
print(f"API request failed: {str(e)}")
return False, []
except Exception as e:
print(f"Unexpected error checking API status: {str(e)}")
return False, []
def text_to_speech(text: str, voice_id: str, format: str, speed: float) -> Optional[str]:
"""Generate speech from text using TTS API."""
if not text.strip():
return None
# Create output filename
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
output_filename = f"output_{timestamp}_voice-{voice_id}_speed-{speed}.{format}"
output_path = os.path.join(OUTPUTS_DIR, output_filename)
try:
response = requests.post(
f"{API_URL}/v1/audio/speech",
json={
"model": "kokoro",
"input": text,
"voice": voice_id,
"response_format": format,
"speed": float(speed)
},
headers={"Content-Type": "application/json"},
timeout=300 # Longer timeout for speech generation
)
response.raise_for_status()
with open(output_path, "wb") as f:
f.write(response.content)
return output_path
except requests.exceptions.Timeout:
print("Speech generation request timed out")
return None
except requests.exceptions.RequestException as e:
print(f"Speech generation request failed: {str(e)}")
return None
except Exception as e:
print(f"Unexpected error generating speech: {str(e)}")
return None
def get_status_html(is_available: bool) -> str:
"""Generate HTML for status indicator."""
color = "green" if is_available else "red"
status = "Available" if is_available else "Unavailable"
return f"""
<div style="display: flex; align-items: center; gap: 8px;">
<div style="width: 12px; height: 12px; border-radius: 50%; background-color: {color};"></div>
<span>TTS Service: {status}</span>
</div>
"""

View file

@ -0,0 +1,5 @@
from .input import create_input_column
from .model import create_model_column
from .output import create_output_column
__all__ = ['create_input_column', 'create_model_column', 'create_output_column']

View file

@ -0,0 +1,46 @@
import gradio as gr
from typing import Tuple
from .. import files
def create_input_column() -> Tuple[gr.Column, dict]:
"""Create the input column with text input and file handling."""
with gr.Column(scale=1) as col:
with gr.Tabs() as tabs:
# Direct Input Tab
with gr.TabItem("Direct Input"):
text_input = gr.Textbox(
label="Text to speak",
placeholder="Enter text here...",
lines=4
)
# File Input Tab
with gr.TabItem("From File"):
# Existing files dropdown
input_files_list = gr.Dropdown(
label="Select Existing File",
choices=files.list_input_files(),
value=None
)
# Simple file upload
file_upload = gr.File(
label="Upload Text File (.txt)",
file_types=[".txt"]
)
file_preview = gr.Textbox(
label="File Content Preview",
interactive=False,
lines=4
)
components = {
"tabs": tabs,
"text_input": text_input,
"file_select": input_files_list,
"file_upload": file_upload,
"file_preview": file_preview
}
return col, components

View file

@ -0,0 +1,53 @@
import gradio as gr
from typing import Tuple, Optional
from .. import api, config
def create_model_column(voice_ids: Optional[list] = None) -> Tuple[gr.Column, dict]:
"""Create the model settings column."""
if voice_ids is None:
voice_ids = []
with gr.Column(scale=1) as col:
gr.Markdown("### Model Settings")
# Status button with embedded status
is_available, _ = api.check_api_status()
status_btn = gr.Button(
f"Checking TTS Service: {'Available' if is_available else 'Not Yet Available'}",
variant="secondary"
)
voice_input = gr.Dropdown(
choices=voice_ids,
label="Voice",
value=voice_ids[0] if voice_ids else None,
interactive=True
)
format_input = gr.Dropdown(
choices=config.AUDIO_FORMATS,
label="Audio Format",
value="mp3"
)
speed_input = gr.Slider(
minimum=0.5,
maximum=2.0,
value=1.0,
step=0.1,
label="Speed"
)
submit_btn = gr.Button(
"Generate Speech",
variant="primary",
size="lg"
)
components = {
"status_btn": status_btn,
"voice": voice_input,
"format": format_input,
"speed": speed_input,
"submit": submit_btn
}
return col, components

View file

@ -0,0 +1,37 @@
import gradio as gr
from typing import Tuple
from .. import files
def create_output_column() -> Tuple[gr.Column, dict]:
"""Create the output column with audio player and file list."""
with gr.Column(scale=1) as col:
gr.Markdown("### Latest Output")
audio_output = gr.Audio(
label="Generated Speech",
type="filepath"
)
gr.Markdown("### Generated Files")
output_files = gr.Dropdown(
label="Previous Outputs",
choices=files.list_output_files(),
value=None,
allow_custom_value=False
)
play_btn = gr.Button("▶️ Play Selected", size="sm")
selected_audio = gr.Audio(
label="Selected Output",
type="filepath",
visible=False
)
components = {
"audio_output": audio_output,
"output_files": output_files,
"play_btn": play_btn,
"selected_audio": selected_audio
}
return col, components

40
ui/lib/config.py Normal file
View file

@ -0,0 +1,40 @@
import os
# API Configuration
API_URL = "http://kokoro-tts:8880"
# File paths
INPUTS_DIR = "/app/ui/data/inputs"
OUTPUTS_DIR = "/app/ui/data/outputs"
# Create directories if they don't exist
os.makedirs(INPUTS_DIR, exist_ok=True)
os.makedirs(OUTPUTS_DIR, exist_ok=True)
# Audio formats
AUDIO_FORMATS = ["mp3", "wav", "opus", "flac"]
# UI Theme
THEME = "monochrome"
CSS = """
.gradio-container {
max-width: 1000px;
margin: auto;
}
.banner-container {
background: transparent !important;
border: none !important;
box-shadow: none !important;
margin-bottom: 2rem;
}
.banner-container img {
width: 100%;
max-width: 600px;
border-radius: 10px;
margin: 20px auto;
display: block;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
"""

87
ui/lib/files.py Normal file
View file

@ -0,0 +1,87 @@
import os
from typing import List, Optional, Tuple
import datetime
from .config import INPUTS_DIR, OUTPUTS_DIR, AUDIO_FORMATS
def list_input_files() -> List[str]:
"""List all input text files."""
return [f for f in os.listdir(INPUTS_DIR) if f.endswith('.txt')]
def list_output_files() -> List[str]:
"""List all output audio files."""
return [os.path.join(OUTPUTS_DIR, f)
for f in os.listdir(OUTPUTS_DIR)
if any(f.endswith(ext) for ext in AUDIO_FORMATS)]
def read_text_file(filename: str) -> str:
"""Read content of a text file."""
if not filename:
return ""
try:
file_path = os.path.join(INPUTS_DIR, filename)
with open(file_path, 'r', encoding='utf-8') as f:
return f.read()
except:
return ""
def save_text(text: str, filename: Optional[str] = None) -> Optional[str]:
"""Save text to a file. Returns the filename if successful."""
if not text.strip():
return None
if filename is None:
# Use input_1.txt, input_2.txt, etc.
base = "input"
counter = 1
while True:
filename = f"{base}_{counter}.txt"
if not os.path.exists(os.path.join(INPUTS_DIR, filename)):
break
counter += 1
else:
# Handle duplicate filenames by adding _1, _2, etc.
base = os.path.splitext(filename)[0]
ext = os.path.splitext(filename)[1] or '.txt'
counter = 1
while os.path.exists(os.path.join(INPUTS_DIR, filename)):
filename = f"{base}_{counter}{ext}"
counter += 1
filepath = os.path.join(INPUTS_DIR, filename)
try:
with open(filepath, "w", encoding="utf-8") as f:
f.write(text)
return filename
except Exception as e:
print(f"Error saving file: {e}")
return None
def process_uploaded_file(file_path: str) -> bool:
"""Save uploaded file to inputs directory. Returns True if successful."""
if not file_path:
return False
try:
filename = os.path.basename(file_path)
if not filename.endswith('.txt'):
return False
# Create target path in inputs directory
target_path = os.path.join(INPUTS_DIR, filename)
# If file exists, add number suffix
base, ext = os.path.splitext(filename)
counter = 1
while os.path.exists(target_path):
new_name = f"{base}_{counter}{ext}"
target_path = os.path.join(INPUTS_DIR, new_name)
counter += 1
# Copy file to inputs directory
import shutil
shutil.copy2(file_path, target_path)
return True
except Exception as e:
print(f"Error saving uploaded file: {e}")
return False

139
ui/lib/handlers.py Normal file
View file

@ -0,0 +1,139 @@
import gradio as gr
import os
import shutil
from . import api, files
def setup_event_handlers(components: dict):
"""Set up all event handlers for the UI components."""
def refresh_status():
is_available, voices = api.check_api_status()
status = "Available" if is_available else "Unavailable"
btn_text = f"🔄 TTS Service: {status}"
if is_available and voices:
return {
components["model"]["status_btn"]: gr.update(value=btn_text),
components["model"]["voice"]: gr.update(choices=voices, value=voices[0] if voices else None)
}
return {
components["model"]["status_btn"]: gr.update(value=btn_text),
components["model"]["voice"]: gr.update(choices=[], value=None)
}
def handle_file_select(filename):
if filename:
try:
text = files.read_text_file(filename)
if text:
preview = text[:200] + "..." if len(text) > 200 else text
return gr.update(value=preview)
except Exception as e:
print(f"Error reading file: {e}")
return gr.update(value="")
def handle_file_upload(file):
if file is None:
return gr.update(choices=files.list_input_files())
try:
# Copy file to inputs directory
filename = os.path.basename(file.name)
target_path = os.path.join(files.INPUTS_DIR, filename)
# Handle duplicate filenames
base, ext = os.path.splitext(filename)
counter = 1
while os.path.exists(target_path):
new_name = f"{base}_{counter}{ext}"
target_path = os.path.join(files.INPUTS_DIR, new_name)
counter += 1
shutil.copy2(file.name, target_path)
except Exception as e:
print(f"Error uploading file: {e}")
return gr.update(choices=files.list_input_files())
def generate_speech(text, selected_file, voice, format, speed):
is_available, _ = api.check_api_status()
if not is_available:
gr.Warning("TTS Service is currently unavailable")
return {
components["output"]["audio_output"]: None,
components["output"]["output_files"]: gr.update(choices=files.list_output_files())
}
# Use text input if provided, otherwise use file content
if text and text.strip():
files.save_text(text)
final_text = text
elif selected_file:
final_text = files.read_text_file(selected_file)
else:
gr.Warning("Please enter text or select a file")
return {
components["output"]["audio_output"]: None,
components["output"]["output_files"]: gr.update(choices=files.list_output_files())
}
result = api.text_to_speech(final_text, voice, format, speed)
if result is None:
gr.Warning("Failed to generate speech. Please try again.")
return {
components["output"]["audio_output"]: None,
components["output"]["output_files"]: gr.update(choices=files.list_output_files())
}
return {
components["output"]["audio_output"]: result,
components["output"]["output_files"]: gr.update(choices=files.list_output_files(), value=os.path.basename(result))
}
def play_selected(file_path):
if file_path and os.path.exists(file_path):
return gr.update(value=file_path, visible=True)
return gr.update(visible=False)
# Connect event handlers
components["model"]["status_btn"].click(
fn=refresh_status,
outputs=[
components["model"]["status_btn"],
components["model"]["voice"]
]
)
components["input"]["file_select"].change(
fn=handle_file_select,
inputs=[components["input"]["file_select"]],
outputs=[components["input"]["file_preview"]]
)
components["input"]["file_upload"].upload(
fn=handle_file_upload,
inputs=[components["input"]["file_upload"]],
outputs=[components["input"]["file_select"]]
)
components["output"]["play_btn"].click(
fn=play_selected,
inputs=[components["output"]["output_files"]],
outputs=[components["output"]["selected_audio"]]
)
components["model"]["submit"].click(
fn=generate_speech,
inputs=[
components["input"]["text_input"],
components["input"]["file_select"],
components["model"]["voice"],
components["model"]["format"],
components["model"]["speed"]
],
outputs=[
components["output"]["audio_output"],
components["output"]["output_files"]
]
)

55
ui/lib/interface.py Normal file
View file

@ -0,0 +1,55 @@
import gradio as gr
from . import api
from .components import create_input_column, create_model_column, create_output_column
from .handlers import setup_event_handlers
def create_interface():
"""Create the main Gradio interface."""
# Initial status check
is_available, available_voices = api.check_api_status()
with gr.Blocks(
title="Kokoro TTS Demo",
theme=gr.themes.Monochrome()
) as demo:
gr.HTML(value='<div style="display: flex; gap: 0;">'
'<a href="https://huggingface.co/hexgrad/Kokoro-82M" target="_blank" style="color: #2196F3; text-decoration: none; margin: 2px; border: 1px solid #2196F3; padding: 4px 8px; height: 24px; box-sizing: border-box; display: inline-flex; align-items: center;">Kokoro-82M HF Repo</a>'
'<a href="https://github.com/remsky/Kokoro-FastAPI" target="_blank" style="color: #2196F3; text-decoration: none; margin: 2px; border: 1px solid #2196F3; padding: 4px 8px; height: 24px; box-sizing: border-box; display: inline-flex; align-items: center;">Kokoro-FastAPI Repo</a>'
'</div>', show_label=False)
# Main interface
with gr.Row():
# Create columns
input_col, input_components = create_input_column()
model_col, model_components = create_model_column(available_voices) # Pass initial voices
output_col, output_components = create_output_column()
# Collect all components
components = {
"input": input_components,
"model": model_components,
"output": output_components
}
# Set up event handlers
setup_event_handlers(components)
# Add periodic status check with Timer
def update_status():
is_available, voices = api.check_api_status()
status = "Available" if is_available else "Unavailable"
return {
components["model"]["status_btn"]: gr.update(value=f"🔄 TTS Service: {status}"),
components["model"]["voice"]: gr.update(choices=voices, value=voices[0] if voices else None)
}
timer = gr.Timer(10, active=True) # Check every 10 seconds
timer.tick(
fn=update_status,
outputs=[
components["model"]["status_btn"],
components["model"]["voice"]
]
)
return demo