Upgraded to V7.0

This commit is contained in:
Saifeddine ALOUI 2023-11-09 02:00:49 +01:00
parent 5f67de4868
commit 70a65f84f2
14 changed files with 110 additions and 123 deletions

8
CHANGELOG.md Normal file

@ -0,0 +1,8 @@
# V 7.0:
Added changelog
Separated images from text in file upload
Added support for multimodal models
Added Dalle and gpt4 vision
enhanced interface
Upgraded the code execution
Now it is possible to execute lollms multiple times on the same PC. You can now have multiple instances with different port numbers and so a work in parallel.

@ -726,6 +726,7 @@ class LoLLMsAPPI(LollmsApplication):
try: try:
ASCIIColors.print("warming up", ASCIIColors.color_bright_cyan) ASCIIColors.print("warming up", ASCIIColors.color_bright_cyan)
generated_text = model.generate(fd, generated_text = model.generate(fd,
n_predict=n_predicts, n_predict=n_predicts,
callback=callback, callback=callback,
@ -1192,7 +1193,8 @@ class LoLLMsAPPI(LollmsApplication):
chunk:str, chunk:str,
message_type:MSG_TYPE message_type:MSG_TYPE
): ):
title[0] += chunk if chunk:
title[0] += chunk
antiprompt = self.personality.detect_antiprompt(title[0]) antiprompt = self.personality.detect_antiprompt(title[0])
if antiprompt: if antiprompt:
ASCIIColors.warning(f"\nDetected hallucination with antiprompt: {antiprompt}") ASCIIColors.warning(f"\nDetected hallucination with antiprompt: {antiprompt}")
@ -1239,7 +1241,7 @@ class LoLLMsAPPI(LollmsApplication):
# Check if there are document files to add to the prompt # Check if there are document files to add to the prompt
documentation = "" documentation = ""
if len(self.personality.files) > 0 and self.personality.vectorizer: if len(self.personality.text_files) > 0 and self.personality.vectorizer:
if documentation=="": if documentation=="":
documentation="Documentation:\n" documentation="Documentation:\n"
docs, sorted_similarities = self.personality.vectorizer.recover_text(current_message.content, top_k=self.config.data_vectorization_nb_chunks) docs, sorted_similarities = self.personality.vectorizer.recover_text(current_message.content, top_k=self.config.data_vectorization_nb_chunks)
@ -1344,11 +1346,9 @@ class LoLLMsAPPI(LollmsApplication):
ASCIIColors.yellow(history) ASCIIColors.yellow(history)
ASCIIColors.bold("DISCUSSION") ASCIIColors.bold("DISCUSSION")
# TODO: upghrade to asciicolors 0.1.4 # TODO: upghrade to asciicolors 0.1.4
# ASCIIColors.hilight(discussion_messages,"!@>",ASCIIColors.color_yellow,ASCIIColors.color_bright_red,False) ASCIIColors.hilight(discussion_messages,"!@>",ASCIIColors.color_yellow,ASCIIColors.color_bright_red,False)
ASCIIColors.yellow(discussion_messages)
ASCIIColors.bold("Final prompt") ASCIIColors.bold("Final prompt")
#ASCIIColors.hilight(prompt_data,"!@>",ASCIIColors.color_yellow,ASCIIColors.color_bright_red,False) ASCIIColors.hilight(prompt_data,"!@>",ASCIIColors.color_yellow,ASCIIColors.color_bright_red,False)
ASCIIColors.yellow(prompt_data)
ASCIIColors.info(f"prompt size:{len(tokens)} tokens") ASCIIColors.info(f"prompt size:{len(tokens)} tokens")
ASCIIColors.info(f"available space after doc and history:{available_space} tokens") ASCIIColors.info(f"available space after doc and history:{available_space} tokens")
@ -1356,81 +1356,6 @@ class LoLLMsAPPI(LollmsApplication):
return prompt_data, current_message.content, tokens return prompt_data, current_message.content, tokens
"""
def prepare_query(self, client_id, message_id=-1, is_continue=False):
messages = self.connections[client_id]["current_discussion"].get_messages()
full_message_list = []
for i, message in enumerate(messages):
if message.id< message_id or (message_id==-1 and i<len(messages)-1):
if message.content!='' and (message.message_type<=MSG_TYPE.MSG_TYPE_FULL_INVISIBLE_TO_USER.value and message.message_type!=MSG_TYPE.MSG_TYPE_FULL_INVISIBLE_TO_AI.value):
full_message_list.append("\n"+self.config.discussion_prompt_separator+message.sender+": "+message.content.strip())
else:
break
link_text = "\n" #self.personality.link_text
if not is_continue:
full_message_list.append(self.config.discussion_prompt_separator +message.sender.strip().replace(":","")+": "+message.content.strip()+link_text+self.personality.ai_message_prefix.strip())
else:
full_message_list.append(self.config.discussion_prompt_separator +message.sender.strip().replace(":","")+": "+message.content.strip())
composed_messages = link_text.join(full_message_list)
t = self.model.tokenize(composed_messages)
cond_tk = self.model.tokenize(self.personality.personality_conditioning)
n_t = len(t)
n_cond_tk = len(cond_tk)
max_prompt_stx_size = 3*int(self.config.ctx_size/4)
if n_cond_tk+n_t>max_prompt_stx_size:
nb_tk = max_prompt_stx_size-n_cond_tk
composed_messages = self.model.detokenize(t[-nb_tk:])
ASCIIColors.warning(f"Cropping discussion to fit context [using {nb_tk} tokens/{self.config.ctx_size}]")
discussion_messages = composed_messages
conditionning = self.personality.personality_conditioning
if self.config["override_personality_model_parameters"]:
conditionning = conditionning+ "\n!@>user description:\nName:"+self.config["user_name"]+"\n"+self.config["user_description"]+"\n"
str_docs = ""
if self.config.use_discussions_history:
if self.discussions_store is not None:
pr = PromptReshaper("{{conditionning}}\n!@>document chunks:\n{{doc}}\n{{content}}")
docs, sorted_similarities = self.discussions_store.recover_text(message.content, top_k=self.config.data_vectorization_nb_chunks)
for doc, infos in zip(docs, sorted_similarities):
str_docs+=f"discussion chunk:\ndiscussion title: {infos[0]}\nchunk content:{doc}"
if len(self.personality.files)>0 and self.personality.vectorizer:
docs, sorted_similarities = self.personality.vectorizer.recover_text(message.content, top_k=self.config.data_vectorization_nb_chunks)
for doc, infos in zip(docs, sorted_similarities):
str_docs+=f"document chunk:\nchunk path: {infos[0]}\nchunk content:{doc}"
if str_docs!="":
pr = PromptReshaper("{{conditionning}}\n!@>document chunks:\n{{doc}}\n{{content}}")
discussion_messages = pr.build({
"doc":str_docs,
"conditionning":conditionning,
"content":discussion_messages
}, self.model.tokenize, self.model.detokenize, self.config.ctx_size-self.config.min_n_predict, place_holders_to_sacrifice=["content"])
else:
pr = PromptReshaper("{{conditionning}}\n{{content}}")
discussion_messages = pr.build({
"conditionning":conditionning,
"content":discussion_messages
}, self.model.tokenize, self.model.detokenize, self.config.ctx_size-self.config.min_n_predict, place_holders_to_sacrifice=["content"])
# remove extra returns
discussion_messages = self.clean_string(discussion_messages)
tokens = self.model.tokenize(discussion_messages)
if self.config["debug"]:
ASCIIColors.yellow(discussion_messages)
ASCIIColors.info(f"prompt size:{len(tokens)} tokens")
return discussion_messages, message.content, tokens
"""
def get_discussion_to(self, client_id, message_id=-1): def get_discussion_to(self, client_id, message_id=-1):
messages = self.connections[client_id]["current_discussion"].get_messages() messages = self.connections[client_id]["current_discussion"].get_messages()
full_message_list = [] full_message_list = []
@ -1625,7 +1550,7 @@ class LoLLMsAPPI(LollmsApplication):
self.close_message(client_id) self.close_message(client_id)
elif message_type == MSG_TYPE.MSG_TYPE_CHUNK: elif message_type == MSG_TYPE.MSG_TYPE_CHUNK:
if self.nb_received_tokens: if self.nb_received_tokens==0:
self.start_time = datetime.now() self.start_time = datetime.now()
dt =(datetime.now() - self.start_time).seconds dt =(datetime.now() - self.start_time).seconds
if dt==0: if dt==0:
@ -1634,7 +1559,8 @@ class LoLLMsAPPI(LollmsApplication):
ASCIIColors.green(f"Received {self.nb_received_tokens} tokens (speed: {spd:.2f}t/s) ",end="\r",flush=True) ASCIIColors.green(f"Received {self.nb_received_tokens} tokens (speed: {spd:.2f}t/s) ",end="\r",flush=True)
sys.stdout = sys.__stdout__ sys.stdout = sys.__stdout__
sys.stdout.flush() sys.stdout.flush()
self.connections[client_id]["generated_text"] += chunk if chunk:
self.connections[client_id]["generated_text"] += chunk
antiprompt = self.personality.detect_antiprompt(self.connections[client_id]["generated_text"]) antiprompt = self.personality.detect_antiprompt(self.connections[client_id]["generated_text"])
if antiprompt: if antiprompt:
ASCIIColors.warning(f"\nDetected hallucination with antiprompt: {antiprompt}") ASCIIColors.warning(f"\nDetected hallucination with antiprompt: {antiprompt}")

