edit title, new discussion, remove discussion

This commit is contained in:
AndzejsP 2023-05-04 17:44:02 +03:00
parent 3179e69a4c
commit aea170cdce
5 changed files with 230 additions and 107 deletions

11
app.py
View File

@ -108,6 +108,7 @@ class Gpt4AllWebUI(GPT4AllAPI):
self.add_endpoint("/stop_gen", "stop_gen", self.stop_gen, methods=["GET"])
self.add_endpoint("/rename", "rename", self.rename, methods=["POST"])
self.add_endpoint("/edit_title", "edit_title", self.edit_title, methods=["POST"])
self.add_endpoint(
"/load_discussion", "load_discussion", self.load_discussion, methods=["POST"]
)
@ -370,7 +371,15 @@ class Gpt4AllWebUI(GPT4AllAPI):
title = data["title"]
self.current_discussion.rename(title)
return "renamed successfully"
def edit_title(self):
data = request.get_json()
title = data["title"]
discussion_id = data["id"]
self.current_discussion = Discussion(discussion_id, self.db)
self.current_discussion.rename(title)
return "title renamed successfully"
def load_discussion(self):
data = request.get_json()
if "id" in data:

3
web/.gitignore vendored
View File

@ -25,3 +25,6 @@ coverage
*.njsproj
*.sln
*.sw?
# REST Client files (VSCODE extension for making GET POST requests easy and fst from text files)
*.http

View File

@ -1,22 +1,54 @@
<template>
<div :class="selected ? 'bg-bg-light-discussion dark:bg-bg-dark-discussion shadow-md' : ''"
class="container flex flex-col sm:flex-row item-center shadow-sm 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="container flex flex-col sm:flex-row item-center shadow-sm 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"
:id="'dis-' + id" @click.stop="selectEvent()">
<!-- INDICATOR FOR SELECTED ITEM -->
<div v-if="selected" class="items-center inline-block min-h-full w-2 rounded-xl self-stretch "
:class="loading ? 'animate-bounce bg-accent ' : ' bg-secondary '"></div>
<div v-else class="items-center inline-block min-h-full w-2 rounded-xl self-stretch"></div>
<div v-if="!selected" class="items-center inline-block min-h-full w-2 rounded-xl self-stretch"></div>
<!-- TITLE -->
<p class="truncate w-auto">{{ title ? title === "untitled" ? "New discussion" : title : "New discussion" }}</p>
<p v-if="!editTitle" :title="title" class="truncate w-full">{{ title ? title === "untitled" ? "New discussion" : title : "New discussion" }}</p>
<input v-if="editTitle" type="text" id="title-box" class="bg-bg-light dark:bg-bg-dark rounded-md border-0 w-full -m-1 p-1"
:value="title" required @input="chnageTitle($event.target.value)" @click.stop>
<!-- CONTROL BUTTONS -->
<div class="flex items-center gap-3 flex-1 max-h-6">
<div class="flex gap-3 flex-1 items-center justify-end invisible group-hover:visible duration-75">
<div class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Edit title">
<div class="flex items-center flex-1 max-h-6">
<!-- DELETE CONFIRM -->
<div v-if="showConfirmation && !editTitleMode" class="flex gap-3 flex-1 items-center justify-end duration-75">
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Confirm removal"
type="button" @click.stop="deleteEvent()">
<i data-feather="check"></i>
</button>
<button class="text-2xl hover:text-red-600 duration-75 active:scale-90 " title="Cancel removal"
type="button" @click.stop="showConfirmation = false">
<i data-feather="x"></i>
</button>
</div>
<!-- EDIT TITLE CONFIRM -->
<div v-if="showConfirmation && editTitleMode" class="flex gap-3 flex-1 items-center justify-end duration-75">
<button class="text-2xl hover:text-red-600 duration-75 active:scale-90 " title="Discard title changes"
type="button" @click.stop="editTitleMode = false ">
<i data-feather="x"></i>
</button>
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Confirm title changes"
type="button" @click.stop="editTitleEvent()">
<i data-feather="check"></i>
</button>
</div>
<!-- EDIT AND REMOVE -->
<div v-if="!showConfirmation"
class="flex gap-3 flex-1 items-center justify-end invisible group-hover:visible duration-75">
<button class="text-2xl hover:text-secondary duration-75 active:scale-90" title="Edit title" type="button"
@click.stop="editTitleMode = true">
<i data-feather="edit-2"></i>
</div>
<div class="text-2xl hover:text-red-600 duration-75 active:scale-90" title="Remove discussion">
</button>
<button class="text-2xl hover:text-red-600 duration-75 active:scale-90 " title="Remove discussion"
type="button" @click.stop="showConfirmation = true">
<i data-feather="trash"></i>
</div>
</button>
</div>
</div>
</div>
@ -28,6 +60,7 @@ import feather from 'feather-icons'
export default {
name: 'Discussion',
emits: ['delete', 'select', 'editTitle'],
props: {
id: Number,
title: String,
@ -39,18 +72,53 @@ export default {
},
data() {
return {
showConfirmation: false,
editTitleMode: false,
editTitle: false,
newTitle: String,
}
},
methods: {
},
deleteEvent() {
this.showConfirmation = false
this.$emit("delete")
},
selectEvent() {
this.$emit("select")
},
editTitleEvent() {
this.editTitle= false
this.editTitleMode= false
this.showConfirmation = false
this.$emit("editTitle",
{
title: this.newTitle,
id: this.id
})
},
chnageTitle(text){
this.newTitle=text
}
},
mounted() {
this.newTitle= this.title
nextTick(() => {
feather.replace()
})
}, watch: {
showConfirmation() {
nextTick(() => {
feather.replace()
})
},
editTitleMode(newval) {
this.showConfirmation=newval
this.editTitle = newval
}
}
}
</script>

