Upgraded ui

This commit is contained in:
Saifeddine ALOUI 2023-12-16 19:46:16 +01:00
parent 8865a3318b
commit 4b98d5c0da
15 changed files with 425 additions and 292 deletions

View File

@ -292,18 +292,22 @@ class LoLLMsAPI(LollmsApplication):
@socketio.on('start_webcam_video_stream')
def start_webcam_video_stream():
self.info("Starting video capture")
self.webcam.start_capture()
@socketio.on('stop_webcam_video_stream')
def stop_webcam_video_stream():
self.info("Stopping video capture")
self.webcam.stop_capture()
@socketio.on('start_audio_stream')
def start_audio_stream():
self.info("Starting audio capture")
self.audio_cap.start_recording()
@socketio.on('stop_audio_stream')
def stop_audio_stream():
self.info("Stopping audio capture")
self.audio_cap.stop_recording()

2
app.py
View File

@ -1773,6 +1773,7 @@ try:
ASCIIColors.info("")
ASCIIColors.info("")
ASCIIColors.info("")
self.socketio.stop()
run_restart_script(self.args)
@ -1786,6 +1787,7 @@ try:
ASCIIColors.info("")
ASCIIColors.info("")
ASCIIColors.info("")
self.socketio.stop()
run_update_script(self.args)
sys.exit()

@ -1 +1 @@
Subproject commit dd713c24cd723bb77277f3b86f2c4cf447b4c62e
Subproject commit 28152965924b7e69e45950f29d4818c31ef4bbff

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-6f17f69e.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">
<title>LoLLMS WebUI - Welcome</title>
<script type="module" crossorigin src="/assets/index-2895a271.js"></script>
<link rel="stylesheet" href="/assets/index-44523b05.css">
<script type="module" crossorigin src="/assets/index-52394dd9.js"></script>
<link rel="stylesheet" href="/assets/index-6f17f69e.css">
</head>
<body>
<div id="app"></div>

View File

