Merge pull request #142 from andzejsp/main

More small steps towards new UI
This commit is contained in:
Saifeddine ALOUI 2023-05-04 21:51:42 +02:00 committed by GitHub
commit eeeabb8744
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 246 additions and 125 deletions

1
.gitignore vendored
View File

@ -167,3 +167,4 @@ backends/
!backends/__init__.py
web/.env.build
web/.env.dev
web/.env.development

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:

View File

@ -1,3 +1,4 @@
VITE_GPT4ALL_API = http://localhost:9600 # http://localhost:9600
VITE_GPT4ALL_API_CHANGE_ORIGIN = 0 # FALSE
VITE_GPT4ALL_API_SECURE = 0 # FALSE
VITE_GPT4ALL_API_BASEURL = / # FALSE

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

1
web/dist/assets/index-2edf1005.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

11
web/dist/assets/index-ffda5761.js 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>GPT4All - WEBUI</title>
<script type="module" crossorigin src="/assets/index-c6cec2f7.js"></script>
<link rel="stylesheet" href="/assets/index-e950823c.css">
<script type="module" crossorigin src="/assets/index-ffda5761.js"></script>
<link rel="stylesheet" href="/assets/index-2edf1005.css">
</head>
<body>
<div id="app"></div>

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 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 }}</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,19 +72,54 @@ export default {
},
data() {
return {
showConfirmation: false,
editTitleMode: false,
editTitle: false,
newTitle: String,
}
},
methods: {
}, mounted() {
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>
<style scoped></style>

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 = '/api/'; // 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>