enhanced code

This commit is contained in:
Saifeddine ALOUI 2023-07-20 02:12:54 +02:00
parent f855b10a0d
commit 94b1e5ca2d
14 changed files with 300 additions and 306 deletions

View File

@ -172,16 +172,25 @@ class LoLLMsAPPI(LollmsApplication):
@socketio.on('cancel_install') @socketio.on('cancel_install')
def cancel_install(data): def cancel_install(data):
model_name = data["model_name"] try:
binding_folder = data["binding_folder"] model_name = data["model_name"]
model_url = data["model_url"] binding_folder = data["binding_folder"]
signature = f"{model_name}_{binding_folder}_{model_url}" model_url = data["model_url"]
self.download_infos[signature]["cancel"]=True signature = f"{model_name}_{binding_folder}_{model_url}"
self.socketio.emit('canceled', { self.download_infos[signature]["cancel"]=True
'status': True self.socketio.emit('canceled', {
}, 'status': True
room=request.sid },
) room=request.sid
)
except Exception as ex:
trace_exception(ex)
self.socketio.emit('canceled', {
'status': False,
'error':str(ex)
},
room=request.sid
)
@socketio.on('install_model') @socketio.on('install_model')
def install_model(data): def install_model(data):
@ -395,6 +404,14 @@ class LoLLMsAPPI(LollmsApplication):
self.connections[client_id]["generated_text"]="" self.connections[client_id]["generated_text"]=""
self.connections[client_id]["cancel_generation"]=False self.connections[client_id]["cancel_generation"]=False
if not self.model:
self.socketio.emit('model_not_selected',
{
"status":False,
"error":"Model not selected. Please select a model"
}, room=client_id
)
return
if self.is_ready: if self.is_ready:
if self.current_discussion is None: if self.current_discussion is None:

View File

