upgraded ui

This commit is contained in:
Saifeddine ALOUI 2023-07-24 01:25:05 +02:00
parent 3ca79f9909
commit de4a6ecef6
13 changed files with 588 additions and 382 deletions

View File

@ -761,7 +761,7 @@ class LoLLMsAPPI(LollmsApplication):
sys.stdout.flush()
antiprompt = self.personality.detect_antiprompt(self.connections[client_id]["generated_text"])
if antiprompt:
ASCIIColors.warning(f"Detected hallucination with antiprompt: {antiprompt}")
ASCIIColors.warning(f"\nDetected hallucination with antiprompt: {antiprompt}")
self.connections[client_id]["generated_text"] = self.remove_text_from_string(self.connections[client_id]["generated_text"],antiprompt)
self.socketio.emit('message', {
'data': self.connections[client_id]["generated_text"],

38
app.py
View File

@ -54,8 +54,9 @@ import psutil
from lollms.main_config import LOLLMSConfig
from typing import Optional
import gc
import pkg_resources
import gc
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
@ -132,18 +133,8 @@ def run_restart_script(args):
arg_string = " ".join([f"--{key} {value}" for key, value in valid_args.items()])
file.write(arg_string)
os.system(f"python {restart_script} {temp_file}")
# Reload the main script with the original arguments
if os.path.exists(temp_file):
with open(temp_file, "r") as file:
args = file.read().split()
main_script = "app.py" # Replace with the actual name of your main script
os.system(f"python {main_script} {' '.join(args)}")
os.remove(temp_file)
else:
print("Error: Temporary arguments file not found.")
sys.exit(1)
os.system(f"python {restart_script}")
sys.exit(0)
def run_update_script(args):
update_script = "update_script.py"
@ -161,18 +152,8 @@ def run_update_script(args):
arg_string = " ".join([f"--{key} {value}" for key, value in valid_args.items()])
file.write(arg_string)
os.system(f"python {update_script} {temp_file}")
# Reload the main script with the original arguments
if os.path.exists(temp_file):
with open(temp_file, "r") as file:
args = file.read().split()
main_script = "app.py" # Replace with the actual name of your main script
os.system(f"python {main_script} {' '.join(args)}")
os.remove(temp_file)
else:
print("Error: Temporary arguments file not found.")
sys.exit(1)
os.system(f"python {update_script}")
sys.exit(0)
class LoLLMsWebUI(LoLLMsAPPI):
@ -205,6 +186,9 @@ class LoLLMsWebUI(LoLLMsAPPI):
# Endpoints
# =========================================================================================
self.add_endpoint("/get_lollms_version", "get_lollms_version", self.get_lollms_version, methods=["POST"])
self.add_endpoint("/reload_binding", "reload_binding", self.reload_binding, methods=["POST"])
self.add_endpoint("/update_software", "update_software", self.update_software, methods=["GET"])
self.add_endpoint("/clear_uploads", "clear_uploads", self.clear_uploads, methods=["GET"])
@ -1162,6 +1146,10 @@ class LoLLMsWebUI(LoLLMsAPPI):
ASCIIColors.info("")
run_update_script(self.args)
def get_lollms_version(self):
version = pkg_resources.get_distribution('lollms').version
ASCIIColors.yellow("Lollms version : "+ version)
return jsonify({"version":version})
def reload_binding(self):
try:

View File

@ -5,9 +5,9 @@ import git
import subprocess
import argparse
def run_git_pull(repo_path):
def run_git_pull():
try:
repo = git.Repo(repo_path)
repo = git.Repo(".")
origin = repo.remotes.origin
origin.pull()
except git.GitCommandError as e:
@ -27,7 +27,7 @@ def main():
repo_path = args.repo
# Perform git pull to update the repository
run_git_pull(repo_path)
run_git_pull()
# Install the new requirements
install_requirements()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
web/dist/index.html vendored
View File

@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LoLLMS WebUI - Welcome</title>
<script type="module" crossorigin src="/assets/index-0fe86d3d.js"></script>
<link rel="stylesheet" href="/assets/index-218d4a39.css">
<script type="module" crossorigin src="/assets/index-2afc397e.js"></script>
<link rel="stylesheet" href="/assets/index-506d87c6.css">
</head>
<body>
<div id="app"></div>

View File

@ -1,32 +1,174 @@
<template>
<div>
<input v-model="inputValue" :placeholder="placeholderText" />
<button @click="pasteFromClipboard"><i data-feather="clipboard"></i></button>
<div class="flex items-center space-x-2">
<input
:value="value"
:type="inputType"
:placeholder="placeholderText"
@input="handleInput"
@paste="handlePaste"
class="flex-1 px-4 py-2 text-lg border border-gray-300 rounded-md focus:outline-none focus:ring focus:border-blue-500"
/>
<button
@click="pasteFromClipboard"
class="p-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring focus:border-blue-300"
>
<i data-feather="clipboard"></i>
</button>
<!-- File type button -->
<button
v-if="inputType === 'file'"
@click="openFileInput"
class="p-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring focus:border-blue-300"
>
<i data-feather="upload"></i>
</button>
<!-- Hidden file input -->
<input
v-if="inputType === 'file'"
ref="fileInput"
type="file"
style="display: none"
:accept="fileAccept"
@change="handleFileInputChange"
/>
</div>
</template>
<script>
import feather from 'feather-icons'
import feather from "feather-icons";
import { nextTick } from "vue";
export default {
props: {
value: String, // Custom v-model prop to receive the input value
inputType: {
type: String,
default: "text",
validator: (value) =>
["text", "email", "password", "file", "path", "integer", "float"].includes(
value
),
},
fileAccept: String, // Prop to specify the accepted file types
},
data() {
return {
inputValue: "",
placeholderText: "Enter text here",
inputValue: this.value,
placeholderText: this.getPlaceholderText(),
};
},
watch: {
value(newVal) {
// Watch for changes from parent component to keep inputValue in sync
console.log("Changing value to ", newVal)
this.inputValue = newVal;
},
},
mounted() {
console.log('mnted all chat', this.allDiscussionPersonalities)
nextTick(() => {
feather.replace()
})
feather.replace();
});
console.log("Changing value to ", this.value)
this.inputValue = this.value;
},
methods: {
pasteFromClipboard() {
navigator.clipboard.readText().then((text) => {
getPlaceholderText() {
switch (this.inputType) {
case "text":
return "Enter text here";
case "email":
return "Enter your email";
case "password":
return "Enter your password";
case "file":
case "path":
return "Choose a file";
case "integer":
return "Enter an integer";
case "float":
return "Enter a float";
default:
return "Enter value here";
}
},
handleInput(event) {
if (this.inputType === "integer") {
const sanitizedValue = event.target.value.replace(/[^0-9]/g, "");
this.inputValue = sanitizedValue;
}
console.log("handling input : ", event.target.value)
this.$emit('input', event.target.value)
},
async pasteFromClipboard() {
try {
const text = await navigator.clipboard.readText();
this.handleClipboardData(text);
} catch (error) {
console.error("Failed to read from clipboard:", error);
// Handle the error gracefully, e.g., show a message to the user
}
},
handlePaste(event) {
const text = event.clipboardData.getData("text");
this.handleClipboardData(text);
},
handleClipboardData(text) {
switch (this.inputType) {
case "email":
this.inputValue = this.isValidEmail(text) ? text : "";
break;
case "password":
// Here, you can add validation for password strength if needed
this.inputValue = text;
});
break;
case "file":
case "path":
// For file and path types, you might not want to allow pasting directly
// into the input field. You can handle this as per your requirements.
this.inputValue = "";
break;
case "integer":
this.inputValue = this.parseInteger(text);
break;
case "float":
this.inputValue = this.parseFloat(text);
break;
default:
this.inputValue = text;
break;
}
},
isValidEmail(value) {
// Simple email validation using regex
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value);
},
parseInteger(value) {
// Parse integer value or return empty if invalid
const parsedValue = parseInt(value);
return isNaN(parsedValue) ? "" : parsedValue;
},
parseFloat(value) {
// Parse float value or return empty if invalid
const parsedValue = parseFloat(value);
return isNaN(parsedValue) ? "" : parsedValue;
},
openFileInput() {
this.$refs.fileInput.click(); // Trigger the file input when the button is clicked
},
handleFileInputChange(event) {
const file = event.target.files[0];
if (file) {
// Display the file path in the input field
this.inputValue = file.name;
}
},
},
};
</script>
<style>
/* Optional: You can add more custom styling here if needed */
</style>

