Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Novel AI service (basic tts only) #1367

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions src/main/java/org/myrobotlab/service/NovelAI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.myrobotlab.service;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import org.myrobotlab.io.FileIO;
import org.myrobotlab.logging.Level;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.LoggingFactory;
import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis;
import org.myrobotlab.service.config.NovelAIConfig;
import org.myrobotlab.service.data.AudioData;
import org.slf4j.Logger;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class NovelAI extends AbstractSpeechSynthesis<NovelAIConfig> {

private static final long serialVersionUID = 1L;

public final static Logger log = LoggerFactory.getLogger(NovelAI.class);

private transient OkHttpClient client = new OkHttpClient();

public NovelAI(String n, String id) {
super(n, id);
client = new OkHttpClient();
}

/**
* The methods apply and getConfig can be used, if more complex configuration
* handling is needed. By default, the framework takes care of most of it,
* including subscription handling.
*
* <pre>
&#64;Override
public ServiceConfig apply(ServiceConfig c) {
super.apply(c)
return c;
}

@Override
public ServiceConfig getConfig() {
super.getConfig()
return config;
}
* </pre>
**/

public static void main(String[] args) {
try {

LoggingFactory.init(Level.INFO);

Runtime.start("novelai", "NovelAI");
Runtime.start("webgui", "WebGui");

} catch (Exception e) {
log.error("main threw", e);
}
}

@Override
public AudioData generateAudioData(AudioData audioData, String toSpeak) throws Exception {

String baseUrl = "https://api.novelai.net/ai/generate-voice?voice=-1&seed=" + config.voice + "&opus=false&version=v2&text=";
String encodedText = URLEncoder.encode(toSpeak, StandardCharsets.UTF_8.toString());
String url = baseUrl + encodedText;

Request request = new Request.Builder().url(url).build();

try {
Response response = client.newCall(request).execute();

if (response.isSuccessful()) {
ResponseBody responseBody = response.body();
if (responseBody != null) {
byte[] bytes = responseBody.bytes();
FileIO.toFile(audioData.getFileName(), bytes);
}
} else {
error("request failed with code: " + response.code());
}
} catch (Exception e) {
error(e);
}
return audioData;
}

@Override
public void loadVoices() throws Exception {
addVoice("Aini", "female", "en", "Aini");
addVoice("Ligeia", "female", "en", "Ligeia");
addVoice("Orea", "female", "en", "Orea");
addVoice("Claea", "female", "en", "Claea");
addVoice("Lim", "female", "en", "Lim");
addVoice("Aurae", "female", "en", "Aurae");
addVoice("Naia", "female", "en", "Naia");

addVoice("Aulon", "male", "en", "Aulon");
addVoice("Elei", "male", "en", "Elei");
addVoice("Ogma", "male", "en", "Ogma");
addVoice("Raid", "male", "en", "Raid");
addVoice("Pega", "male", "en", "Pega");
addVoice("Lam", "male", "en", "Lam");
}
}
18 changes: 18 additions & 0 deletions src/main/java/org/myrobotlab/service/config/NovelAIConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.myrobotlab.service.config;

import org.myrobotlab.framework.Plan;

public class NovelAIConfig extends SpeechSynthesisConfig {


public NovelAIConfig() {
voice = "Aini";
}

@Override
public Plan getDefault(Plan plan, String name) {
super.getDefault(plan, name);
return plan;
}

}
33 changes: 33 additions & 0 deletions src/main/java/org/myrobotlab/service/meta/NovelAIMeta.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.myrobotlab.service.meta;

import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.service.meta.abstracts.MetaData;
import org.slf4j.Logger;

