mirror of
https://github.com/ParisNeo/lollms-webui.git
synced 2025-04-08 03:14:17 +00:00
Upgraded ui
This commit is contained in:
parent
8865a3318b
commit
4b98d5c0da
@ -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
2
app.py
@ -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
|
8
web/dist/assets/index-44523b05.css
vendored
8
web/dist/assets/index-44523b05.css
vendored
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
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
4
web/dist/index.html
vendored
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
})
|
||||
|
131
web/src/components/VideoFrame.vue
Normal file
131
web/src/components/VideoFrame.vue
Normal 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>
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user