mirror of
https://github.com/ParisNeo/lollms-webui.git
synced 2024-12-24 06:36:37 +00:00
added voice
This commit is contained in:
parent
0b8d2cf8bd
commit
bb43498c89
@ -38,7 +38,7 @@ from datetime import datetime
|
|||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from lollms.utilities import find_first_available_file_index
|
from lollms.utilities import find_first_available_file_index, convert_language_name
|
||||||
|
|
||||||
if not PackageManager.check_package_installed("requests"):
|
if not PackageManager.check_package_installed("requests"):
|
||||||
PackageManager.install_package("requests")
|
PackageManager.install_package("requests")
|
||||||
@ -48,24 +48,6 @@ import requests
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
def convert_language_name(language_name):
|
|
||||||
# Remove leading and trailing spaces
|
|
||||||
language_name = language_name.strip()
|
|
||||||
|
|
||||||
# Convert to lowercase
|
|
||||||
language_name = language_name.lower().replace(".","")
|
|
||||||
|
|
||||||
# Define a dictionary mapping language names to their codes
|
|
||||||
language_codes = {
|
|
||||||
"english": "en",
|
|
||||||
"spanish": "es",
|
|
||||||
"french": "fr",
|
|
||||||
"german": "de",
|
|
||||||
# Add more language names and codes as needed
|
|
||||||
}
|
|
||||||
|
|
||||||
# Return the corresponding language code if found, or None otherwise
|
|
||||||
return language_codes.get(language_name,"en")
|
|
||||||
|
|
||||||
def terminate_thread(thread):
|
def terminate_thread(thread):
|
||||||
if thread:
|
if thread:
|
||||||
@ -1211,7 +1193,7 @@ class LoLLMsAPI(LollmsApplication):
|
|||||||
try:
|
try:
|
||||||
from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS
|
from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS
|
||||||
if self.tts is None:
|
if self.tts is None:
|
||||||
self.tts = LollmsXTTS(self, voice_samples_path=Path(personality.audio_samples[0]).parent)
|
self.tts = LollmsXTTS(self, voice_samples_path=Path(__file__).parent.parent/"voices")
|
||||||
except:
|
except:
|
||||||
self.warning(f"Personality {personality.name} request using custom voice but couldn't load XTTS")
|
self.warning(f"Personality {personality.name} request using custom voice but couldn't load XTTS")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@ -2079,9 +2061,10 @@ class LoLLMsAPI(LollmsApplication):
|
|||||||
)
|
)
|
||||||
if self.config.auto_read and len(self.personality.audio_samples)>0:
|
if self.config.auto_read and len(self.personality.audio_samples)>0:
|
||||||
try:
|
try:
|
||||||
|
self.process_chunk("Generating voice output",MSG_TYPE.MSG_TYPE_STEP_START,client_id=client_id)
|
||||||
from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS
|
from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS
|
||||||
if self.tts is None:
|
if self.tts is None:
|
||||||
self.tts = LollmsXTTS(self, voice_samples_path=Path(self.personality.audio_samples[0]).parent)
|
self.tts = LollmsXTTS(self, voice_samples_path=Path(__file__).parent.parent/"voices")
|
||||||
language = convert_language_name(self.personality.language)
|
language = convert_language_name(self.personality.language)
|
||||||
self.tts.set_speaker_folder(Path(self.personality.audio_samples[0]).parent)
|
self.tts.set_speaker_folder(Path(self.personality.audio_samples[0]).parent)
|
||||||
fn = self.personality.name.lower().replace(' ',"_").replace('.','')
|
fn = self.personality.name.lower().replace(' ',"_").replace('.','')
|
||||||
@ -2089,11 +2072,12 @@ class LoLLMsAPI(LollmsApplication):
|
|||||||
url = f"audio/{fn}"
|
url = f"audio/{fn}"
|
||||||
self.tts.tts_to_file(self.connections[client_id]["generated_text"], Path(self.personality.audio_samples[0]).name, f"{fn}", language=language)
|
self.tts.tts_to_file(self.connections[client_id]["generated_text"], Path(self.personality.audio_samples[0]).name, f"{fn}", language=language)
|
||||||
fl = f"""
|
fl = f"""
|
||||||
<audio controls autoplay>
|
<audio controls>
|
||||||
<source src="{url}" type="audio/wav">
|
<source src="{url}" type="audio/wav">
|
||||||
Your browser does not support the audio element.
|
Your browser does not support the audio element.
|
||||||
</audio>
|
</audio>
|
||||||
"""
|
"""
|
||||||
|
self.process_chunk("Generating voice output",MSG_TYPE.MSG_TYPE_STEP_END,client_id=client_id)
|
||||||
self.process_chunk(fl,MSG_TYPE.MSG_TYPE_CHUNK,client_id=client_id)
|
self.process_chunk(fl,MSG_TYPE.MSG_TYPE_CHUNK,client_id=client_id)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
23
app.py
23
app.py
@ -27,7 +27,7 @@ import traceback
|
|||||||
import webbrowser
|
import webbrowser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from lollms.com import NotificationType, NotificationDisplayType
|
from lollms.com import NotificationType, NotificationDisplayType
|
||||||
from lollms.utilities import AdvancedGarbageCollector, reinstall_pytorch_with_cuda
|
from lollms.utilities import AdvancedGarbageCollector, reinstall_pytorch_with_cuda, convert_language_name
|
||||||
def run_update_script(args=None):
|
def run_update_script(args=None):
|
||||||
update_script = Path(__file__).parent/"update_script.py"
|
update_script = Path(__file__).parent/"update_script.py"
|
||||||
|
|
||||||
@ -484,6 +484,10 @@ try:
|
|||||||
"/import_multiple_discussions", "import_multiple_discussions", self.import_multiple_discussions, methods=["POST"]
|
"/import_multiple_discussions", "import_multiple_discussions", self.import_multiple_discussions, methods=["POST"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.add_endpoint(
|
||||||
|
"/read", "read", self.read, methods=["POST"]
|
||||||
|
)
|
||||||
|
|
||||||
self.add_endpoint(
|
self.add_endpoint(
|
||||||
"/get_presets", "get_presets", self.get_presets, methods=["GET"]
|
"/get_presets", "get_presets", self.get_presets, methods=["GET"]
|
||||||
)
|
)
|
||||||
@ -753,6 +757,23 @@ try:
|
|||||||
presets.append(preset)
|
presets.append(preset)
|
||||||
return jsonify(presets)
|
return jsonify(presets)
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
# Get the JSON data from the POST request.
|
||||||
|
data = request.get_json()
|
||||||
|
try:
|
||||||
|
from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS
|
||||||
|
if self.tts is None:
|
||||||
|
self.tts = LollmsXTTS(self, voice_samples_path=Path(__file__).parent/"voices")
|
||||||
|
language = convert_language_name(self.personality.language)
|
||||||
|
self.tts.set_speaker_folder(Path(__file__).parent/"voices")
|
||||||
|
fn = self.personality.name.lower().replace(' ',"_").replace('.','')
|
||||||
|
fn = f"playground_voice.wav"
|
||||||
|
url = f"audio/{fn}"
|
||||||
|
self.tts.tts_to_file(data['text'], "main_voice.wav", f"{fn}", language=language)
|
||||||
|
return jsonify({"url": url})
|
||||||
|
except:
|
||||||
|
return jsonify({"url": None})
|
||||||
|
|
||||||
def add_preset(self):
|
def add_preset(self):
|
||||||
# Get the JSON data from the POST request.
|
# Get the JSON data from the POST request.
|
||||||
preset_data = request.get_json()
|
preset_data = request.get_json()
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit ab25c6c6aab13bbfa98bc1409e7d0a69808a1f7d
|
Subproject commit 2df38174ccfdd1bae63140f4cc1a7aa024f9a844
|
BIN
voices/main_voice.wav
Normal file
BIN
voices/main_voice.wav
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
2
web/dist/index.html
vendored
2
web/dist/index.html
vendored
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>LoLLMS WebUI - Welcome</title>
|
<title>LoLLMS WebUI - Welcome</title>
|
||||||
<script type="module" crossorigin src="/assets/index-3d66646d.js"></script>
|
<script type="module" crossorigin src="/assets/index-537a0c2f.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/index-ad8f7b32.css">
|
<link rel="stylesheet" href="/assets/index-ad8f7b32.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
139
web/src/components/PaintingComponent.vue
Normal file
139
web/src/components/PaintingComponent.vue
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<div class="painting-component">
|
||||||
|
<canvas
|
||||||
|
ref="canvas"
|
||||||
|
:width="canvasWidth"
|
||||||
|
:height="canvasHeight"
|
||||||
|
@mousedown="startPainting"
|
||||||
|
@mousemove="paint"
|
||||||
|
@mouseup="stopPainting"
|
||||||
|
@mouseleave="stopPainting"
|
||||||
|
></canvas>
|
||||||
|
<div class="controls">
|
||||||
|
<label for="stroke-width">Stroke Width:</label>
|
||||||
|
<input type="number" id="stroke-width" v-model="strokeWidth" min="1" max="50" />
|
||||||
|
<label for="stroke-color">Stroke Color:</label>
|
||||||
|
<input type="color" id="stroke-color" v-model="strokeColor" />
|
||||||
|
<button @click="undoStroke">Undo</button>
|
||||||
|
<button @click="saveDrawing">Save</button>
|
||||||
|
<button @click="loadDrawing">Load</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PaintingComponent',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
canvasWidth: 800,
|
||||||
|
canvasHeight: 600,
|
||||||
|
strokeWidth: 5,
|
||||||
|
strokeColor: '#000000',
|
||||||
|
isPainting: false,
|
||||||
|
strokes: [],
|
||||||
|
undoneStrokes: [],
|
||||||
|
canvas: null,
|
||||||
|
ctx: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initCanvas();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initCanvas() {
|
||||||
|
this.canvas = this.$refs.canvas;
|
||||||
|
this.ctx = this.canvas.getContext('2d');
|
||||||
|
this.ctx.lineCap = 'round';
|
||||||
|
this.ctx.lineJoin = 'round';
|
||||||
|
},
|
||||||
|
startPainting(event) {
|
||||||
|
const { offsetX, offsetY } = event;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(offsetX, offsetY);
|
||||||
|
this.isPainting = true;
|
||||||
|
},
|
||||||
|
paint(event) {
|
||||||
|
if (this.isPainting) {
|
||||||
|
const { offsetX, offsetY } = event;
|
||||||
|
this.ctx.lineTo(offsetX, offsetY);
|
||||||
|
this.ctx.strokeStyle = this.strokeColor;
|
||||||
|
this.ctx.lineWidth = this.strokeWidth;
|
||||||
|
this.ctx.stroke();
|
||||||
|
this.undoneStrokes = []; // Clear the undone strokes as a new stroke is made
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stopPainting() {
|
||||||
|
if (this.isPainting) {
|
||||||
|
this.ctx.closePath();
|
||||||
|
this.isPainting = false;
|
||||||
|
this.strokes.push(this.canvas.toDataURL()); // Save the current state
|
||||||
|
}
|
||||||
|
},
|
||||||
|
undoStroke() {
|
||||||
|
if (this.strokes.length > 0) {
|
||||||
|
this.undoneStrokes.push(this.strokes.pop());
|
||||||
|
this.reloadCanvas();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reloadCanvas() {
|
||||||
|
if (this.strokes.length > 0) {
|
||||||
|
const lastStroke = new Image();
|
||||||
|
lastStroke.onload = () => {
|
||||||
|
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
this.ctx.drawImage(lastStroke, 0, 0);
|
||||||
|
};
|
||||||
|
lastStroke.src = this.strokes[this.strokes.length - 1];
|
||||||
|
} else {
|
||||||
|
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveDrawing() {
|
||||||
|
const dataURL = this.canvas.toDataURL("image/png");
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.download = 'painting.png';
|
||||||
|
link.href = dataURL;
|
||||||
|
link.click();
|
||||||
|
},
|
||||||
|
loadDrawing() {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.onchange = e => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = readerEvent => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
this.ctx.drawImage(img, 0, 0);
|
||||||
|
this.strokes.push(this.canvas.toDataURL());
|
||||||
|
};
|
||||||
|
img.src = readerEvent.target.result;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
};
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.painting-component {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls label {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -22,6 +22,13 @@
|
|||||||
class="w-6 hover:text-secondary duration-75 active:scale-90 cursor-pointer">
|
class="w-6 hover:text-secondary duration-75 active:scale-90 cursor-pointer">
|
||||||
<i data-feather="volume-2"></i>
|
<i data-feather="volume-2"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
title="read"
|
||||||
|
@click.stop="read()"
|
||||||
|
:class="{ 'text-red-500': isTalking }"
|
||||||
|
class="w-6 hover:text-secondary duration-75 active:scale-90 cursor-pointer">
|
||||||
|
<i data-feather="voicemail"></i>
|
||||||
|
</button>
|
||||||
<button v-show="!generating" id="export-button" @click="exportText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="upload"></i></button>
|
<button v-show="!generating" id="export-button" @click="exportText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="upload"></i></button>
|
||||||
<button v-show="!generating" id="import-button" @click="importText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="download"></i></button>
|
<button v-show="!generating" id="import-button" @click="importText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="download"></i></button>
|
||||||
|
|
||||||
@ -478,6 +485,18 @@ export default {
|
|||||||
// This event will be triggered when the voices are loaded
|
// This event will be triggered when the voices are loaded
|
||||||
this.voices = this.speechSynthesis.getVoices();
|
this.voices = this.speechSynthesis.getVoices();
|
||||||
},
|
},
|
||||||
|
read(){
|
||||||
|
this.generating=true
|
||||||
|
axios.post("./read",{text:this.text}).then(response => {
|
||||||
|
console.log(response.data.url)
|
||||||
|
let url = response.data.url
|
||||||
|
this.text+=`\n<audio controls>\n<source src="${url}" type="audio/wav">\nYour browser does not support the audio element.\n</audio>`
|
||||||
|
this.generating=false
|
||||||
|
}).catch(ex=>{
|
||||||
|
this.$refs.toast.showToast(`Error: ${ex}`,4,false)
|
||||||
|
this.generating=false
|
||||||
|
});
|
||||||
|
},
|
||||||
speak() {
|
speak() {
|
||||||
if (this.msg) {
|
if (this.msg) {
|
||||||
this.speechSynthesis.cancel();
|
this.speechSynthesis.cancel();
|
||||||
|
Loading…
Reference in New Issue
Block a user