mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-11 04:16:55 +00:00
Modal test frontend can hit /speak or /advanced_tts
This commit is contained in:
1 parent
ac1a9281d4
commit
3ce772cd80
10 files changed
+275
-7
No files matched your search
@@ -17,6 +17,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "PORT=12345 react-scripts start",
|
||||
"start7000": "PORT=7000 react-scripts start",
|
||||
"start8000": "PORT=8000 react-scripts start",
|
||||
"start8080": "PORT=8080 react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
|
||||
@@ -28,7 +28,7 @@ class TtsResponse {
|
||||
|
||||
let makeTtsRequest = (sentence: string): Promise<TtsResponse> => {
|
||||
const request = new TtsRequest(sentence);
|
||||
const url = `/tts`;
|
||||
const url = `http://localhost:12345/advanced_tts`;
|
||||
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import React from 'react';
|
||||
import Howl from 'howler';
|
||||
import TextInput from './TextInput';
|
||||
import TrackList from './TrackList';
|
||||
import { ModelPickerDropdownComponent } from './model_picker/ModelPickerDropdownComponent';
|
||||
import { SpeakerModeComponent } from './modes/speaker_mode/SpeakerModeComponent';
|
||||
|
||||
enum Mode {
|
||||
SPEAKER,
|
||||
ADVANCED,
|
||||
}
|
||||
|
||||
interface Props {
|
||||
}
|
||||
|
||||
interface State {
|
||||
utterances: Array<TextAudioPair>
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
class TextAudioPair {
|
||||
@@ -26,7 +31,10 @@ class MainComponent extends React.Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { utterances: new Array() };
|
||||
this.state = {
|
||||
utterances: new Array(),
|
||||
mode: Mode.ADVANCED,
|
||||
};
|
||||
}
|
||||
|
||||
/** Add a new audio track. */
|
||||
@@ -38,7 +46,40 @@ class MainComponent extends React.Component<Props, State> {
|
||||
this.setState({utterances: utterances});
|
||||
}
|
||||
|
||||
changeMode = () => {
|
||||
let nextMode;
|
||||
switch (this.state.mode) {
|
||||
case Mode.ADVANCED:
|
||||
nextMode = Mode.SPEAKER;
|
||||
break;
|
||||
|
||||
case Mode.SPEAKER:
|
||||
nextMode = Mode.ADVANCED;
|
||||
break;
|
||||
}
|
||||
this.setState({
|
||||
mode: nextMode
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
let component;
|
||||
if (this.state.mode == Mode.ADVANCED) {
|
||||
component = this.renderAdvancedMode();
|
||||
} else {
|
||||
component = this.renderSpeakerMode();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.changeMode}>Change Mode</button>
|
||||
<hr />
|
||||
{component}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
public renderAdvancedMode() {
|
||||
return (
|
||||
<div>
|
||||
<TextInput appendUtteranceCallback={this.appendUtterance} />
|
||||
@@ -46,6 +87,14 @@ class MainComponent extends React.Component<Props, State> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderSpeakerMode() {
|
||||
return (
|
||||
<div>
|
||||
<SpeakerModeComponent></SpeakerModeComponent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {TextAudioPair, MainComponent};
|
||||
@@ -70,7 +70,7 @@ class TextInput extends React.Component<Props, State> {
|
||||
request.arpabet_tacotron_model = this.state.arpabet_tacotron_model || "";
|
||||
request.melgan_model = this.state.melgan_model || "";
|
||||
|
||||
const url = '/tts';
|
||||
const url = 'http://localhost:12345/advanced_tts';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@@ -26,7 +26,7 @@ class ModelPickerDropdownComponent extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
public loadModels() {
|
||||
const url = '/models';
|
||||
const url = 'http://localhost:12345/models';
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import Howl from 'howler';
|
||||
|
||||
interface Props {
|
||||
speaker?: String,
|
||||
}
|
||||
|
||||
interface State {
|
||||
text?: String,
|
||||
howl?: Howl,
|
||||
}
|
||||
|
||||
/// Requests to the "speak" endpoint.
|
||||
class SpeakRequest {
|
||||
text: String
|
||||
speaker: String
|
||||
|
||||
constructor(text: String, speaker: String) {
|
||||
this.text = text;
|
||||
this.speaker = speaker;
|
||||
}
|
||||
}
|
||||
|
||||
class SpeakerAudioForm extends React.Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
text: undefined,
|
||||
howl: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// OnMount events. An async CTOR, essentially.
|
||||
}
|
||||
|
||||
handleTextChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
const text = (ev.target as HTMLInputElement).value;
|
||||
this.setState({text: text});
|
||||
}
|
||||
|
||||
makeRequest = (ev: React.FormEvent<HTMLFormElement>) => {
|
||||
console.log("Form Submit");
|
||||
|
||||
if (!this.state.text) {
|
||||
return;
|
||||
}
|
||||
|
||||
let request = new SpeakRequest(this.state.text, this.props.speaker!);
|
||||
|
||||
const url = 'http://localhost:12345/speak';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
})
|
||||
.then(res => res.blob())
|
||||
.then(blob => {
|
||||
console.log(blob);
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
console.log(url);
|
||||
|
||||
const sound = new Howl.Howl({
|
||||
src: [url],
|
||||
format: 'wav',
|
||||
});
|
||||
|
||||
this.setState({howl: sound});
|
||||
sound.play();
|
||||
|
||||
(window as any).sound = sound;
|
||||
});
|
||||
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<form onSubmit={this.makeRequest}>
|
||||
<input onChange={this.handleTextChange} />
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SpeakerAudioForm;
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
import React from 'react';
|
||||
import { SpeakerResponse, SpeakerDetails } from './SpeakerResponse';
|
||||
|
||||
interface Props {
|
||||
changeCallback: (ev: React.FormEvent<HTMLSelectElement>) => void,
|
||||
speakers: SpeakerDetails[]
|
||||
}
|
||||
|
||||
interface State {
|
||||
}
|
||||
|
||||
class SpeakerDropdownComponent extends React.Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div>
|
||||
<strong>Select Speaker</strong>
|
||||
<br />
|
||||
<select onChange={this.props.changeCallback}>
|
||||
{this.props.speakers.map(function(speaker: SpeakerDetails) {
|
||||
let name : string = speaker.name || "unknown";
|
||||
let slug : string = speaker.slug || "unknown";
|
||||
return (<option value={slug}>{name}</option>)
|
||||
})};
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {SpeakerDropdownComponent};
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import { SpeakerDetails, SpeakerResponse } from './SpeakerResponse';
|
||||
import { SpeakerDropdownComponent } from './SpeakerDropdownComponent';
|
||||
import SpeakerAudioForm from './SpeakerAudioForm';
|
||||
|
||||
interface Props {
|
||||
}
|
||||
|
||||
interface State {
|
||||
speaker_response?: SpeakerResponse,
|
||||
speaker_slug?: string,
|
||||
}
|
||||
|
||||
class SpeakerModeComponent extends React.Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
speaker_response: undefined,
|
||||
speaker_slug: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadSpeakers();
|
||||
}
|
||||
|
||||
public loadSpeakers() {
|
||||
const url = 'http://localhost:12345/speakers';
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(
|
||||
(result) => {
|
||||
console.log('loaded speakers', result);
|
||||
let first_speaker = result.speakers[0].slug;
|
||||
this.setState({
|
||||
speaker_response: result,
|
||||
speaker_slug: first_speaker,
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
changeSpeaker = (ev: React.FormEvent<HTMLSelectElement>) => {
|
||||
const speakerSlug = (ev.target as HTMLInputElement).value;
|
||||
this.setState({
|
||||
speaker_slug: speakerSlug,
|
||||
})
|
||||
}
|
||||
|
||||
public render() {
|
||||
let speakers = this.state.speaker_response?.speakers;
|
||||
let dropdown;
|
||||
|
||||
if (speakers?.length || 0 > 0) {
|
||||
dropdown = <SpeakerDropdownComponent
|
||||
speakers={speakers!}
|
||||
changeCallback={this.changeSpeaker}
|
||||
/>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<b>SpeakerMode</b>
|
||||
{dropdown}
|
||||
<SpeakerAudioForm speaker={this.state.speaker_slug!} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {SpeakerModeComponent};
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
class SpeakerResponse {
|
||||
speakers?: SpeakerDetails[]
|
||||
}
|
||||
|
||||
class SpeakerDetails {
|
||||
name?: string
|
||||
slug?: string
|
||||
model_pipeline?: string
|
||||
tacotron?: string
|
||||
melgan?: string
|
||||
}
|
||||
|
||||
export {SpeakerResponse, SpeakerDetails};
|
||||
@@ -101,7 +101,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.allowed_origin("http://trumped.com")
|
||||
.allowed_origin("http://jungle.horse")
|
||||
.allowed_methods(vec!["GET", "POST"])
|
||||
.allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
|
||||
.allowed_headers(vec![http::header::ACCEPT])
|
||||
.allowed_header(http::header::CONTENT_TYPE)
|
||||
.max_age(3600)
|
||||
.finish())
|
||||
|
||||
Reference in new issue
Block a user