@ -1,63 +1,124 @@
<template>
<div class="floating-frame">
<img v-if="isAudioActive" :src="imageDataUrl" alt="Spectrogram" width="300" height="300" />
<div class="floating-frame bg-white" :style="{ bottom: position.bottom + 'px', right: position.right + 'px', 'z-index': zIndex } " @mousedown.stop="startDrag" @mouseup.stop="stopDrag">
<div class="handle" @mousedown.stop="startDrag" @mouseup.stop="stopDrag">Drag Me</div>
<img v-if="isAudioActive && imageDataUrl != null" :src="imageDataUrl" alt="Spectrogram" width="300" height="300" />
<div class="controls">
<button v-if="!isAudioActive" class="w-full bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded" @click="startAudioStream">Activate Audio</button>
<button v-if="isAudioActive" class="w-full bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded" @click="stopAudioStream">Deactivate Audio</button>
<button v-if="!isAudioActive" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded" @click="startAudioStream"><i data-feather="mic"></i> </button>
<button v-if="isAudioActive" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded" @click="stopAudioStream"><i data-feather="mic"></i></button>
<span v-if="isAudioActive">FPS: {{ frameRate }}</span>
</div>
</div>
</template>
<script>
import socket from '@/services/websocket.js';
export default {
data() {
return {
isAudioActive: false,
imageDataUrl: null
};
import socket from '@/services/websocket.js';
import feather from 'feather-icons'
import { nextTick } from 'vue'
export default {
data() {
return {
isAudioActive: false,
imageDataUrl: null,
isDragging: false,
position: { bottom: 0, right: 0 },
dragStart: { x: 0, y: 0 },
zIndex: 0, // Add a data property for z-index
frameRate: 0,
frameCount: 0,
lastFrameTime: Date.now(),
};
},
methods: {
startAudioStream() {
this.isAudioActive = true;
socket.emit('start_audio_stream');
nextTick(() => {
feather.replace()
})
},
methods: {
startAudioStream() {
this.isAudioActive = true;
socket.emit('start_audio_stream');
},
stopAudioStream() {
this.isAudioActive = false;
this.imageData = null;
socket.emit('stop_audio_stream');
stopAudioStream() {
this.isAudioActive = false;
this.imageDataUrl = null;
socket.emit('stop_audio_stream');
nextTick(() => {
feather.replace()
})
},
startDrag(event) {
this.isDragging = true;
this.zIndex = 5001; // Increase z-index when dragging starts
this.dragStart.x = event.clientX;
this.dragStart.y = event.clientY;
document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.stopDrag);
},
drag(event) {
if (this.isDragging) {
const deltaX = event.clientX - this.dragStart.x;
const deltaY = event.clientY - this.dragStart.y;
this.position.bottom -= deltaY;
this.position.right -= deltaX;
this.dragStart.x = event.clientX;
this.dragStart.y = event.clientY;
}
},
mounted() {
socket.on('update_spectrogram', (imageBase64) => {
if (this.isAudioActive) {
this.imageDataUrl = 'data:image/jpeg;base64,' + imageBase64;
}
});
stopDrag() {
this.isDragging = false;
this.zIndex = 0; // Reset z-index when dragging stops
document.removeEventListener('mousemove', this.drag);
document.removeEventListener('mouseup', this.stopDrag);
}
};
</script>
<style>
.floating-frame {
margin: 15px;
float: left;
width: 800px;
height: auto;
border: 1px solid #000;
border-radius: 4px;
overflow: hidden;
z-index: 5000;
},
mounted() {
feather.replace();
socket.on('update_spectrogram', (imageBase64) => {
if (this.isAudioActive) {
this.imageDataUrl = 'data:image/jpeg;base64,' + imageBase64;
this.frameCount++;
const now = Date.now();
const delta = now - this.lastFrameTime;
if (delta >= 1000) { // Calculate FPS every second
this.frameRate = this.frameCount;
this.frameCount = 0;
this.lastFrameTime = now;
}
}
});
}
.floating-frame img {
width: 100%;
height: auto;
}
.controls {
margin-top: 10px;
}
</style>
};
</script>
<style>
.floating-frame {
margin: 15px;
float: left;
height: auto;
border: 1px solid #000;
border-radius: 4px;
overflow: hidden;
z-index: 5000;
position: fixed;
cursor: move;
bottom: 0;
right: 0;
}
.handle {
width: 100%;
height: 20px;
background: #ccc;
cursor: move;
text-align: center;
}
.floating-frame img {
width: 100%;
height: auto;
}
.controls {
margin-top: 10px;
}
</style>

View File

@ -288,7 +288,6 @@
</div>
</form>
</div>
<Toast ref="toast"/>
<UniversalForm ref="universalForm" class="z-20" />
</template>
<style scoped>
@ -331,7 +330,6 @@ import InteractiveMenu from '@/components/InteractiveMenu.vue';
import { useStore } from 'vuex'; // Import the useStore function
import { inject } from 'vue';
import socket from '@/services/websocket.js'
import Toast from '../components/Toast.vue'
import UniversalForm from '../components/UniversalForm.vue';
import modelImgPlaceholder from "../assets/default_model.png"
console.log("modelImgPlaceholder:",modelImgPlaceholder)
@ -347,7 +345,6 @@ export default {
},
components: {
Toast,
UniversalForm,
MountedPersonalities,
MountedPersonalitiesList,
@ -441,17 +438,17 @@ export default {
if (response && response.data) {
console.log('binding set with new settings', response.data)
this.$refs.toast.showToast("Binding settings updated successfully!", 4, true)
this.$store.state.toast.showToast("Binding settings updated successfully!", 4, true)
} else {
this.$refs.toast.showToast("Did not get binding settings responses.\n" + response, 4, false)
this.$store.state.toast.showToast("Did not get binding settings responses.\n" + response, 4, false)
this.isLoading = false
}
})
} catch (error) {
this.$refs.toast.showToast("Did not get binding settings responses.\n Endpoint error: " + error.message, 4, false)
this.$store.state.toast.showToast("Did not get binding settings responses.\n Endpoint error: " + error.message, 4, false)
this.isLoading = false
}
@ -459,7 +456,7 @@ export default {
})
} else {
this.$refs.toast.showToast("Binding has no settings", 4, false)
this.$store.state.toast.showToast("Binding has no settings", 4, false)
this.isLoading = false
}
@ -468,7 +465,7 @@ export default {
} catch (error) {
this.isLoading = false
this.$refs.toast.showToast("Could not open binding settings. Endpoint error: " + error.message, 4, false)
this.$store.state.toast.showToast("Could not open binding settings. Endpoint error: " + error.message, 4, false)
}
},
async unmountPersonality(pers) {
@ -480,7 +477,7 @@ export default {
if (res.status) {
this.$store.state.config.personalities = res.personalities
this.$refs.toast.showToast("Personality unmounted", 4, true)
this.$store.state.toast.showToast("Personality unmounted", 4, true)
//pers.isMounted = false
this.$store.dispatch('refreshMountedPersonalities');
@ -491,12 +488,12 @@ export default {
// const res2 = await this.select_personality(lastPers.personality)
const res2 = await this.select_personality(pers.personality)
if (res2.status) {
this.$refs.toast.showToast("Selected personality:\n" + lastPers.name, 4, true)
this.$store.state.toast.showToast("Selected personality:\n" + lastPers.name, 4, true)
}
} else {
this.$refs.toast.showToast("Could not unmount personality\nError: " + res.error, 4, false)
this.$store.state.toast.showToast("Could not unmount personality\nError: " + res.error, 4, false)
}
this.loading = false
@ -531,7 +528,7 @@ export default {
if (pers) {
if (pers.selected) {
this.$refs.toast.showToast("Personality already selected", 4, true)
this.$store.state.toast.showToast("Personality already selected", 4, true)
return
}
@ -545,10 +542,10 @@ export default {
const res = await this.select_personality(pers)
console.log('pers is mounted', res)
if (res && res.status && res.active_personality_id > -1) {
this.$refs.toast.showToast("Selected personality:\n" + pers.name, 4, true)
this.$store.state.toast.showToast("Selected personality:\n" + pers.name, 4, true)
} else {
this.$refs.toast.showToast("Error on select personality:\n" + pers.name, 4, false)
this.$store.state.toast.showToast("Error on select personality:\n" + pers.name, 4, false)
}
} else {
@ -623,10 +620,10 @@ export default {
console.log(response);
await this.$store.dispatch('refreshConfig');
await this.$store.dispatch('refreshModels');
this.$refs.toast.showToast(`Model changed to ${this.currentModel.name}`,4,true)
this.$store.state.toast.showToast(`Model changed to ${this.currentModel.name}`,4,true)
this.selecting_model=false
}).catch(err=>{
this.$refs.toast.showToast(`Error ${err}`,4,true)
this.$store.state.toast.showToast(`Error ${err}`,4,true)
this.selecting_model=false
});
@ -671,7 +668,7 @@ export default {
console.log('File sent successfully');
this.isFileSentList[this.filesList.length-1]=true;
console.log(this.isFileSentList)
this.onShowToastMessage("File uploaded successfully",4,true);
this.$store.state.toast.showToast("File uploaded successfully",4,true);
this.loading = false
next();
}

View File

@ -1,63 +0,0 @@
<template>
<div class="floating-frame">
<img v-if="isVideoActive" :src="imageDataUrl" alt="Webcam Frame" width="300" height="300" />
<div class="controls">
<button v-if="!isVideoActive" class="w-full bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded" @click="startVideoStream">Activate Video</button>
<button v-if="isVideoActive" class="w-full bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded" @click="stopVideoStream">Deactivate Video</button>
</div>
</div>
</template>
<script>
import socket from '@/services/websocket.js';
export default {
data() {
return {
isVideoActive: false,
imageDataUrl: null
};
},
methods: {
startVideoStream() {
this.isVideoActive = true;
socket.emit('start_webcam_video_stream');
},
stopVideoStream() {
this.isVideoActive = false;
this.imageData = null;
socket.emit('stop_webcam_video_stream');
}
},
mounted() {
socket.on('image', (imageBase64) => {
if (this.isVideoActive) {
this.imageDataUrl = 'data:image/jpeg;base64,' + imageBase64;
}
});
}
};
</script>
<style>
.floating-frame {
margin: 15px;
float: left;
width: 200px;
height: auto;
border: 1px solid #000;
border-radius: 4px;
overflow: hidden;
z-index: 5000;
}
.floating-frame img {
width: 100%;
height: auto;
}
.controls {
margin-top: 10px;
}
</style>

View File

@ -2,39 +2,33 @@
<div class="absolute bottom-16 right-2 z-20 flex flex-col gap-3 min-w-[300px]">
<TransitionGroup name="toastItem" tag="div">
<div v-for=" t in toastArr" :key="t.id" class="relative">
<div class="flex flex-row items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800"
role="alert">
<div class="flex flex-row flex-grow items-center">
<slot>
<div class="flex flex-row items-center w-full p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" role="alert">
<div class="flex flex-row flex-grow items-center h-auto">
<div v-if="t.log_type==0"
class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-red-500 bg-red-100 rounded-lg dark:bg-red-800 dark:text-red-200">
<i data-feather="x"></i>
<span class="sr-only">Cross icon</span>
</div>
<div v-if="t.log_type==1"
class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
<i data-feather="check"></i>
<span class="sr-only">Check icon</span>
</div>
<div v-if="t.log_type==2"
class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-blue-500 bg-blue-100 rounded-lg dark:bg-blue-800 dark:text-blue-200">
<i data-feather="info"></i>
<span class="sr-only"></span>
</div>
<div v-if="t.log_type==3"
class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-orange-500 bg-orange-100 rounded-lg dark:bg-orange-800 dark:text-orange-200">
<i data-feather="alert-triangle"></i>
<span class="sr-only"></span>
</div>
<div class="ml-3 text-sm font-normal whitespace-pre-wrap line-clamp-3 max-w-xs max-h-[400px] overflow-auto break-words" :title="t.message">{{ t.message }}</div>
<div v-if="t.log_type==0"
class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-red-500 bg-red-100 rounded-lg dark:bg-red-800 dark:text-red-200">
<i data-feather="x"></i>
<span class="sr-only">Cross icon</span>
</div>
<div v-if="t.log_type==1"
class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
<i data-feather="check"></i>
<span class="sr-only">Check icon</span>
</div>
<div v-if="t.log_type==2"
class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-blue-500 bg-blue-100 rounded-lg dark:bg-blue-800 dark:text-blue-200">
<i data-feather="info"></i>
<span class="sr-only"></span>
</div>
<div v-if="t.log_type==3"
class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-orange-500 bg-orange-100 rounded-lg dark:bg-orange-800 dark:text-orange-200">
<i data-feather="alert-triangle"></i>
<span class="sr-only"></span>
</div>
<div class="ml-3 text-sm font-normal whitespace-pre-wrap line-clamp-3" :title="t.message">{{ t.message }}</div>
</slot>
</div>
<div class="flex ">
<button type="button" @click.stop="copyToClipBoard(t.message)" title="Copy message"
class=" bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700">
<span class="sr-only">Copy message</span>

View File

@ -134,6 +134,9 @@ export default {
},
watch:{
isConnected(){
if (!this.isConnected){
this.$store.state.toast.showToast("Server suddenly disconnected. Please reboot the server", 410, false)
}
nextTick(() => {
feather.replace()
})

View File

@ -0,0 +1,131 @@
<template>
<div class="floating-frame bg-white" :style="{ bottom: position.bottom + 'px', right: position.right + 'px', 'z-index': zIndex }" @mousedown.stop="startDrag" @mouseup.stop="stopDrag">
<div class="handle" @mousedown.stop="startDrag" @mouseup.stop="stopDrag">Drag Me</div>
<img v-if="isVideoActive && imageDataUrl!=null" :src="imageDataUrl" alt="Webcam Frame" width="300" height="300" />
<p v-if="isVideoActive && imageDataUrl==null" :src="imageDataUrl" alt="Webcam Frame" width="300" height="300" >Loading. Please wait...</p>
<div class="controls">
<button v-if="!isVideoActive" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded" @click="startVideoStream"><i data-feather='video'></i></button>
<button v-if="isVideoActive" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded" @click="stopVideoStream"><i data-feather='video'></i></button>
<span v-if="isVideoActive">FPS: {{ frameRate }}</span>
</div>
</div>
</template>
<script>
import socket from '@/services/websocket.js';
import feather from 'feather-icons'
import { nextTick } from 'vue'
export default {
data() {
return {
isVideoActive: false,
imageDataUrl: null,
isDragging: false,
position: { bottom: 0, right: 0 },
dragStart: { x: 0, y: 0 },
zIndex: 0, // Add a data property for z-index
frameRate: 0,
frameCount: 0,
lastFrameTime: Date.now(),
};
},
methods: {
startVideoStream() {
this.isVideoActive = true;
socket.emit('start_webcam_video_stream');
nextTick(() => {
feather.replace()
})
},
stopVideoStream() {
this.isVideoActive = false;
this.imageData = null;
socket.emit('stop_webcam_video_stream');
nextTick(() => {
feather.replace()
})
},
startDrag(event) {
this.isDragging = true;
this.zIndex = 5001; // Increase z-index when dragging starts
this.dragStart.x = event.clientX;
this.dragStart.y = event.clientY;
document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.stopDrag);
},
drag(event) {
if (this.isDragging) {
const deltaX = event.clientX - this.dragStart.x;
const deltaY = event.clientY - this.dragStart.y;
this.position.bottom -= deltaY;
this.position.right -= deltaX;
this.dragStart.x = event.clientX;
this.dragStart.y = event.clientY;
}
},
stopDrag() {
this.isDragging = false;
this.zIndex = 0; // Reset z-index when dragging stops
document.removeEventListener('mousemove', this.drag);
document.removeEventListener('mouseup', this.stopDrag);
}
},
mounted() {
feather.replace();
socket.on('video_stream_image', (imageBase64) => {
if (this.isVideoActive) {
this.imageDataUrl = 'data:image/jpeg;base64,' + imageBase64;
this.frameCount++;
const now = Date.now();
const delta = now - this.lastFrameTime;
if (delta >= 1000) { // Calculate FPS every second
this.frameRate = this.frameCount;
this.frameCount = 0;
this.lastFrameTime = now;
}
}
});
}
};
</script>
<style>
.floating-frame {
margin: 15px;
float: left;
height: auto;
border: 1px solid #000;
border-radius: 4px;
overflow: hidden;
z-index: 5000;
position: fixed;
cursor: move;
bottom: 0;
right: 0;
}
.handle {
width: 100%;
height: 20px;
background: #ccc;
cursor: move;
text-align: center;
}
.floating-frame img {
width: 100%;
height: auto;
}
.controls {
margin-top: 10px;
}
/* Add a container for the floating frames and apply flexbox */
.container {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
}
</style>

View File

@ -1,30 +1,30 @@
<template>
<div class="flex-row w-[800]px y-overflow scrollbar-thin scrollbar-track-bg-light-tone scrollbar-thumb-bg-light-tone-panel hover:scrollbar-thumb-primary dark:scrollbar-track-bg-dark-tone dark:scrollbar-thumb-bg-dark-tone-panel dark:hover:scrollbar-thumb-primary active:scrollbar-thumb-secondary">
<div ref="webglContainer">
<div class="flex-col y-overflow scrollbar-thin scrollbar-track-bg-light-tone scrollbar-thumb-bg-light-tone-panel hover:scrollbar-thumb-primary dark:scrollbar-track-bg-dark-tone dark:scrollbar-thumb-bg-dark-tone-panel dark:hover:scrollbar-thumb-primary active:scrollbar-thumb-secondary">
<div v-if="!activePersonality || !activePersonality.scene_path" class="text-center">
<!-- Display text when there's no scene_path or empty avatar -->
Personality does not have a 3d avatar.
</div>
<div v-if="!activePersonality || (!activePersonality.avatar || activePersonality.avatar === '')" class="text-center">
Personality does not have an avatar.
</div>
<FloatingFrame />
<AudioFrame />
<div class="floating-frame2">
<div v-html="htmlContent"></div>
</div>
</div>
</div>
<div ref="webglContainer">
</div>
<div class="flex-col y-overflow scrollbar-thin scrollbar-track-bg-light-tone scrollbar-thumb-bg-light-tone-panel hover:scrollbar-thumb-primary dark:scrollbar-track-bg-dark-tone dark:scrollbar-thumb-bg-dark-tone-panel dark:hover:scrollbar-thumb-primary active:scrollbar-thumb-secondary">
<div v-if="!activePersonality || !activePersonality.scene_path" class="text-center">
<!-- Display text when there's no scene_path or empty avatar -->
Personality does not have a 3d avatar.
</div>
<div v-if="!activePersonality || (!activePersonality.avatar || activePersonality.avatar === '')" class="text-center">
Personality does not have an avatar.
</div>
<div class="floating-frame2">
<div v-html="htmlContent"></div>
</div>
</div>
<VideoFrame ref="video_frame"/>
<AudioFrame ref="audio_frame"/>
</template>
<script>
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { TextureLoader } from 'three';
import FloatingFrame from '@/components/FloatingFrame.vue';
import VideoFrame from '@/components/VideoFrame.vue';
import AudioFrame from '@/components/AudioFrame.vue';
import feather from 'feather-icons'
import { nextTick } from 'vue'
export default {
@ -40,7 +40,7 @@
},
},
components: {
FloatingFrame,
VideoFrame,
AudioFrame
},
computed: {
@ -61,6 +61,11 @@
console.log("Personality:", this.personality)
this.initWebGLScene();
this.updatePersonality();
nextTick(() => {
feather.replace()
})
this.$refs.video_frame.position = { bottom: 0, right: 0 }
this.$refs.audio_frame.position = { bottom: 0, right: 100 }
},
beforeDestroy() {
// Clean up WebGL resources here
@ -165,7 +170,6 @@
}
.floating-frame2 {
margin: 15px;
float: left;
width: 800px;
height: auto;
border: 1px solid #000;

@ -1 +1 @@
Subproject commit 0d8341c54937d57513dfae062fd9731812e96f5e
Subproject commit 97ec1416d0af1377a8cfc96d9f55cb3f31681293