View File

@ -1,45 +0,0 @@
<script setup>
import { Modal } from 'flowbite-vue'
import { ref } from 'vue'
// const isShowModal = ref(false)
// function closeModal() {
// isShowModal.value = false
// }
// function showModal() {
// isShowModal.value = true
// }
</script>
<template>
<!-- <button @click="showModal" type="button"
class="mt-5 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
Show modal
</button> -->
<Modal :size="size" v-if="ShowModal" @close="closeModal()">
<template #header>
<div class="flex items-center text-lg">
<slot name="header"></slot>
</div>
</template>
<template #body>
<slot name="body"></slot>
</template>
<template #footer>
<slot name="footer"></slot>
</template>
</Modal>
</template>
<script>
export default {
name: 'ModalSimple',
props: {
ShowModal: Boolean
},methods:{
closeModal(){
this.ShowModal = false
}
},
}
</script>

View File

@ -6,7 +6,7 @@
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">
<!-- CONTROL PANEL -->
<button class=" text-2xl hover:text-secondary duration-75 active:scale-90 " title="Create new discussion"
type="button" @click="createNewDiscussionShow">
type="button" @click="createNewDiscussion()">
<i data-feather="plus"></i>
</button>
<button class=" text-2xl hover:text-secondary duration-75 active:scale-90 "
@ -43,33 +43,15 @@
@input="filterDiscussions()">
</div>
</form>
<!-- Create discussion -->
<ModalSimple :ShowModal="showCreateDiscussionModal">
<template v-slot:header>
<p>Create new discussion</p>
</template>
<template v-slot:body>
<div class="mb-6">
<label for="default-input" class="block mb-2 text-sm font-medium ">Enter discussion title:</label>
<input type="text" id="default-input"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
</div>
</template>
<template v-slot:footer>
<div class="flex justify-between">
<button type="button"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Create</button>
</div>
</template>
</ModalSimple>
</div>
<div class="relative overflow-y-scroll no-scrollbar">
<!-- DISCUSSION LIST -->
<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" :loading="currentDiscussion.id == item.id && loading"
@click="selectDiscussion(item)" />
@select="selectDiscussion(item)" @delete="deleteDiscussion(item.id)" @editTitle="editTitle" />
<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">
@ -93,7 +75,7 @@
<WelcomeComponent v-if="discussionArr.length < 1" />
<ChatBox v-if="discussionArr.length > 1" @messageSentEvent="sendMsg" />
<ChatBox v-if="discussionArr.length > 0" @messageSentEvent="sendMsg" />
</div>
@ -108,12 +90,6 @@
<script>
import axios from "axios";
import { nextTick } from 'vue'
axios.defaults.baseURL = import.meta.env.VITE_GPT4ALL_API_BASEURL // Use this for external development not for production
import websocket from '@/services/websocket.js';
export default {
setup() {
@ -139,7 +115,8 @@ export default {
const res = await axios.get("/list_discussions");
if (res) {
this.list = res.data
this.tempList = this.list
return res.data
}
@ -152,17 +129,68 @@ export default {
},
async load_discussion(id) {
try {
if (id) {
this.loading = true
const res = await axios.post("/load_discussion", {
id: id
});
this.loading = false
if (res) {
this.discussionArr = res.data
this.loading = false
this.discussionArr = res.data.filter((item) => item.sender != "conditionner") // Filter out the conditionner entries
}
}
} catch (error) {
console.log(error)
this.loading = false
}
},
async new_discussion(title) {
try {
const res = await axios.get("/new_discussion", { params: { title: title } });
if (res) {
return res.data
}
} catch (error) {
console.log(error)
return {}
}
},
async delete_discussion(id) {
try {
if (id) {
this.loading = true
const res = await axios.post("/delete_discussion", {
id: id
});
this.loading = false
}
} catch (error) {
console.log(error)
this.loading = false
}
},
async edit_title(discussion_id, new_title) {
try {
if (discussion_id) {
this.loading = true
const res = await axios.post("/edit_title", {
id: discussion_id,
title: new_title
});
this.loading = false
if (res.status == 200) {
const index = this.list.findIndex(x => x.id == discussion_id);
const discussionItem = this.list[index]
discussionItem.title = new_title
this.tempList = this.list
}
}
@ -183,12 +211,20 @@ export default {
}, 100)
}
},
selectDiscussion(item) {
async selectDiscussion(item) {
this.currentDiscussion = item
this.load_discussion(item.id)
await this.load_discussion(item.id)
if (this.discussionArr.length > 1) {
if (this.currentDiscussion.title === "" || this.currentDiscussion.title === null ) {
this.changeTitleUsingUserMSG(this.currentDiscussion.id, this.discussionArr[1].content)
}
}
},
scrollToElement(el) {
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: "center", inline: "nearest" });
@ -228,28 +264,72 @@ export default {
this.scrollToElement(responseMessageElement)
})
if (this.currentDiscussion.title === "" || this.currentDiscussion.title === null) {
this.changeTitleUsingUserMSG(this.currentDiscussion.id, usrMessage.content)
}
},
sendMsg(msg) {
//console.log("Message sent:", msg)
websocket.emit('generate_msg', { prompt: msg });
},
steamMessageContent(content) {
//console.log("Message obj recieved:", content)
const lastMsg = this.discussionArr[this.discussionArr.length - 1]
lastMsg.content = content.data
},
createNewDiscussionShow(){
console.log("aa",this.showCreateDiscussionModal)
this.showCreateDiscussionModal=true
}
async changeTitleUsingUserMSG(id, msg) {
const index = this.list.findIndex(x => x.id == id);
const discussionItem = this.list[index]
if (msg) {
discussionItem.title = msg
this.tempList = this.list
}
await this.edit_title(id,msg)
},
async createNewDiscussion() {
const res = await this.new_discussion()
await this.list_discussions()
const index = this.list.findIndex(x => x.id == res.id);
const discussionItem = this.list[index]
this.selectDiscussion(discussionItem)
nextTick(() => {
const selectedDisElement = document.getElementById('dis-' + res.id)
this.scrollToElement(selectedDisElement)
})
},
async deleteDiscussion(id) {
const index = this.list.findIndex(x => x.id == id);
const discussionItem = this.list[index]
discussionItem.loading=true
this.delete_discussion(id)
if (this.currentDiscussion.id == id) {
this.currentDiscussion = {}
}
await this.list_discussions()
},
async editTitle(newTitleObj) {
//const index = this.$refs.discussionList.findIndex(x => x.id == newTitleObj.id);
//const discussionItem = this.$refs.discussionList[index]
//console.log(JSON.stringify(discussionItem))
//discussionItem.loading.value=true
//console.log(discussionItem.title)
await this.edit_title(newTitleObj.id, newTitleObj.title)
},
},
async created() {
// Constructor
this.list = await this.list_discussions()
this.tempList = this.list
await this.list_discussions()
nextTick(() => {
feather.replace()
})
@ -263,7 +343,7 @@ export default {
Message,
ChatBox,
WelcomeComponent,
ModalSimple
}, watch: {
filterTitle(newVal, oldVal) {
if (newVal == "") {
@ -282,9 +362,14 @@ import Discussion from '../components/Discussion.vue';
import Message from '../components/Message.vue';
import ChatBox from '../components/ChatBox.vue'
import WelcomeComponent from '../components/WelcomeComponent.vue'
import ModalSimple from '../components/ModalSimple.vue'
import feather from 'feather-icons'
import axios from "axios";
import { nextTick } from 'vue';
import websocket from '@/services/websocket.js';
import { onMounted } from 'vue'
import { initFlowbite } from 'flowbite'
@ -292,4 +377,7 @@ import { initFlowbite } from 'flowbite'
onMounted(() => {
initFlowbite();
})
axios.defaults.baseURL = import.meta.env.VITE_GPT4ALL_API_BASEURL;
</script>