Skip to content

Commit

Permalink
Novel AI service (basic tts only) (#1367)
Browse files Browse the repository at this point in the history
  • Loading branch information
supertick authored Nov 16, 2023
1 parent baf243d commit a736fde
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 0 deletions.
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>

0 comments on commit a736fde

Please sign in to comment.