moved around controls and lots of housekeeping

This commit is contained in:
AndzejsP 2023-05-05 12:53:54 +03:00
parent f7530e7bc4
commit 6baf5f9823

View File

@ -1,73 +1,77 @@
<template> <template>
<div <div
class="overflow-y-scroll flex flex-col no-scrollbar shadow-lg min-w-[24rem] max-w-[24rem] bg-bg-light-tone dark:bg-bg-dark-tone "> class="overflow-y-scroll flex flex-col no-scrollbar shadow-lg min-w-[24rem] max-w-[24rem] bg-bg-light-tone dark:bg-bg-dark-tone">
<!-- LEFT SIDE PANEL --> <!-- LEFT SIDE PANEL -->
<div <div
class="z-10 sticky top-0 flex-row p-2 flex items-center gap-3 flex-0 bg-bg-light-tone dark:bg-bg-dark-tone mt-0 px-4 shadow-md"> class="z-10 sticky top-0 flex-col bg-bg-light-tone dark:bg-bg-dark-tone shadow-md">
<!-- CONTROL PANEL -->
<button class=" text-2xl hover:text-secondary duration-75 active:scale-90 " title="Create new discussion"
type="button" @click="createNewDiscussion()">
<i data-feather="plus"></i>
</button>
<button class=" text-2xl hover:text-secondary duration-75 active:scale-90 "
title="Reset database, remove all discussions">
<i data-feather="refresh-ccw"></i>
</button>
<button class=" text-2xl hover:text-secondary duration-75 active:scale-90 " title="Export database"
type="button">
<i data-feather="database"></i>
</button>
<button class=" text-2xl hover:text-secondary duration-75 active:scale-90 rotate-90"
title="Export discussion to a file" type="button">
<i data-feather="log-out"></i>
</button>
<!-- SEARCH BAR --> <!-- SEARCH BAR -->
<form> <form class="flex-row p-4 pb-0 items-center gap-3 flex-0 w-full">
<div class="relative"> <div class="relative">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<div class="scale-75 "> <div class="scale-75">
<i data-feather="search"></i> <i data-feather="search"></i>
</div> </div>
</div> </div>
<div class="absolute inset-y-0 right-0 flex items-center pr-3 "> <div class="absolute inset-y-0 right-0 flex items-center pr-3">
<div class=" hover:text-secondary duration-75 active:scale-90 " <div class="hover:text-secondary duration-75 active:scale-90"
:class="filterTitle ? 'visible' : 'invisible'" title="Clear" @click="filterTitle = ''"> :class="filterTitle ? 'visible' : 'invisible'" title="Clear" @click="filterTitle = ''">
<i data-feather="x"></i> <i data-feather="x"></i>
</div> </div>
</div> </div>
<input type="search" id="default-search" <input type="search" id="default-search"
class="block w-full p-2 pl-10 pr-10 text-sm border border-gray-300 rounded-lg bg-bg-light focus:ring-secondary focus:border-secondary dark:bg-bg-dark dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-secondary dark:focus:border-secondary " class="block w-full p-2 pl-10 pr-10 text-sm border border-gray-300 rounded-lg bg-bg-light focus:ring-secondary focus:border-secondary dark:bg-bg-dark dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-secondary dark:focus:border-secondary"
placeholder="Search..." title="Filter discussions by title" v-model="filterTitle" placeholder="Search..." title="Filter discussions by title" v-model="filterTitle"
@input="filterDiscussions()"> @input="filterDiscussions()" />
</div> </div>
</form> </form>
<!-- CONTROL PANEL -->
<div class="flex-row p-4 flex items-center gap-3 flex-0">
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Create new discussion"
type="button" @click="createNewDiscussion()">
<i data-feather="plus"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Edit discussion list"
type="button" @click="">
<i data-feather="check-square"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90"
title="Reset database, remove all discussions">
<i data-feather="refresh-ccw"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Export database" type="button">
<i data-feather="database"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90 rotate-90"
title="Export discussion to a file" type="button">
<i data-feather="log-out"></i>
</button>
</div> </div>
<div class="relative overflow-y-scroll no-scrollbar">
</div>
<div class="relative overflow-y-scroll no-scrollbar">
<!-- DISCUSSION LIST --> <!-- DISCUSSION LIST -->
<div class="mx-4 flex-grow" :class="filterInProgress ? 'opacity-20 pointer-events-none' : ''"> <div class="mx-4 flex-grow" :class="filterInProgress ? 'opacity-20 pointer-events-none' : ''">
<Discussion v-for="(item, index) in list" :key="index" :id="item.id" :title="item.title" <Discussion v-for="(item, index) in list" :key="index" :id="item.id" :title="item.title"
ref="discussionList" :selected="currentDiscussion.id == item.id" ref="discussionList" :selected="currentDiscussion.id == item.id"
:loading="currentDiscussion.id == item.id && loading" @select="selectDiscussion(item)" :loading="currentDiscussion.id == item.id && loading" @select="selectDiscussion(item)"
@delete="deleteDiscussion(item.id)" @editTitle="editTitle" /> @delete="deleteDiscussion(item.id)" @editTitle="editTitle" />
<div v-if="list.length < 1" <div v-if="list.length < 1"
class=" gap-2 py-2 my-2 hover:shadow-md hover:bg-primary-light dark:hover:bg-primary rounded-md p-2 duration-75 group cursor-pointer"> class="gap-2 py-2 my-2 hover:shadow-md hover:bg-primary-light dark:hover:bg-primary rounded-md p-2 duration-75 group cursor-pointer">
<p class="px-3">No discussions are found</p> <p class="px-3">No discussions are found</p>
</div> </div>
<div <div
class="sticky bottom-0 bg-gradient-to-t pointer-events-none from-bg-light-tone dark:from-bg-dark-tone flex height-64 "> class="sticky bottom-0 bg-gradient-to-t pointer-events-none from-bg-light-tone dark:from-bg-dark-tone flex height-64">
<!-- FADING DISCUSSION LIST END ELEMENT --> <!-- FADING DISCUSSION LIST END ELEMENT -->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="overflow-y-scroll flex flex-col no-scrollbar flex-grow " <div class="overflow-y-scroll flex flex-col no-scrollbar flex-grow"
:class="loading ? 'opacity-20 pointer-events-none' : ''"> :class="loading ? 'opacity-20 pointer-events-none' : ''">
<!-- CHAT AREA --> <!-- CHAT AREA -->
<div> <div>
@ -76,11 +80,8 @@
<WelcomeComponent v-if="discussionArr.length < 1" /> <WelcomeComponent v-if="discussionArr.length < 1" />
<ChatBox v-if="discussionArr.length > 0" @messageSentEvent="sendMsg" /> <ChatBox v-if="discussionArr.length > 0" @messageSentEvent="sendMsg" :loading="isGenerating"/>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
@ -90,53 +91,45 @@
</style> </style>
<script> <script>
export default { export default {
setup() { },
setup() {
},
data() { data() {
return { return {
list: [], // Discussion list list: [], // Discussion list
tempList: [], // Copy of Discussion list (used for keeping the original list during filtering discussions/searching action) tempList: [], // Copy of Discussion list (used for keeping the original list during filtering discussions/searching action)
currentDiscussion: {}, // Current/selected discussion id currentDiscussion: {}, // Current/selected discussion id
discussionArr: [], discussionArr: [],
loading: false, loading: false,
filterTitle: "", filterTitle: '',
filterInProgress: false, filterInProgress: false,
isCreated:false isCreated: false,
isGenerating:false
} }
}, },
methods: { methods: {
async list_discussions() { async list_discussions() {
try { try {
const res = await axios.get("/list_discussions"); const res = await axios.get('/list_discussions')
if (res) { if (res) {
this.list = res.data this.list = res.data
this.tempList = this.list this.tempList = this.list
return res.data return res.data
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
return [] return []
} }
}, },
async load_discussion(id) { async load_discussion(id) {
try { try {
if (id) { if (id) {
this.loading = true this.loading = true
const res = await axios.post("/load_discussion", { const res = await axios.post('/load_discussion', {
id: id id: id
}); })
this.loading = false this.loading = false
if (res) { if (res) {
// Filter out the user and bot entries // Filter out the user and bot entries
this.discussionArr = res.data.filter((item) => item.type == 0) this.discussionArr = res.data.filter((item) => item.type == 0)
const lastMessage = this.discussionArr[this.discussionArr.length - 1] const lastMessage = this.discussionArr[this.discussionArr.length - 1]
@ -144,26 +137,21 @@ export default {
nextTick(() => { nextTick(() => {
const selectedElement = document.getElementById('msg-' + lastMessage.id) const selectedElement = document.getElementById('msg-' + lastMessage.id)
this.scrollToElement(selectedElement) this.scrollToElement(selectedElement)
}) })
} }
} }
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
this.loading = false this.loading = false
} }
}, },
async new_discussion(title) { async new_discussion(title) {
try { try {
const res = await axios.get("/new_discussion", { params: { title: title } }); const res = await axios.get('/new_discussion', { params: { title: title } })
if (res) { if (res) {
return res.data return res.data
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
@ -174,83 +162,71 @@ export default {
try { try {
if (id) { if (id) {
this.loading = true this.loading = true
const res = await axios.post("/delete_discussion", { const res = await axios.post('/delete_discussion', {
id: id id: id
}); })
this.loading = false this.loading = false
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
this.loading = false this.loading = false
} }
}, },
async edit_title(discussion_id, new_title) { async edit_title(discussion_id, new_title) {
try { try {
if (discussion_id) { if (discussion_id) {
this.loading = true this.loading = true
const res = await axios.post("/edit_title", { const res = await axios.post('/edit_title', {
id: discussion_id, id: discussion_id,
title: new_title title: new_title
}); })
this.loading = false this.loading = false
if (res.status == 200) { if (res.status == 200) {
const index = this.list.findIndex(x => x.id == discussion_id); const index = this.list.findIndex((x) => x.id == discussion_id)
const discussionItem = this.list[index] const discussionItem = this.list[index]
discussionItem.title = new_title discussionItem.title = new_title
this.tempList = this.list this.tempList = this.list
} }
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
this.loading = false this.loading = false
} }
}, },
filterDiscussions() { filterDiscussions() {
// Search bar in for filtering discussions by title (serch) // Search bar in for filtering discussions by title (serch)
if (!this.filterInProgress) { if (!this.filterInProgress) {
this.filterInProgress = true this.filterInProgress = true
setTimeout(() => { setTimeout(() => {
this.list = this.tempList.filter((item) => item.title.includes(this.filterTitle)) this.list = this.tempList.filter((item) => item.title.includes(this.filterTitle))
this.filterInProgress = false this.filterInProgress = false
}, 100) }, 100)
} }
}, },
async selectDiscussion(item) { async selectDiscussion(item) {
// When discussion is selected it loads the discussion array // When discussion is selected it loads the discussion array
this.currentDiscussion = item this.currentDiscussion = item
localStorage.setItem("selected_discussion", this.currentDiscussion.id) localStorage.setItem('selected_discussion', this.currentDiscussion.id)
await this.load_discussion(item.id) await this.load_discussion(item.id)
if (this.discussionArr.length > 1) { if (this.discussionArr.length > 1) {
if (this.currentDiscussion.title === '' || this.currentDiscussion.title === null) {
if (this.currentDiscussion.title === "" || this.currentDiscussion.title === null) {
this.changeTitleUsingUserMSG(this.currentDiscussion.id, this.discussionArr[1].content) this.changeTitleUsingUserMSG(this.currentDiscussion.id, this.discussionArr[1].content)
} }
} }
nextTick(() => { nextTick(() => {
const selectedDisElement = document.getElementById('dis-' + item.id) const selectedDisElement = document.getElementById('dis-' + item.id)
this.scrollToElement(selectedDisElement) this.scrollToElement(selectedDisElement)
}) })
}, },
scrollToElement(el) { scrollToElement(el) {
//console.log("scroll", el.id)
if (el) { if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' })
el.scrollIntoView({ behavior: 'smooth', block: "center", inline: "nearest" });
} }
}, },
createMsg(msgObj) { createMsg(msgObj) {
@ -262,23 +238,22 @@ export default {
id: msgObj.message, id: msgObj.message,
//parent: 10, //parent: 10,
rank: 0, rank: 0,
sender: msgObj.user, sender: msgObj.user
//type: 0 //type: 0
} }
this.discussionArr.push(usrMessage) this.discussionArr.push(usrMessage)
nextTick(() => { nextTick(() => {
const userMsgElement = document.getElementById('msg-' + msgObj.message) const userMsgElement = document.getElementById('msg-' + msgObj.message)
this.scrollToElement(userMsgElement) this.scrollToElement(userMsgElement)
}) })
// Create response message // Create response message
let responseMessage = { let responseMessage = {
content: "..typing", content: '..typing',
id: msgObj.response_id, id: msgObj.response_id,
//parent: 10, //parent: 10,
rank: 0, rank: 0,
sender: msgObj.bot, sender: msgObj.bot
//type: 0 //type: 0
} }
this.discussionArr.push(responseMessage) this.discussionArr.push(responseMessage)
@ -287,73 +262,63 @@ export default {
this.scrollToElement(responseMessageElement) this.scrollToElement(responseMessageElement)
}) })
if (this.currentDiscussion.title === "" || this.currentDiscussion.title === null) { if (this.currentDiscussion.title === '' || this.currentDiscussion.title === null) {
this.changeTitleUsingUserMSG(this.currentDiscussion.id, usrMessage.content) this.changeTitleUsingUserMSG(this.currentDiscussion.id, usrMessage.content)
} }
this.isGenerating=false
}, },
sendMsg(msg) { sendMsg(msg) {
// Sends message to backend // Sends message to backend
this.isGenerating=true
websocket.emit('generate_msg', { prompt: msg }); websocket.emit('generate_msg', { prompt: msg })
}, },
steamMessageContent(content) { steamMessageContent(content) {
// Streams response message content from backend // Streams response message content from backend
const lastMsg = this.discussionArr[this.discussionArr.length - 1] const lastMsg = this.discussionArr[this.discussionArr.length - 1]
lastMsg.content = content.data lastMsg.content = content.data
}, },
async changeTitleUsingUserMSG(id, msg) { async changeTitleUsingUserMSG(id, msg) {
// If discussion is untitled or title is null then it sets the title to first user message. // If discussion is untitled or title is null then it sets the title to first user message.
const index = this.list.findIndex(x => x.id == id); const index = this.list.findIndex((x) => x.id == id)
const discussionItem = this.list[index] const discussionItem = this.list[index]
if (msg) { if (msg) {
discussionItem.title = msg discussionItem.title = msg
this.tempList = this.list this.tempList = this.list
} }
await this.edit_title(id, msg) await this.edit_title(id, msg)
}, },
async createNewDiscussion() { async createNewDiscussion() {
// Creates new discussion on backend,
// Creates new discussion on backend, // gets new discussion list, selects
// gets new discussion list, selects // newly created discussion,
// newly created discussion,
// scrolls to the discussion // scrolls to the discussion
const res = await this.new_discussion() const res = await this.new_discussion()
await this.list_discussions() await this.list_discussions()
const index = this.list.findIndex(x => x.id == res.id); const index = this.list.findIndex((x) => x.id == res.id)
const discussionItem = this.list[index] const discussionItem = this.list[index]
this.selectDiscussion(discussionItem) this.selectDiscussion(discussionItem)
nextTick(() => { nextTick(() => {
const selectedDisElement = document.getElementById('dis-' + res.id) const selectedDisElement = document.getElementById('dis-' + res.id)
this.scrollToElement(selectedDisElement) this.scrollToElement(selectedDisElement)
}) })
}, },
loadLastUsedDiscussion() { loadLastUsedDiscussion() {
// Checks local storage for last selected discussion // Checks local storage for last selected discussion
const id = localStorage.getItem("selected_discussion") const id = localStorage.getItem('selected_discussion')
if (id) { if (id) {
const index = this.list.findIndex(x => x.id == id); const index = this.list.findIndex((x) => x.id == id)
const discussionItem = this.list[index] const discussionItem = this.list[index]
this.selectDiscussion(discussionItem) this.selectDiscussion(discussionItem)
} }
}, },
async deleteDiscussion(id) { async deleteDiscussion(id) {
// Deletes discussion from backend and frontend // Deletes discussion from backend and frontend
const index = this.list.findIndex(x => x.id == id); const index = this.list.findIndex((x) => x.id == id)
const discussionItem = this.list[index] const discussionItem = this.list[index]
discussionItem.loading = true discussionItem.loading = true
this.delete_discussion(id) this.delete_discussion(id)
@ -363,85 +328,81 @@ export default {
await this.list_discussions() await this.list_discussions()
}, },
async editTitle(newTitleObj) { async editTitle(newTitleObj) {
//const index = this.$refs.discussionList.findIndex(x => x.id == newTitleObj.id); // const index = this.$refs.discussionList.findIndex(x => x.id == newTitleObj.id);
//const discussionItem = this.$refs.discussionList[index] // let discussionItem = this.$refs.discussionList[index]
// discussionItem.title = newTitleObj.title
//console.log(JSON.stringify(discussionItem)) //console.log(JSON.stringify(discussionItem))
//discussionItem.loading.value=true //discussionItem.loading.value=true
//console.log(discussionItem.title) //console.log(discussionItem.title)
const index = this.list.findIndex((x) => x.id == newTitleObj.id)
const discussionItem = this.list[index]
discussionItem.title = newTitleObj.title
await this.edit_title(newTitleObj.id, newTitleObj.title) await this.edit_title(newTitleObj.id, newTitleObj.title)
}
},
}, },
async created() { async created() {
// Constructor // Constructor
await this.list_discussions() await this.list_discussions()
this.loadLastUsedDiscussion() this.loadLastUsedDiscussion()
this.isCreated=true this.isCreated = true
nextTick(() => { nextTick(() => {
feather.replace() feather.replace()
}) })
// WebSocket responses // WebSocket responses
websocket.on("infos", this.createMsg) websocket.on('infos', this.createMsg)
websocket.on("message", this.steamMessageContent) websocket.on('message', this.steamMessageContent)
},
},activated(){ activated() {
// This lifecycle hook runs every time you switch from other page back to this page (vue-router) // This lifecycle hook runs every time you switch from other page back to this page (vue-router)
// To fix scrolling back to last message, this hook is needed. // To fix scrolling back to last message, this hook is needed.
// If anyone knows hor to fix scroll issue when changing pages, please do fix it :D // If anyone knows hor to fix scroll issue when changing pages, please do fix it :D
if(this.isCreated){ if (this.isCreated) {
this.loadLastUsedDiscussion() this.loadLastUsedDiscussion()
} }
}, },
components: { components: {
Discussion, Discussion,
Message, Message,
ChatBox, ChatBox,
WelcomeComponent, WelcomeComponent
},
}, watch: { watch: {
filterTitle(newVal, oldVal) { filterTitle(newVal, oldVal) {
if (newVal == "") { if (newVal == '') {
this.filterInProgress = true this.filterInProgress = true
this.list = this.tempList this.list = this.tempList
this.filterInProgress = false this.filterInProgress = false
} }
} }
} }
} }
</script> </script>
<script setup > <script setup>
import Discussion from '../components/Discussion.vue'; import Discussion from '../components/Discussion.vue'
import Message from '../components/Message.vue'; import Message from '../components/Message.vue'
import ChatBox from '../components/ChatBox.vue' import ChatBox from '../components/ChatBox.vue'
import WelcomeComponent from '../components/WelcomeComponent.vue' import WelcomeComponent from '../components/WelcomeComponent.vue'
import feather from 'feather-icons' import feather from 'feather-icons'
import axios from "axios"; import axios from 'axios'
import { nextTick, onActivated } from 'vue'; import { nextTick, onActivated } from 'vue'
import websocket from '@/services/websocket.js'; import websocket from '@/services/websocket.js'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { initFlowbite } from 'flowbite' import { initFlowbite } from 'flowbite'
// initialize components based on data attribute selectors // initialize components based on data attribute selectors
onMounted(() => { onMounted(() => {
initFlowbite(); initFlowbite()
}) })
axios.defaults.baseURL = import.meta.env.VITE_GPT4ALL_API_BASEURL; axios.defaults.baseURL = import.meta.env.VITE_GPT4ALL_API_BASEURL
</script>
</script>