public class NovelAIMeta extends MetaData {
private static final long serialVersionUID = 1L;
public final static Logger log = LoggerFactory.getLogger(NovelAIMeta.class);

/**
* This class is contains all the meta data details of a service. It's peers,
* dependencies, and all other meta data related to the service.
*
*/
public NovelAIMeta() {

// add a cool description
addDescription("service to interface with NovelAI");

// false will prevent it being seen in the ui
setAvailable(true);

// add it to one or many categories
addCategory("speech");

// add a sponsor to this service
// the person who will do maintenance
// setSponsor("GroG");

}

}
Binary file added src/main/resources/resource/NovelAI.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions src/main/resources/resource/WebGui/app/service/js/NovelAIGui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
angular.module('mrlapp.service.NovelAIGui', []).controller('NovelAIGuiCtrl', ['peer', '$scope', 'mrl', '$uibModal', function(peer, $scope, mrl, $uibModal) {
console.info('NovelAIGuiCtrl')
var _self = this
var msg = this.msg
$scope.autoClear = true
$scope.textArea = false
$scope.spoken = ''

// new selected voice "container" - since it comes from a map next leaves are
// key & value ... value contains the entire voice selected
$scope.newVoice = {
selected: null
}

this.updateState = function(service) {
$scope.service = service
if (service.voice) {
$scope.newVoice.selected = {
'key': service.voice.name,
'value': service.voice
}
}
$scope.$apply()
}

this.onMsg = function(inMsg) {
let data = inMsg.data[0]
switch (inMsg.method) {
case 'onState':
_self.updateState(data)
break
case 'onStartSpeaking':
$scope.spoken = data
$scope.$apply()
break
default:
console.error("ERROR - unhandled method " + $scope.name + " " + inMsg.method)
break
}
}

$scope.speak = function(text) {
msg.send("speak", text)

if ($scope.autoClear) {
$scope.text = ''
}
}

$scope.setVoice = function(text) {
console.log($scope.service.voice.name)
msg.send("setVoice", text.name)
}

msg.subscribe('publishStartSpeaking')
msg.subscribe(this)
}
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<br/>
<div class="row">
<div class="col-md-6"><h3>{{spoken}}</h3>
</div>
</div>
<div class="row">
<div class="col-md-6">
<table class="table table-striped">
<tr>
<td>
<img ng-src="{{(service.ready)?'../green.png':'../red.png'}}"/>
<span ng-show="!service.ready">NovelAI requires AWS Keys</span>
</td>
<td>
mute <toggle width="30" height="30" ng-model="service.mute" ng-change="msg.setMute(service.mute)" on="" off=""/>
</td>

<td colspan="2">
<div class="btn-group" uib-dropdown>
<button type="button" class="btn btn-default">{{service.voice.name}} - {{service.voice.locale.tag}} - {{service.voice.gender}}</button>
<button type="button" class="btn btn-default" uib-dropdown-toggle>
<span class="caret"></span>
</button>
<ul uib-dropdown-menu>
<li ng-repeat="(key, container) in service.voices">
<a href="" ng-click="msg.setVoice(key);msg.broadcastState()">{{key}} - {{container.locale.tag}} - {{container.gender}}</a>
</li>
</ul>
</div>
</td>
</tr>
<tr>
<td colspan="4">
<input class="form-control" ng-keyup="$event.keyCode == 13 && speak(text)" ng-show="!textArea" type="text" ng-model="text" placeholder="enter text">
<textarea type="text" ng-show="textArea" class="form-control" placeholder="type here" ng-model="text"/>
</td>
</tr>
<tr>
<td>auto-clear &nbsp;&nbsp;</td>
<td>
<input type="checkbox" ng-model="autoClear"/>
</td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;text area</td>
<td>
<input type="checkbox" ng-model="textArea"/>
</td>
<tr>
<td></td>
<td></td>
<td></td>
<td>
<button class="btn btn-default" ng-click="speak(text)">Speak</button>
</td>
</tr>
</table>
</div>
</div>
<div class="row" ng-show="!service.ready">
<div class="col-lg-12">
<form class="form-inline">
<input class="form-control" type="password" ng-model="key" placeholder="aws NovelAI key" title="key name to identify secret">
<input class="form-control" type="password" ng-model="secret" placeholder="aws NovelAI secret" title="NovelAI key and secret ">
<button class="btn btn-default" ng-click="msg.setKeys(key, secret);">set keys</button>
<a href="https://docs.aws.amazon.com/NovelAI/latest/dg/authentication-and-access-control.html">how to get keys</a>
</form>
</div>
</div>