@ -300,7 +300,7 @@ class DiscussionsDB:
# Insert message into the database # Insert message into the database
self.insert("INSERT INTO message (sender, content, type, rank, parent, binding, model, personality, created_at, finished_generating_at, discussion_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", self.insert("INSERT INTO message (sender, content, type, rank, parent, binding, model, personality, created_at, finished_generating_at, discussion_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(sender, content, content_type, rank, parent, model, personality, created_at, finished_generating_at, discussion_id)) (sender, content, content_type, rank, parent, binding, model, personality, created_at, finished_generating_at, discussion_id))
discussions.append(discussion) discussions.append(discussion)

2
app.py
View File

@ -1447,6 +1447,7 @@ class LoLLMsWebUI(LoLLMsAPPI):
try: try:
filename = model.get('filename',"") filename = model.get('filename',"")
server = model.get('server',"") server = model.get('server',"")
variants = model.get('variants',[])
image_url = model.get("icon", '/images/default_model.png') image_url = model.get("icon", '/images/default_model.png')
license = model.get("license", 'unknown') license = model.get("license", 'unknown')
owner = model.get("owner", 'unknown') owner = model.get("owner", 'unknown')
@ -1462,6 +1463,7 @@ class LoLLMsWebUI(LoLLMsAPPI):
is_installed = local_path.exists() or model_type.lower()=="api" is_installed = local_path.exists() or model_type.lower()=="api"
models.append({ models.append({
'title': filename, 'title': filename,
'variants': variants,
'icon': image_url, # Replace with the path to the model icon 'icon': image_url, # Replace with the path to the model icon
'license': license, 'license': license,
'owner': owner, 'owner': owner,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

8
web/dist/assets/index-61c53820.css vendored Normal file

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"> <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-6e792e12.js"></script> <script type="module" crossorigin src="/assets/index-12bf0526.js"></script>
<link rel="stylesheet" href="/assets/index-4f9d4363.css"> <link rel="stylesheet" href="/assets/index-61c53820.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -1,80 +1,93 @@
<!-- ChoiceDialog.vue --> <!-- ChoiceDialog.vue -->
<template> <template>
<transition name="fade"> <transition name="fade">
<div v-if="show" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"> <div v-if="show" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-96"> <div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-96">
<h2 class="text-xl font-semibold mb-4">{{ title }}</h2> <h2 class="text-xl font-semibold mb-4">{{ title }}</h2>
<div class="h-48 overflow-y-auto"> <div class="h-48 overflow-y-auto">
<ul> <ul>
<li <li
v-for="(choice, index) in choices" v-for="(choice, index) in choices"
:key="index" :key="index"
@click="selectChoice(choice)" @click="selectChoice(choice)"
class="py-2 px-4 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700" :class="{'selected-choice': choice === selectedChoice}"
> class="py-2 px-4 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700"
{{ choice }}
</li>
</ul>
</div>
<div class="flex justify-end mt-4">
<button
@click="closeDialog"
class="py-2 px-4 mr-2 bg-red-500 hover:bg-red-600 text-white rounded-lg transition duration-300"
> >
Cancel <span class="font-bold"> {{ choice.name }} </span><br>
</button> <span class="text-xs text-gray-500"> {{ this.formatSize(choice.size) }}</span>
<button </li>
@click="validateChoice" </ul>
class="py-2 px-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition duration-300" </div>
> <div class="flex justify-end mt-4">
Validate <button
</button> @click="closeDialog"
</div> class="py-2 px-4 mr-2 bg-red-500 hover:bg-red-600 text-white rounded-lg transition duration-300"
>
Cancel
</button>
<button
@click="validateChoice"
class="py-2 px-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition duration-300"
>
Validate
</button>
</div> </div>
</div> </div>
</transition> </div>
</template> </transition>
</template>
<script> <script>
export default { export default {
props: { props: {
show: { show: {
type: Boolean, type: Boolean,
required: true, required: true,
},
title: {
type: String,
default: "Select an option",
},
choices: {
type: Array,
required: true,
},
}, },
methods: { title: {
selectChoice(choice) { type: String,
this.$emit("choice-selected", choice); default: "Select an option",
},
closeDialog() {
this.$emit("close-dialog");
},
validateChoice() {
// Perform validation if needed
this.$emit("choice-validated");
},
}, },
}; choices: {
</script> type: Array,
required: true,
<style> },
.fade-enter-active, },
.fade-leave-active { data() {
transition: opacity 0.3s; return {
} selectedChoice: null,
};
.fade-enter-from, },
.fade-leave-to { methods: {
opacity: 0; selectChoice(choice) {
} this.selectedChoice = choice; // Update the selectedChoice when a choice is clicked
</style> this.$emit("choice-selected", choice);
},
closeDialog() {
this.$emit("close-dialog");
},
validateChoice() {
// Perform validation if needed
this.$emit("choice-validated");
},
formatSize(size) {
if (size < 1024) {
return size + " bytes";
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(2) + " KB";
} else if (size < 1024 * 1024 * 1024) {
return (size / (1024 * 1024)).toFixed(2) + " MB";
} else {
return (size / (1024 * 1024 * 1024)).toFixed(2) + " GB";
}
},
},
};
</script>
<style>
/* ... (unchanged) */
.selected-choice {
background-color: #bde4ff; /* Change this color as per your preference */
}
</style>

View File

@ -1,80 +0,0 @@
<!-- ChoiceDialog.vue -->
<template>
<transition name="fade">
<div v-if="show" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-96">
<h2 class="text-xl font-semibold mb-4">{{ title }}</h2>
<div class="h-48 overflow-y-auto">
<ul>
<li
v-for="(choice, index) in choices"
:key="index"
@click="selectChoice(choice)"
class="py-2 px-4 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700"
>
{{ choice }}
</li>
</ul>
</div>
<div class="flex justify-end mt-4">
<button
@click="closeDialog"
class="py-2 px-4 mr-2 bg-red-500 hover:bg-red-600 text-white rounded-lg transition duration-300"
>
Cancel
</button>
<button
@click="validateChoice"
class="py-2 px-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition duration-300"
>
Validate
</button>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
required: true,
},
title: {
type: String,
default: "Select an option",
},
choices: {
type: Array,
required: true,
},
},
methods: {
selectChoice(choice) {
this.$emit("choice-selected", choice);
},
closeDialog() {
this.$emit("close-dialog");
},
validateChoice() {
// Perform validation if needed
this.$emit("choice-validated");
},
},
};
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@ -198,8 +198,6 @@ export default {
} }
}, mounted() { }, mounted() {
console.log("Mounted message")
console.log(this.message)
this.new_message_content = this.message.content this.new_message_content = this.message.content
nextTick(() => { nextTick(() => {
feather.replace() feather.replace()

View File

@ -244,6 +244,7 @@
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@ -254,6 +255,7 @@ import axios from "axios";
import { nextTick } from 'vue' import { nextTick } from 'vue'
import feather from 'feather-icons' import feather from 'feather-icons'
import defaultImgPlaceholder from "../assets/default_model.png" import defaultImgPlaceholder from "../assets/default_model.png"
const bUrl = import.meta.env.VITE_LOLLMS_API_BASEURL const bUrl = import.meta.env.VITE_LOLLMS_API_BASEURL
export default { export default {
props: { props: {
@ -285,8 +287,8 @@ export default {
installing: false, installing: false,
uninstalling: false, uninstalling: false,
failedToLoad: false, failedToLoad: false,
fileSize: '',
linkNotValid: false, linkNotValid: false,
selected_variant: ''
}; };
}, },
async mounted() { async mounted() {
@ -299,15 +301,24 @@ export default {
}) })
}, },
methods: { methods: {
formatFileSize(sizeInBytes) {
if (sizeInBytes < 1024) {
return sizeInBytes + " bytes";
} else if (sizeInBytes < 1024 * 1024) {
return (sizeInBytes / 1024).toFixed(2) + " KB";
} else if (sizeInBytes < 1024 * 1024 * 1024) {
return (sizeInBytes / (1024 * 1024)).toFixed(2) + " MB";
} else {
return (sizeInBytes / (1024 * 1024 * 1024)).toFixed(2) + " GB";
}
},
computedFileSize(size) { computedFileSize(size) {
return filesize(size) return filesize(size)
}, },
async getFileSize(url) { async getFileSize(url) {
//console.log(this.model_type);
if (this.model_type != "api") { if (this.model_type != "api") {
try { try {
const res = await axios.head(url) const res = await axios.head(url)
//console.log("addddd",url, res.headers)
if (res) { if (res) {
if (res.headers["content-length"]) { if (res.headers["content-length"]) {
@ -367,24 +378,17 @@ export default {
event.target.src = defaultImgPlaceholder event.target.src = defaultImgPlaceholder
}, },
toggleInstall() { toggleInstall() {
this.getFileSize(this.model.path).then(data=>{
this.fileSize = data
})
if (this.isInstalled) { if (this.isInstalled) {
this.uninstalling = true; this.uninstalling = true;
// Simulate uninstallation delay (replace this with your WebSocket logic) // Simulate uninstallation delay (replace this with your WebSocket logic)
this.onUninstall(this); this.onUninstall(this);
} else { } else {
this.installing = true; //this.installing = true;
this.onInstall(this); this.onInstall(this);
} }
}, },
toggleSelected() { toggleSelected() {
this.getFileSize(this.model.path).then(data=>{
this.fileSize = data
})
this.onSelected(this) this.onSelected(this)
}, },
toggleCopy() { toggleCopy() {
@ -405,12 +409,21 @@ export default {
} }
}, },
copyContentToClipboard() { copyContentToClipboard() {
console.log('asdasdas')
this.$emit('copy', 'this.message.content') this.$emit('copy', 'this.message.content')
}, },
}, },
computed: { computed: {
fileSize: {
get() {
// console.log(this.model)
if (this.model && this.model.variants && this.model.variants.length > 0) {
const sizeInBytes = this.model.variants[0]["size"];
return this.formatFileSize(sizeInBytes);
}
return null; // Return null if the conditions are not met
},
},
speed_computed() { speed_computed() {
return filesize(this.speed) return filesize(this.speed)
}, },

View File

@ -25,7 +25,7 @@
fill="currentFill" /> fill="currentFill" />
</svg> </svg>
</div> </div>
<div class="content flex-1" :class="{'text-green-500': done, 'text-yellow-500': !done}">{{ message }}</div> <div class="content flex-1 px-2" :class="{'text-green-500': done, 'text-yellow-500': !done}">{{ message }}</div>
</div> </div>
</template> </template>

View File

@ -808,7 +808,8 @@ export default {
// Create response message // Create response message
let responseMessage = { let responseMessage = {
content:msgObj.data, //content: " please stand by ...",//msgObj.message, //content:msgObj.data,
content: "✍ please stand by ...",//msgObj.message,
created_at:msgObj.created_at, created_at:msgObj.created_at,
binding:msgObj.binding, binding:msgObj.binding,
model:msgObj.model, model:msgObj.model,
@ -834,7 +835,6 @@ export default {
if (msgObj.type == "input_message_infos") { if (msgObj.type == "input_message_infos") {
// This is a user input // This is a user input
this.changeTitleUsingUserMSG(this.currentDiscussion.id, msgObj.message) this.changeTitleUsingUserMSG(this.currentDiscussion.id, msgObj.message)
} }
} }
console.log("infos", msgObj) console.log("infos", msgObj)
@ -896,7 +896,7 @@ export default {
message: msg, message: msg,
id: lastmsgid, id: lastmsgid,
rank: 0, rank: 0,
user: "user", user: this.$store.config.user_name,
created_at: new Date().toLocaleString(), created_at: new Date().toLocaleString(),
}; };
@ -913,7 +913,7 @@ export default {
}, },
streamMessageContent(msgObj) { streamMessageContent(msgObj) {
// Streams response message content from binding // Streams response message content from binding
console.log("Received message",msgObj) //console.log("Received message",msgObj)
const parent = msgObj.user_message_id const parent = msgObj.user_message_id
const discussion_id = msgObj.discussion_id const discussion_id = msgObj.discussion_id
this.setDiscussionLoading(discussion_id, true); this.setDiscussionLoading(discussion_id, true);

View File

@ -866,7 +866,7 @@
</div> </div>
<div v-if="!searchModel"> <div v-if="!searchModel">
<div v-if="models.length > 0" class="mb-2"> <div v-if="models && models.length > 0" class="mb-2">
<label for="model" class="block ml-2 mb-2 text-sm font-medium text-gray-900 dark:text-white"> <label for="model" class="block ml-2 mb-2 text-sm font-medium text-gray-900 dark:text-white">
Models: ({{ models.length }}) Models: ({{ models.length }})
</label> </label>
@ -1395,6 +1395,13 @@
<MessageBox ref="messageBox" /> <MessageBox ref="messageBox" />
<Toast ref="toast" /> <Toast ref="toast" />
<UniversalForm ref="universalForm" class="z-20" /> <UniversalForm ref="universalForm" class="z-20" />
<ChoiceDialog class="z-20"
:show="variantSelectionDialogVisible"
:choices="variant_choices"
@choice-selected="onVariantChoiceSelected"
@close-dialog="oncloseVariantChoiceDialog"
@choice-validated="onvalidateVariantChoice"
/>
</template> </template>
<style scoped> <style scoped>
/* THESE ARE FOR TransitionGroup components */ /* THESE ARE FOR TransitionGroup components */
@ -1475,6 +1482,9 @@ import defaultImgPlaceholder from "../assets/default_model.png"
import AddModelDialog from "@/components/AddModelDialog.vue"; import AddModelDialog from "@/components/AddModelDialog.vue";
import UniversalForm from '../components/UniversalForm.vue'; import UniversalForm from '../components/UniversalForm.vue';
import ChoiceDialog from "@/components/ChoiceDialog.vue";
const bUrl = import.meta.env.VITE_LOLLMS_API_BASEURL const bUrl = import.meta.env.VITE_LOLLMS_API_BASEURL
axios.defaults.baseURL = import.meta.env.VITE_LOLLMS_API_BASEURL axios.defaults.baseURL = import.meta.env.VITE_LOLLMS_API_BASEURL
export default { export default {
@ -1488,11 +1498,17 @@ export default {
Toast, Toast,
PersonalityEntry, PersonalityEntry,
BindingEntry, BindingEntry,
UniversalForm UniversalForm,
ChoiceDialog
}, },
data() { data() {
return { return {
// Variant selection
variant_choices:[],
variantSelectionDialogVisible:false,
currenModelToInstall:null,
// Loading text
loading_text:"", loading_text:"",
// Current personality language // Current personality language
personality_language:null, personality_language:null,
@ -1551,8 +1567,75 @@ export default {
}, },
methods: { methods: {
onVariantChoiceSelected(choice){
this.selected_variant = choice
},
oncloseVariantChoiceDialog(){
this.variantSelectionDialogVisible=false;
},
onvalidateVariantChoice(){
this.variantSelectionDialogVisible=false;
this.currenModelToInstall.installing=true;
let model_object = this.currenModelToInstall;
if (model_object.linkNotValid) {
model_object.installing = false
this.$refs.toast.showToast("Link is not valid, file does not exist", 4, false)
return
}
let path = model_object.path;
this.showProgress = true;
this.progress = 0;
this.addModel = { model_name: this.selected_variant.name, binding_folder: this.configFile.binding_name, model_url: model_object.path }
console.log("installing...", this.addModel);
// Use an arrow function for progressListener
const progressListener = (response) => {
console.log("received something");
if (response.status && response.progress <= 100) {
this.addModel = response
console.log(`Progress`, response);
model_object.progress = response.progress
model_object.speed = response.speed
model_object.total_size = response.total_size
model_object.downloaded_size = response.downloaded_size
model_object.start_time = response.start_time
model_object.installing = true
if (model_object.progress == 100) {
const index = this.models.findIndex((model) => model.path === path);
this.models[index].isInstalled = true;
this.showProgress = false;
model_object.installing = false
console.log("Received succeeded")
socket.off('install_progress', progressListener);
console.log("Installed successfully")
// Update the isInstalled property of the corresponding model
this.$refs.toast.showToast("Model:\n" + model_object.title + "\ninstalled!", 4, true)
this.$store.dispatch('refreshDiskUsage');
}
} else {
socket.off('install_progress', progressListener);
console.log("Install failed")
// Installation failed or encountered an error
model_object.installing = false;
this.showProgress = false;
console.error('Installation failed:', response.error);
this.$refs.toast.showToast("Model:\n" + model_object.title + "\nfailed to install!", 4, false)
this.$store.dispatch('refreshDiskUsage');
}
};
socket.on('install_progress', progressListener);
socket.emit('install_model', { path: path });
console.log("Started installation, please wait");
},
uploadAvatar(event){ uploadAvatar(event){
console.log("here")
const file = event.target.files[0]; // Get the selected file const file = event.target.files[0]; // Get the selected file
const formData = new FormData(); // Create a FormData object const formData = new FormData(); // Create a FormData object
formData.append('avatar', file); // Add the file to the form data with the key 'avatar' formData.append('avatar', file); // Add the file to the form data with the key 'avatar'
@ -1870,61 +1953,10 @@ export default {
// Model installation // Model installation
onInstall(model_object) { onInstall(model_object) {
if (model_object.linkNotValid) { this.variant_choices = model_object.model.variants;
model_object.installing = false this.currenModelToInstall = model_object;
this.$refs.toast.showToast("Link is not valid, file does not exist", 4, false) console.log(this.variant_choices)
return this.variantSelectionDialogVisible=true;
}
let path = model_object.path;
this.showProgress = true;
this.progress = 0;
this.addModel = { model_name: model_object.model.title, binding_folder: this.configFile.binding_name, model_url: model_object.path }
console.log("installing...", this.addModel);
// Use an arrow function for progressListener
const progressListener = (response) => {
console.log("received something");
if (response.status && response.progress <= 100) {
this.addModel = response
console.log(`Progress`, response);
model_object.progress = response.progress
model_object.speed = response.speed
model_object.total_size = response.total_size
model_object.downloaded_size = response.downloaded_size
model_object.start_time = response.start_time
model_object.installing = true
if (model_object.progress == 100) {
const index = this.models.findIndex((model) => model.path === path);
this.models[index].isInstalled = true;
this.showProgress = false;
model_object.installing = false
console.log("Received succeeded")
socket.off('install_progress', progressListener);
console.log("Installed successfully")
// Update the isInstalled property of the corresponding model
this.$refs.toast.showToast("Model:\n" + model_object.title + "\ninstalled!", 4, true)
this.$store.dispatch('refreshDiskUsage');
}
} else {
socket.off('install_progress', progressListener);
console.log("Install failed")
// Installation failed or encountered an error
model_object.installing = false;
this.showProgress = false;
console.error('Installation failed:', response.error);
this.$refs.toast.showToast("Model:\n" + model_object.title + "\nfailed to install!", 4, false)
this.$store.dispatch('refreshDiskUsage');
}
};
socket.on('install_progress', progressListener);
socket.emit('install_model', { path: path });
console.log("Started installation, please wait");
}, },
onInstallAddModel() { onInstallAddModel() {
@ -2371,7 +2403,6 @@ export default {
}, },
async update_model(value) { async update_model(value) {
console.log("hjsdfhksdjufkdshf")
if (!value) this.isModelSelected = false if (!value) this.isModelSelected = false
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
this.isLoading = true this.isLoading = true