View File

@ -159,8 +159,6 @@ export default {
const script = document.createElement('script');
script.textContent = `
// Your inline script code here
console.log('Inline script executed!');
function copyContentToClipboard(id) {
console.log("copied");
const codeElement = document.getElementById('code_' + id);

View File

@ -236,15 +236,19 @@ export default {
this.voices = this.speechSynthesis.getVoices();
},
speak() {
if(this.msg)
{
this.isVoiceActive = false;
if (this.msg) {
this.speechSynthesis.cancel();
this.msg = null;
return
this.isVoiceActive = false;
return;
}
const textToSpeak = this.message.content;
let startIndex =0;
// Set isVoiceActive to true before starting synthesis
console.log("voice on")
this.isVoiceActive = true;
const chunkSize = 200; // You can adjust the chunk size as needed
this.message.content;
// Create a new SpeechSynthesisUtterance instance
this.msg = new SpeechSynthesisUtterance();
@ -254,34 +258,50 @@ export default {
this.msg.voice = this.voices.filter(voice => voice.name === this.$store.state.config.audio_out_voice)[0];
}
// Set isVoiceActive to true before starting synthesis
this.isVoiceActive = true;
// Function to find the index of the last sentence that fits within the chunk size
const findLastSentenceIndex = (startIndex) => {
let txt = this.message.content.substring(startIndex, startIndex+chunkSize)
// Define an array of characters that represent end of sentence markers.
const endOfSentenceMarkers = ['.', '!', '?'];
// Initialize a variable to store the index of the last end of sentence marker.
let lastIndex = -1;
// Iterate through the end of sentence markers and find the last occurrence in the txt string.
endOfSentenceMarkers.forEach(marker => {
const markerIndex = txt.lastIndexOf(marker);
if (markerIndex > lastIndex) {
lastIndex = markerIndex;
}
});
return lastIndex+startIndex;
};
// Function to speak a chunk of text
const speakChunk = (startIdx) => {
const chunk = textToSpeak.substr(startIdx, chunkSize);
const speakChunk = () => {
const endIndex = findLastSentenceIndex(startIndex);
const chunk = this.message.content.substring(startIndex, endIndex);
this.msg.text = chunk;
startIndex = endIndex + 1;
this.msg.onend = (event) => {
if (startIndex < this.message.content.length-2) {
// Use setTimeout to add a brief delay before speaking the next chunk
setTimeout(() => {
speakChunk();
}, 1); // Adjust the delay as needed
} else {
this.isVoiceActive = false;
console.log("voice off :",this.message.content.length," ",endIndex)
}
};
this.speechSynthesis.speak(this.msg);
};
// Listen for the end event to set isVoiceActive to false after synthesis completes
this.msg.addEventListener('end', () => {
const startIdx = msg.text.length;
if (startIdx < textToSpeak.length) {
// Use setTimeout to add a brief delay before speaking the next chunk
setTimeout(() => {
speakChunk(startIdx);
}, 200); // Adjust the delay as needed
} else {
this.isVoiceActive = false;
}
});
// Speak the first chunk
speakChunk(0);
speakChunk();
},
toggleModel() {
this.expanded = !this.expanded;
},
@ -378,10 +398,24 @@ export default {
day_diff == 1 && "Yesterday" ||
day_diff < 7 && day_diff + " days ago" ||
day_diff < 31 && Math.ceil(day_diff / 7) + " weeks ago";
},
checkForFullSentence() {
if(this.message.content.trim().split(" ").length>3){
// If the sentence contains at least 3 words, call the speak() method
this.speak();
return; // Exit the loop after the first full sentence is found
}
},
}, watch: {
'message.content': function (newContent) {
if(this.$store.state.config.auto_speak){
if(!this.isVoiceActive){
// Watch for changes to this.message.content and call the checkForFullSentence method
this.checkForFullSentence();
}
}
},
showConfirmation() {
nextTick(() => {
feather.replace()
@ -403,6 +437,7 @@ export default {
})
},
},
computed: {
isTalking :{

View File

@ -23,6 +23,19 @@
<i data-feather="github"></i>
</div>
</a>
<a href="https://www.youtube.com/channel/UCJzrg0cyQV2Z30SQ1v2FdSQ" target="_blank">
<div class="text-2xl hover:text-primary duration-150" title="Visit repository page">
<i data-feather="youtube"></i>
</div>
</a>
<a href="https://twitter.com/SpaceNerduino" target="_blank">
<div class="text-2xl hover:text-primary duration-150" title="Follow me on my twitter acount">
<i data-feather="twitter"></i>
</div>
</a>
<div class="sun text-2xl w-6 hover:text-primary duration-150" title="Swith to Light theme"
@click="themeSwitch()">
<i data-feather="sun"></i>

View File

@ -1,5 +1,10 @@
<template>
<div class="container mx-auto p-4 bg-bg-light-tone dark:bg-bg-dark-tone shadow-lg">
<div>
<h2 class="text-2xl font-bold mb-2">About Lord of large Language Models</h2>
<p class="mb-4"> Lollms version {{ lollmsVersion }}</p>
<p>Discord link: <a class="text-blue-500 hover:text-blue-400 duration-150" href="https://discord.gg/C73K7hjy">https://discord.gg/C73K7hjy</a></p>
</div>
<div class="mb-8 overflow-y-auto max-h-96 scrollbar">
<h2 class="text-2xl font-bold mb-2">Frequently Asked Questions</h2>
@ -12,7 +17,7 @@
</div>
<div>
<h2 class="text-2xl font-bold mb-2">Contact Us</h2>
<p class="mb-4">If you have any further questions or need assistance, feel free to reach out to us.</p>
<p class="mb-4">If you have any further questions or need assistance, feel free to reach out to me.</p>
<p>Discord link: <a class="text-blue-500 hover:text-blue-400 duration-150" href="https://discord.gg/C73K7hjy">https://discord.gg/C73K7hjy</a></p>
</div>
<div class="mt-8">
@ -25,20 +30,47 @@
</template>
<script>
import axios from 'axios';
import Papa from 'papaparse'; // Import the Papa Parse library for CSV parsing
export default {
name: 'HelpPage',
data() {
return {
lollmsVersion: "unknown",
faqs: [], // Array to store the loaded FAQs
githubLink: 'https://github.com/ParisNeo/lollms-webui', // Replace with your project's GitHub link
};
},
mounted() {
this.loadFAQs(); // Call the method to load FAQs when the component is mounted
this.fetchLollmsVersion().then((val)=>{this.lollmsVersion=val});
},
computed: {
// This will be triggered whenever lollmsVersion is updated
// but it will not be directly used in the template.
async fetchLollmsVersion() {
return await axios.get("/get_lollms_version");
},
},
async created() {
// Fetch the data when the component is created
},
methods: {
async api_get_req(endpoint) {
try {
const res = await axios.get("/" + endpoint);
if (res) {
return res.data
}
} catch (error) {
console.log(error.message, 'api_get_req')
return
}
},
loadFAQs() {
// Fetch and parse the CSV file
fetch('/help/faqs.csv')

View File

@ -530,9 +530,32 @@
</button>
</div>
<div :class="{ 'hidden': minconf_collapsed }" class="flex flex-col mb-2 px-3 pb-0">
<table style="width: 100%;">
<div class="flex flex-col mb-2 px-3 pb-2">
<div class="pb-2">
<table class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<th>Generic</th>
<tr>
<td style="min-width: 200px;">
<label for="db_path" class="text-sm font-bold" style="margin-right: 1rem;">Database path:</label>
</td>
<td style="width: 100%;">
<input
type="text"
id="db_path"
required
v-model="db_path"
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
>
</td>
<td>
<button
class="hover:text-secondary bg-blue-100 m-2 p-2 duration-75 flex justify-center w-full hover:bg-bg-light-tone hover:dark:bg-bg-dark-tone rounded-lg"
@click="update_setting('db_path', db_path)"
>
<i data-feather="check"></i>
</button>
</td>
</tr>
<tr>
<td style="min-width: 200px;">
<label for="enable_gpu" class="text-sm font-bold" style="margin-right: 1rem;">Enable GPU:</label>
@ -577,32 +600,11 @@
</button>
</td>
</tr>
<!-- Row 2 -->
<tr>
<td style="min-width: 200px;">
<label for="db_path" class="text-sm font-bold" style="margin-right: 1rem;">Database path:</label>
</td>
<td style="width: 100%;">
<input
type="text"
id="db_path"
required
v-model="db_path"
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
>
</td>
<td>
<button
class="hover:text-secondary bg-blue-100 m-2 p-2 duration-75 flex justify-center w-full hover:bg-bg-light-tone hover:dark:bg-bg-dark-tone rounded-lg"
@click="update_setting('db_path', db_path)"
>
<i data-feather="check"></i>
</button>
</td>
</tr>
<!-- Row 3 -->
</table>
</div>
<div class="pb-2">
<table class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<th>User</th>
<tr>
<td style="min-width: 200px;">
<label for="user_name" class="text-sm font-bold" style="margin-right: 1rem;">User name:</label>
@ -668,6 +670,34 @@
</button>
</td>
</tr>
</table>
</div>
<div class="pb-2">
<table class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<th>Audio</th>
<tr>
<td style="min-width: 200px;">
<label for="auto_speak" class="text-sm font-bold" style="margin-right: 1rem;">Enable auto speak:</label>
</td>
<td>
<input
type="checkbox"
id="auto_speak"
required
v-model="auto_speak"
class="mt-1 px-2 py-1 border border-gray-300 rounded"
>
</td>
<td>
<button
class="hover:text-secondary bg-blue-100 m-2 p-2 duration-75 flex justify-center w-full hover:bg-bg-light-tone hover:dark:bg-bg-dark-tone rounded-lg"
@click="update_setting('auto_speak', auto_speak)"
>
<i data-feather="check"></i>
</button>
</td>
</tr>
<tr>
<td style="min-width: 200px;">
<label for="audio_in_language" class="text-sm font-bold" style="margin-right: 1rem;">Input Audio Language:</label>
@ -724,6 +754,11 @@
</table>
</div>
</div>
<!-- Row 0 -->
<div class="w-full">
<button
@ -742,13 +777,15 @@
</button>
</div>
<!-- Row 0 -->
<div v-if="has_updates" class="w-full">
<div class="w-full">
<button
class="hover:text-secondary w-full bg-red-100 m-2 p-2 duration-75 flex justify-center w-full hover:bg-bg-light-tone hover:dark:bg-bg-dark-tone rounded-lg"
@click="api_get_req('update_software').then((res)=>{if(res.status){this.$refs.toast.showToast('Success!', 4, true)}else{this.$refs.toast.showToast('Success!', 4, true)}})"
>
Upgrade program
<div v-if="has_updates" >
<i data-feather="alert-circle"></i>
</div>
</button>
</div>
@ -3064,6 +3101,15 @@ export default {
},
},
auto_speak:{
get() {
return this.$store.state.config.auto_speak;
},
set(value) {
// You should not set the value directly here; use the updateSetting method instead
this.$store.state.config.auto_speak = value
},
},
audio_in_language:{
get() {
return this.$store.state.config.audio_in_language;

View File

@ -4,100 +4,43 @@
<!-- Model/Tokenizer -->
<div class="mb-4">
<label for="model_name" class="text-sm">Model Name:</label>
<input
type="text"
id="model_name"
v-model="model_name"
required
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
>
<ClipBoardTextInput id="model_path" inputType="text" :value="model_name" />
</div>
<div class="mb-4">
<label for="tokenizer_name" class="text-sm">Tokenizer Name:</label>
<input
type="text"
id="tokenizer_name"
v-model="tokenizer_name"
required
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
>
<ClipBoardTextInput id="model_path" inputType="text" :value="tokenizer_name" />
</div>
<!-- Dataset -->
<div class="mb-4">
<label for="dataset_path" class="text-sm">Dataset:</label>
<input
type="file"
id="dataset_path"
ref="dataset_path"
accept=".parquet"
v-on:change="selectDatasetPath"
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
>
<p class="mt-2 text-xs">Selected File: {{ selectedDatasetPath }}</p>
<ClipBoardTextInput id="model_path" inputType="file" :value="dataset_path"/>
</div>
<div class="mb-4">
<label for="max_length" class="text-sm">Max Length:</label>
<input
type="number"
id="max_length"
v-model.number="max_length"
required
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
>
<ClipBoardTextInput id="model_path" inputType="integer" :value="max_length"/>
</div>
<div class="mb-4">
<label for="batch_size" class="text-sm">Batch Size:</label>
<input
type="number"
id="batch_size"
v-model.number="batch_size"
required
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
>
<ClipBoardTextInput id="model_path" inputType="integer" :value="batch_size"/>
</div>
<!-- Train Dynamics -->
<div class="mb-4">
<label for="lr" class="text-sm">Learning Rate:</label>
<input
type="number"
id="lr"
v-model.number="lr"
required
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
>
<ClipBoardTextInput id="model_path" inputType="integer" :value="lr"/>
</div>
<div class="mb-4">
<label for="num_epochs" class="text-sm">Number of Epochs:</label>
<input
type="number"
id="num_epochs"
v-model.number="num_epochs"
required
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
>
<ClipBoardTextInput id="model_path" inputType="integer" :value="num_epochs"/>
</div>
<!-- Logging -->
<div class="mb-4">
<label for="output_dir" class="text-sm">Output Directory:</label>
<input
type="text"
id="output_dir"
v-model="selectedFolder"
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded"
placeholder="Enter or select the output folder"
>
<input
type="file"
id="folder_selector"
ref="folder_selector"
style="display: none"
webkitdirectory
v-on:change="selectOutputDirectory"
>
<button type="button" @click="openFolderSelector" class="bg-blue-500 text-white px-4 py-2 rounded">Select Folder</button>
<ClipBoardTextInput id="model_path" inputType="text" :value="output_dir" />
</div>
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded">Train LLM</button>
@ -106,7 +49,11 @@
</template>
<script>
import ClipBoardTextInput from "@/components/ClipBoardTextInput.vue";
export default {
components: {
ClipBoardTextInput,
},
data() {
return {
model_name: 'jondurbin/airoboros-7b-gpt4',
@ -154,6 +101,13 @@
}
},
},
watch: {
model_name(newVal) {
// Watch for changes to model_name and propagate them to the child component
console.log("watching model_name", newVal)
this.$refs.clipboardInput.inputValue = newVal;
},
},
};
</script>