@ -243,16 +243,16 @@ class DiscussionsDB:
discussion = {"id": discussion_id, "title":discussion_title, "messages": []} discussion = {"id": discussion_id, "title":discussion_title, "messages": []}
rows = self.select(f"SELECT sender, content, message_type, rank, parent_message_id, binding, model, personality, created_at, finished_generating_at FROM message WHERE discussion_id=?",(discussion_id,)) rows = self.select(f"SELECT sender, content, message_type, rank, parent_message_id, binding, model, personality, created_at, finished_generating_at FROM message WHERE discussion_id=?",(discussion_id,))
for message_row in rows: for message_row in rows:
sender = message_row[1] sender = message_row[0]
content = message_row[2] content = message_row[1]
content_type = message_row[3] content_type = message_row[2]
rank = message_row[4] rank = message_row[3]
parent_message_id = message_row[5] parent_message_id = message_row[4]
binding = message_row[6] binding = message_row[5]
model = message_row[7] model = message_row[6]
personality = message_row[8] personality = message_row[7]
created_at = message_row[9] created_at = message_row[8]
finished_generating_at = message_row[10] finished_generating_at = message_row[9]
discussion["messages"].append( discussion["messages"].append(
{"sender": sender, "content": content, "message_type": content_type, "rank": rank, "parent_message_id": parent_message_id, "binding": binding, "model":model, "personality":personality, "created_at":created_at, "finished_generating_at":finished_generating_at} {"sender": sender, "content": content, "message_type": content_type, "rank": rank, "parent_message_id": parent_message_id, "binding": binding, "model":model, "personality":personality, "created_at":created_at, "finished_generating_at":finished_generating_at}

24
app.py

@ -13,7 +13,7 @@ __github__ = "https://github.com/ParisNeo/lollms-webui"
__copyright__ = "Copyright 2023, " __copyright__ = "Copyright 2023, "
__license__ = "Apache 2.0" __license__ = "Apache 2.0"
__version__ ="6.8" __version__ ="7.0 (Alpha)"
main_repo = "https://github.com/ParisNeo/lollms-webui.git" main_repo = "https://github.com/ParisNeo/lollms-webui.git"
import os import os
@ -144,7 +144,6 @@ try:
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)
def get_ip_address(): def get_ip_address():
# Create a socket object # Create a socket object
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -194,7 +193,7 @@ try:
if config.auto_update: if config.auto_update:
if check_update_(): if check_update_():
ASCIIColors.info("New version found. Updating!") ASCIIColors.info("New version found. Updating!")
self.update_software() run_update_script()
if len(config.personalities)==0: if len(config.personalities)==0:
config.personalities.append("generic/lollms") config.personalities.append("generic/lollms")
@ -360,6 +359,8 @@ try:
self.add_endpoint("/edit_title", "edit_title", self.edit_title, methods=["POST"]) self.add_endpoint("/edit_title", "edit_title", self.edit_title, methods=["POST"])
self.add_endpoint("/make_title", "make_title", self.make_title, methods=["POST"]) self.add_endpoint("/make_title", "make_title", self.make_title, methods=["POST"])
self.add_endpoint("/get_server_address", "get_server_address", self.get_server_address, methods=["GET"])
self.add_endpoint( self.add_endpoint(
"/delete_discussion", "/delete_discussion",
@ -481,6 +482,10 @@ try:
def get_server_address(self):
server_address = request.host_url
return server_address
def execute_python(self, code, discussion_id, message_id): def execute_python(self, code, discussion_id, message_id):
def spawn_process(code): def spawn_process(code):
"""Executes Python code and returns the output as JSON.""" """Executes Python code and returns the output as JSON."""
@ -1716,7 +1721,7 @@ try:
def get_current_personality_files_list(self): def get_current_personality_files_list(self):
if self.personality is None: if self.personality is None:
return jsonify({"state":False, "error":"No personality selected"}) return jsonify({"state":False, "error":"No personality selected"})
return jsonify({"state":True, "files":[{"name":Path(f).name, "size":Path(f).stat().st_size} for f in self.personality.files]}) return jsonify({"state":True, "files":[{"name":Path(f).name, "size":Path(f).stat().st_size} for f in self.personality.text_files]})
def clear_personality_files_list(self): def clear_personality_files_list(self):
if self.personality is None: if self.personality is None:
@ -2540,9 +2545,14 @@ try:
# if autoshow # if autoshow
if config.auto_show_browser: if config.auto_show_browser:
webbrowser.open(f"http://{config['host']}:{config['port']}") webbrowser.open(f"http://{config['host']}:{config['port']}")
socketio.run(app, host=config["host"], port=config["port"],
# prevent error: The Werkzeug web server is not designed to run in production
allow_unsafe_werkzeug=True) try:
socketio.run(app, host=config["host"], port=config["port"],
# prevent error: The Werkzeug web server is not designed to run in production
allow_unsafe_werkzeug=True)
except Exception as ex:
trace_exception(ex)
# http_server = WSGIServer((config["host"], config["port"]), app, handler_class=WebSocketHandler) # http_server = WSGIServer((config["host"], config["port"]), app, handler_class=WebSocketHandler)
# http_server.serve_forever() # http_server.serve_forever()
except Exception as ex: except Exception as ex:

@ -15,4 +15,4 @@ GitPython
setuptools setuptools
numpy==1.24 numpy==1.24
flask_compress flask_compress
ascii_colors>=0.1.3 ascii_colors>=0.1.4

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
web/dist/index.html vendored

@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LoLLMS WebUI - Welcome</title> <title>LoLLMS WebUI - Welcome</title>
<script type="module" crossorigin src="/assets/index-5d95834b.js"></script> <script type="module" crossorigin src="/assets/index-6d02e1a1.js"></script>
<link rel="stylesheet" href="/assets/index-f06a4c0c.css"> <link rel="stylesheet" href="/assets/index-78770f39.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

@ -43,6 +43,11 @@ hljs.configure({ languages: ['bash'] }); // Set bash as the default language
hljs.highlightAll(); hljs.highlightAll();
export default { export default {
props: { props: {
host: {
type: String,
required: false,
default: "http://localhost:9600",
},
language: { language: {
type: String, type: String,
required: true, required: true,
@ -120,7 +125,7 @@ export default {
executeCode() { executeCode() {
const json = JSON.stringify({ 'code': this.code, 'discussion_id': this.discussion_id, 'message_id': this.message_id, 'language': this.language}) const json = JSON.stringify({ 'code': this.code, 'discussion_id': this.discussion_id, 'message_id': this.message_id, 'language': this.language})
console.log(json) console.log(json)
fetch('http://localhost:9600/execute_code', { fetch(`${this.host}/execute_code`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: json body: json
@ -141,7 +146,7 @@ export default {
openFolder() { openFolder() {
const json = JSON.stringify({ 'discussion_id': this.discussion_id }) const json = JSON.stringify({ 'discussion_id': this.discussion_id })
console.log(json) console.log(json)
fetch('http://localhost:9600/open_code_folder', { fetch(`${this.host}/open_code_folder`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: json body: json

@ -4,6 +4,7 @@
<div v-for="(item, index) in markdownItems" :key="index"> <div v-for="(item, index) in markdownItems" :key="index">
<code-block <code-block
v-if="item.type === 'code'" v-if="item.type === 'code'"
:host="host"
:language="item.language" :language="item.language"
:code="item.code" :code="item.code"
:discussion_id="discussion_id" :discussion_id="discussion_id"
@ -40,6 +41,11 @@ function escapeHtml(unsafe) {
export default { export default {
name: 'MarkdownRenderer', name: 'MarkdownRenderer',
props: { props: {
host: {
type: String,
required: false,
default: "http://localhost:9600",
},
markdownText: { markdownText: {
type: String, type: String,
required: true, required: true,

@ -144,7 +144,7 @@
</div> </div>
</div> </div>
<MarkdownRenderer ref="mdRender" v-if="!editMsgMode" :markdown-text="message.content" :message_id="message.id"> <MarkdownRenderer ref="mdRender" v-if="!editMsgMode" :host="host" :markdown-text="message.content" :message_id="message.id">
</MarkdownRenderer> </MarkdownRenderer>
<div > <div >
<textarea v-if="editMsgMode" ref="mdTextarea" @keydown.tab.prevent="insertTab" <textarea v-if="editMsgMode" ref="mdTextarea" @keydown.tab.prevent="insertTab"
@ -224,6 +224,11 @@ export default {
DynamicUIRenderer, DynamicUIRenderer,
}, },
props: { props: {
host: {
type: String,
required: false,
default: "http://localhost:9600",
},
message: Object, message: Object,
avatar: '' avatar: ''
}, },

@ -230,8 +230,12 @@
<!-- CHAT AREA --> <!-- CHAT AREA -->
<div class=" container pt-4 pb-10 mb-28"> <div class=" container pt-4 pb-10 mb-28">
<TransitionGroup v-if="discussionArr.length > 0" name="list"> <TransitionGroup v-if="discussionArr.length > 0" name="list">
<Message v-for="(msg, index) in discussionArr" :key="msg.id" :message="msg" :id="'msg-' + msg.id" <Message v-for="(msg, index) in discussionArr"
ref="messages" @copy="copyToClipBoard" @delete="deleteMessage" @rankUp="rankUpMessage" :key="msg.id" :message="msg" :id="'msg-' + msg.id"
:host="host"
ref="messages"
@copy="copyToClipBoard" @delete="deleteMessage" @rankUp="rankUpMessage"
@rankDown="rankDownMessage" @updateMessage="updateMessage" @resendMessage="resendMessage" @continueMessage="continueMessage" @rankDown="rankDownMessage" @updateMessage="updateMessage" @resendMessage="resendMessage" @continueMessage="continueMessage"
:avatar="getAvatar(msg.sender)" /> :avatar="getAvatar(msg.sender)" />
@ -362,6 +366,7 @@ export default {
data() { data() {
return { return {
host:"",
// To be synced with the backend database types // To be synced with the backend database types
msgTypes: { msgTypes: {
// Messaging // Messaging
@ -545,8 +550,6 @@ export default {
this.loading = false this.loading = false
this.setDiscussionLoading(id, this.loading) this.setDiscussionLoading(id, this.loading)
if (data) { if (data) {
console.log("received discussion")
console.log(data)
// Filter out the user and bot entries // Filter out the user and bot entries
this.discussionArr = data.filter((item) => this.discussionArr = data.filter((item) =>
item.message_type == this.msgTypes.MSG_TYPE_CHUNK || item.message_type == this.msgTypes.MSG_TYPE_CHUNK ||
@ -1128,7 +1131,6 @@ export default {
}, },
streamMessageContent(msgObj) { streamMessageContent(msgObj) {
// Streams response message content from binding // Streams response message content from binding
console.log("Received message",msgObj)
const discussion_id = msgObj.discussion_id const discussion_id = msgObj.discussion_id
this.setDiscussionLoading(discussion_id, true); this.setDiscussionLoading(discussion_id, true);
if (this.currentDiscussion.id == discussion_id) { if (this.currentDiscussion.id == discussion_id) {
@ -1724,6 +1726,16 @@ export default {
this.isCreated = true this.isCreated = true
}, },
async mounted() { async mounted() {
try {
const response = await fetch('/get_server_address'); // Replace with the actual endpoint on your Flask server
const serverAddress = await response.text();
console.log(`Server address: ${serverAddress}`)
this.host = `${serverAddress}`; // Construct the full server address dynamically
} catch (error) {
console.error('Error fetching server address:', error);
// Handle error if necessary
this.host = process.env.VITE_LOLLMS_API
}
//console.log('chatbox mnt',this.$refs) //console.log('chatbox mnt',this.$refs)
this.$nextTick(() => { this.$nextTick(() => {
feather.replace(); feather.replace();

@ -3,8 +3,23 @@ import { fileURLToPath, URL } from 'node:url'
import { defineConfig,loadEnv } from 'vite' import { defineConfig,loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default ({ mode }) => { export default async ({ mode }) => {
async function getFlaskServerURL() {
try {
const response = await fetch('/get_server_address'); // Replace with the actual endpoint on your Flask server
const serverAddress = await response.text();
console.log(`Server address: ${serverAddress}`)
return `${serverAddress}`; // Construct the full server address dynamically
} catch (error) {
// console.error('Error fetching server address:', error);
// Handle error if necessary
return process.env.VITE_LOLLMS_API
}
}
const serverURL = await getFlaskServerURL()
console.log(serverURL)
// Load app-level env vars to node-level env vars. // Load app-level env vars to node-level env vars.
process.env = {...process.env, ...loadEnv(mode, process.cwd())}; process.env = {...process.env, ...loadEnv(mode, process.cwd())};
@ -21,7 +36,7 @@ export default ({ mode }) => {
server: { server: {
proxy: { proxy: {
"/api/": { "/api/": {
target: process.env.VITE_LOLLMS_API, target: serverURL,//process.env.VITE_LOLLMS_API,//getFlaskServerURL(),// process.env.VITE_LOLLMS_API,
changeOrigin: process.env.VITE_LOLLMS_API_CHANGE_ORIGIN, changeOrigin: process.env.VITE_LOLLMS_API_CHANGE_ORIGIN,
secure: process.env.VITE_LOLLMS_API_SECURE, secure: process.env.VITE_LOLLMS_API_SECURE,
rewrite: (path) => path.replace(/^\/api/, ""), rewrite: (path) => path.replace(/^\/api/, ""),

@ -1 +1 @@
Subproject commit fdfae6890b123726e6f86fcaa845a6cb5f32dc85 Subproject commit dd3ace5dd06cb02b1c13b83aa785266f50ff663c