mirror of
https://github.com/ParisNeo/lollms-webui.git
synced 2024-12-19 20:37:51 +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
|
||||
import time
|
||||
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"):
|
||||
PackageManager.install_package("requests")
|
||||
@ -48,24 +48,6 @@ import requests
|
||||
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):
|
||||
if thread:
|
||||
@ -1211,7 +1193,7 @@ class LoLLMsAPI(LollmsApplication):
|
||||
try:
|
||||
from lollms.audio_gen_modules.lollms_xtts import LollmsXTTS
|
||||
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:
|
||||
self.warning(f"Personality {personality.name} request using custom voice but couldn't load XTTS")
|
||||
except Exception as ex:
|
||||
@ -2079,9 +2061,10 @@ class LoLLMsAPI(LollmsApplication):
|
||||
)
|
||||
if self.config.auto_read and len(self.personality.audio_samples)>0:
|
||||
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
|
||||
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)
|
||||
self.tts.set_speaker_folder(Path(self.personality.audio_samples[0]).parent)
|
||||
fn = self.personality.name.lower().replace(' ',"_").replace('.','')
|
||||
@ -2089,11 +2072,12 @@ class LoLLMsAPI(LollmsApplication):
|
||||
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)
|
||||
fl = f"""
|
||||
<audio controls autoplay>
|
||||
<audio controls>
|
||||
<source src="{url}" type="audio/wav">
|
||||
Your browser does not support the audio element.
|
||||
</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)
|
||||
|
||||
"""
|
||||
|
23
app.py
23
app.py
@ -27,7 +27,7 @@ import traceback
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
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):
|
||||
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"]
|
||||
)
|
||||
|
||||
self.add_endpoint(
|
||||
"/read", "read", self.read, methods=["POST"]
|
||||
)
|
||||
|
||||
self.add_endpoint(
|
||||
"/get_presets", "get_presets", self.get_presets, methods=["GET"]
|
||||
)
|
||||
@ -753,6 +757,23 @@ try:
|
||||
presets.append(preset)
|
||||
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):
|
||||
# Get the JSON data from the POST request.
|
||||
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">
|
||||
<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">
|
||||
</head>
|
||||
<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">
|
||||
<i data-feather="volume-2"></i>
|
||||
</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="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.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() {
|
||||
if (this.msg) {
|
||||
this.speechSynthesis.cancel();
|
||||
|
Loading…
Reference in New Issue
Block a user