Markdown message (half) support
This commit is contained in:
1 parent
e07f8ee05f
commit
eb9a1ad9e8
9 files changed
+147
-92
No files matched your search
@@ -71,6 +71,8 @@ class ExpertAgent(Agent):
|
|||||||
elif model_engine == "replicate":
|
elif model_engine == "replicate":
|
||||||
if 'parameters' in db_model:
|
if 'parameters' in db_model:
|
||||||
opts['parameters'] = db_model['parameters']
|
opts['parameters'] = db_model['parameters']
|
||||||
|
if 'format' in db_model:
|
||||||
|
opts['format'] = db_model['format']
|
||||||
|
|
||||||
response = await inference.replicate_completion(self.app, messages, model=db_model.value['model'], opts=opts)
|
response = await inference.replicate_completion(self.app, messages, model=db_model.value['model'], opts=opts)
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class ChatMixin:
|
|||||||
sender=sender,
|
sender=sender,
|
||||||
kind=kind or 'message',
|
kind=kind or 'message',
|
||||||
subkind=subkind,
|
subkind=subkind,
|
||||||
#format=format,
|
format=format,
|
||||||
content=initial_content,
|
content=initial_content,
|
||||||
thread_id=thread_id
|
thread_id=thread_id
|
||||||
)
|
)
|
||||||
@@ -159,7 +159,7 @@ class ChatMixin:
|
|||||||
for agent_id in agent_ids:
|
for agent_id in agent_ids:
|
||||||
if str(agent_id) != str(sender):
|
if str(agent_id) != str(sender):
|
||||||
await self.notify_message_to_agent(agent_id, message)
|
await self.notify_message_to_agent(agent_id, message)
|
||||||
|
|
||||||
def notify_users():
|
def notify_users():
|
||||||
nonlocal updating
|
nonlocal updating
|
||||||
self.notify_message_to_user(chat_owner_id, message, updating)
|
self.notify_message_to_user(chat_owner_id, message, updating)
|
||||||
|
|||||||
+4
-1
@@ -8,13 +8,14 @@ import temporary as tmp
|
|||||||
MESSAGE_PAGE_SIZE = 256
|
MESSAGE_PAGE_SIZE = 256
|
||||||
|
|
||||||
class Message:
|
class Message:
|
||||||
def __init__(self, id, content, sender, time=None, kind='message', subkind=None, thread_id=None):
|
def __init__(self, id, content, sender, time=None, kind='message', subkind=None, format=None, thread_id=None):
|
||||||
self.id = id
|
self.id = id
|
||||||
self.content = content
|
self.content = content
|
||||||
self.sender = sender
|
self.sender = sender
|
||||||
self.time = time
|
self.time = time
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
self.subkind = subkind
|
self.subkind = subkind
|
||||||
|
self.format = format
|
||||||
self.thread_id = thread_id
|
self.thread_id = thread_id
|
||||||
|
|
||||||
def from_db_record(record):
|
def from_db_record(record):
|
||||||
@@ -32,6 +33,8 @@ class Message:
|
|||||||
"time": self.time,
|
"time": self.time,
|
||||||
"sender": self.sender,
|
"sender": self.sender,
|
||||||
"kind": self.kind,
|
"kind": self.kind,
|
||||||
|
"subkind": self.subkind,
|
||||||
|
"format": self.format,
|
||||||
"content": self.content,
|
"content": self.content,
|
||||||
"threadId": self.thread_id,
|
"threadId": self.thread_id,
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -31,7 +31,16 @@ async def init_database (data_directory):
|
|||||||
|
|
||||||
# Checks whether an object is an instance of any database model
|
# Checks whether an object is an instance of any database model
|
||||||
def is_instance (value):
|
def is_instance (value):
|
||||||
isinstance(value, models.Model)
|
import temporary as tmp
|
||||||
|
if hasattr(value, 'id'):
|
||||||
|
if tmp.is_tmp_id(value.id):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if isinstance(value, models.Model):
|
||||||
|
return True
|
||||||
|
|
||||||
|
print("NOT A DB INSTANCE??? " + repr(value))
|
||||||
|
return False
|
||||||
|
|
||||||
class User(models.Model):
|
class User(models.Model):
|
||||||
id = fields.IntField(pk=True)
|
id = fields.IntField(pk=True)
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ def get_llama_cpp_model(app, model_file):
|
|||||||
return last_model_instance
|
return last_model_instance
|
||||||
|
|
||||||
if last_model_instance is not None:
|
if last_model_instance is not None:
|
||||||
#last_model_instance = None
|
last_model_instance = None
|
||||||
del last_model_instance
|
#del last_model_instance
|
||||||
|
|
||||||
last_model_file = model_file
|
last_model_file = model_file
|
||||||
last_model_instance = Llama(
|
last_model_instance = Llama(
|
||||||
@@ -176,7 +176,7 @@ async def replicate_base_completion(app, model, input):
|
|||||||
|
|
||||||
def run_replicate_inference_loop(replicate_client, model, input, content_stream):
|
def run_replicate_inference_loop(replicate_client, model, input, content_stream):
|
||||||
print("START INFERENCE")
|
print("START INFERENCE")
|
||||||
completion = replicate_client.run(model, input=input)
|
completion = replicate_client.run(model, input=input, stream=True)
|
||||||
for event in completion:
|
for event in completion:
|
||||||
if event:
|
if event:
|
||||||
content_stream.feed(str(event))
|
content_stream.feed(str(event))
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ from stream import Stream
|
|||||||
# relative to the module file???? wtf
|
# relative to the module file???? wtf
|
||||||
frontend_path = '../../frontend/dist'
|
frontend_path = '../../frontend/dist'
|
||||||
|
|
||||||
def start_server(app):
|
async def start_server(app):
|
||||||
nest_asyncio.apply()
|
nest_asyncio.apply()
|
||||||
|
|
||||||
server = Quart(__name__, static_folder=frontend_path)
|
server = Quart(__name__, static_folder=frontend_path)
|
||||||
server.secret_key = 'horde-secret'
|
server.secret_key = 'horde-secret'
|
||||||
auth_manager = QuartAuth(server, mode="brearer")
|
auth_manager = QuartAuth(server, mode="bearer")
|
||||||
|
|
||||||
stream_listeners = {}
|
stream_listeners = {}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ def start_server(app):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# TODO: print username instead, same in the finally block
|
# TODO: print username instead, same in the finally block
|
||||||
print("User logged in:", user_id)
|
print("User logged in:", user_id)
|
||||||
await websocket.send(json.dumps({'message': 'authentication successful'}))
|
await websocket.send(json.dumps({'message': 'authentication successful'}))
|
||||||
|
|
||||||
send_queue = asyncio.Queue()
|
send_queue = asyncio.Queue()
|
||||||
@@ -307,4 +307,4 @@ def start_server(app):
|
|||||||
print("User logged out:", user_id)
|
print("User logged out:", user_id)
|
||||||
app.remove_user_connection(user_id, send_queue)
|
app.remove_user_connection(user_id, send_queue)
|
||||||
|
|
||||||
server.run(debug=True)
|
server.run(debug=True)
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"marked": "^18.0.2",
|
||||||
"vite-plugin-pwa": "^1.0.2",
|
"vite-plugin-pwa": "^1.0.2",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.17",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<script setup>
|
||||||
|
import { marked } from "marked";
|
||||||
|
marked.use({
|
||||||
|
renderer: {
|
||||||
|
code (code, infostring, escaped) {
|
||||||
|
let langElem = infostring ?
|
||||||
|
`<div class="language">${infostring}</div>` : "";
|
||||||
|
|
||||||
|
return `<div class="code">
|
||||||
|
${langElem}
|
||||||
|
<div class="content">${code}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function chatEntryClass(msg) {
|
||||||
|
if (msg.type == 'note') {
|
||||||
|
return ['note'];
|
||||||
|
} else if (msg.type == 'message') {
|
||||||
|
return ['message', msg.sender == 'user' ? 'sent' : 'received'];
|
||||||
|
} else if (msg.type == 'log') {
|
||||||
|
return ['message', 'log'];
|
||||||
|
} else {
|
||||||
|
return ['info'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="chatEntryClass(message)" @contextmenu.prevent="$emit('open-menu', message)">
|
||||||
|
<template v-if="message.type == 'message' || message.type == null">
|
||||||
|
<div v-if="message.sender != 'user'" class="sender">{{message.sender}}</div>
|
||||||
|
|
||||||
|
<div v-if="message.format == 'markdown'" v-html="marked.parse(message.content)" class="content"></div>
|
||||||
|
<div v-else class="content">{{message.content}}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="message.type == 'note'">
|
||||||
|
<div class="sender">{{message.sender}}</div>
|
||||||
|
<div class="content">{{message.content}}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="message.type == 'log'">
|
||||||
|
<div class="sender">Log ({{message.sender}})</div>
|
||||||
|
<div class="content">{{message.content}}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="message.type == 'thread-header'">
|
||||||
|
<div class="content">
|
||||||
|
Thread {{message.threadId}} ({{message.messageCount}} messages)
|
||||||
|
<a href="#" @click.prevent="$emit('toggleThread', message.threadId, !message.expanded)">{{message.expanded ? "Hide" : "Show"}}</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.info {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 50em;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
width: fit-content;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.sent {
|
||||||
|
align-self: end;
|
||||||
|
margin-left: 2em;
|
||||||
|
}
|
||||||
|
.message.received {
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .content {
|
||||||
|
white-space: preserve;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message ul, .message ol {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .sender,
|
||||||
|
.note .sender {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 100%;
|
||||||
|
margin-top: -5px; /* half message padding */
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.log .content {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
} from "vue";
|
} from "vue";
|
||||||
|
|
||||||
import TitleBar from '../components/TitleBar.vue';
|
import TitleBar from '../components/TitleBar.vue';
|
||||||
|
import Message from '../components/Message.vue';
|
||||||
|
|
||||||
import * as backend from '../backend';
|
import * as backend from '../backend';
|
||||||
import * as ui from '../ui';
|
import * as ui from '../ui';
|
||||||
@@ -401,18 +402,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function chatEntryClass (msg) {
|
|
||||||
if (msg.type == 'note') {
|
|
||||||
return ['note'];
|
|
||||||
} else if (msg.type == 'message') {
|
|
||||||
return ['message', msg.sender == 'user' ? 'sent' : 'received'];
|
|
||||||
} else if (msg.type == 'log') {
|
|
||||||
return ['message', 'log'];
|
|
||||||
} else {
|
|
||||||
return ['info'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function inputKeyDown (event) {
|
function inputKeyDown (event) {
|
||||||
if (event.key == "Enter" && event.ctrlKey) {
|
if (event.key == "Enter" && event.ctrlKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -433,31 +422,13 @@
|
|||||||
</TitleBar>
|
</TitleBar>
|
||||||
|
|
||||||
<div class="messages-container" ref="chatContainerElem">
|
<div class="messages-container" ref="chatContainerElem">
|
||||||
<div v-for="msg in chatEntries" :class="chatEntryClass(msg)" @contextmenu.prevent="chatEntryContextMenu(msg)">
|
<Message
|
||||||
<template v-if="msg.type == 'message' || msg.type == null">
|
v-for="msg in chatEntries"
|
||||||
<div v-if="msg.sender != 'user'" class="sender">{{msg.sender}}</div>
|
:key="msg.id || msg.threadId"
|
||||||
<div class="content">{{msg.content}}</div>
|
:message="msg"
|
||||||
</template>
|
@open-menu="chatEntryContextMenu"
|
||||||
|
@toggle-thread="expandThread"
|
||||||
<template v-if="msg.type == 'note'">
|
/>
|
||||||
<div class="sender">{{msg.sender}}</div>
|
|
||||||
<div class="content">{{msg.content}}</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="msg.type == 'log'">
|
|
||||||
<div class="sender">Log ({{msg.sender}})</div>
|
|
||||||
<div class="content">{{msg.content}}</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="msg.type == 'thread-header'">
|
|
||||||
<div class="content">
|
|
||||||
Thread {{msg.threadId}} ({{msg.messageCount}} messages)
|
|
||||||
<a hred="#" @click.prevent="expandThread(msg.threadId, !msg.expanded)">{{msg.expanded ? "Hide" : "Show"}}</a>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!--message v-if="!msg.collapse" :value="msg.content" :format="msg.format"></message-->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
@@ -511,50 +482,6 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
width: fit-content;
|
|
||||||
max-width: 50em;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note {
|
|
||||||
width: fit-content;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.sent {
|
|
||||||
align-self: end;
|
|
||||||
margin-left: 2em;
|
|
||||||
}
|
|
||||||
.message.received {
|
|
||||||
margin-right: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message .content {
|
|
||||||
white-space: preserve;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message .sender,
|
|
||||||
.note .sender {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 100%;
|
|
||||||
margin-top: -5px; /* half message padding */
|
|
||||||
}
|
|
||||||
|
|
||||||
.message.log .content {
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-container {
|
.input-container {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
|
|||||||
Reference in new issue
Block a user