From eb9a1ad9e83357a8abdfb9ba7f853b41ae37daf1 Mon Sep 17 00:00:00 2001 From: Shivering Ocelot Date: Sun, 3 May 2026 15:54:42 -0500 Subject: [PATCH] Markdown message (half) support --- backend/src/agents/expert.py | 2 + backend/src/app_mixins/chat.py | 4 +- backend/src/chat.py | 5 +- backend/src/database.py | 11 ++- backend/src/inference.py | 6 +- backend/src/server.py | 8 +- frontend/package.json | 1 + frontend/src/components/Message.vue | 113 ++++++++++++++++++++++++++++ frontend/src/views/Chat.vue | 89 ++-------------------- 9 files changed, 147 insertions(+), 92 deletions(-) create mode 100644 frontend/src/components/Message.vue diff --git a/backend/src/agents/expert.py b/backend/src/agents/expert.py index 0942179..469210a 100644 --- a/backend/src/agents/expert.py +++ b/backend/src/agents/expert.py @@ -71,6 +71,8 @@ class ExpertAgent(Agent): elif model_engine == "replicate": if 'parameters' in db_model: 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) diff --git a/backend/src/app_mixins/chat.py b/backend/src/app_mixins/chat.py index bdef4b4..89a5006 100644 --- a/backend/src/app_mixins/chat.py +++ b/backend/src/app_mixins/chat.py @@ -122,7 +122,7 @@ class ChatMixin: sender=sender, kind=kind or 'message', subkind=subkind, - #format=format, + format=format, content=initial_content, thread_id=thread_id ) @@ -159,7 +159,7 @@ class ChatMixin: for agent_id in agent_ids: if str(agent_id) != str(sender): await self.notify_message_to_agent(agent_id, message) - + def notify_users(): nonlocal updating self.notify_message_to_user(chat_owner_id, message, updating) diff --git a/backend/src/chat.py b/backend/src/chat.py index e319d3c..1a63cf8 100644 --- a/backend/src/chat.py +++ b/backend/src/chat.py @@ -8,13 +8,14 @@ import temporary as tmp MESSAGE_PAGE_SIZE = 256 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.content = content self.sender = sender self.time = time self.kind = kind self.subkind = subkind + self.format = format self.thread_id = thread_id def from_db_record(record): @@ -32,6 +33,8 @@ class Message: "time": self.time, "sender": self.sender, "kind": self.kind, + "subkind": self.subkind, + "format": self.format, "content": self.content, "threadId": self.thread_id, } diff --git a/backend/src/database.py b/backend/src/database.py index 4cd626b..c7d0800 100644 --- a/backend/src/database.py +++ b/backend/src/database.py @@ -31,7 +31,16 @@ async def init_database (data_directory): # Checks whether an object is an instance of any database model 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): id = fields.IntField(pk=True) diff --git a/backend/src/inference.py b/backend/src/inference.py index a1815f9..2174608 100644 --- a/backend/src/inference.py +++ b/backend/src/inference.py @@ -33,8 +33,8 @@ def get_llama_cpp_model(app, model_file): return last_model_instance if last_model_instance is not None: - #last_model_instance = None - del last_model_instance + last_model_instance = None + #del last_model_instance last_model_file = model_file 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): print("START INFERENCE") - completion = replicate_client.run(model, input=input) + completion = replicate_client.run(model, input=input, stream=True) for event in completion: if event: content_stream.feed(str(event)) diff --git a/backend/src/server.py b/backend/src/server.py index 723e425..c2904d8 100644 --- a/backend/src/server.py +++ b/backend/src/server.py @@ -12,12 +12,12 @@ from stream import Stream # relative to the module file???? wtf frontend_path = '../../frontend/dist' -def start_server(app): +async def start_server(app): nest_asyncio.apply() server = Quart(__name__, static_folder=frontend_path) server.secret_key = 'horde-secret' - auth_manager = QuartAuth(server, mode="brearer") + auth_manager = QuartAuth(server, mode="bearer") stream_listeners = {} @@ -116,7 +116,7 @@ def start_server(app): return # 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'})) send_queue = asyncio.Queue() @@ -307,4 +307,4 @@ def start_server(app): print("User logged out:", user_id) app.remove_user_connection(user_id, send_queue) - server.run(debug=True) \ No newline at end of file + server.run(debug=True) diff --git a/frontend/package.json b/frontend/package.json index 4ed4094..e8b9a3e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "marked": "^18.0.2", "vite-plugin-pwa": "^1.0.2", "vue": "^3.5.17", "vue-router": "^4.5.1" diff --git a/frontend/src/components/Message.vue b/frontend/src/components/Message.vue new file mode 100644 index 0000000..43c2bc0 --- /dev/null +++ b/frontend/src/components/Message.vue @@ -0,0 +1,113 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Chat.vue b/frontend/src/views/Chat.vue index ea43b79..c2c6e13 100644 --- a/frontend/src/views/Chat.vue +++ b/frontend/src/views/Chat.vue @@ -5,6 +5,7 @@ } from "vue"; import TitleBar from '../components/TitleBar.vue'; + import Message from '../components/Message.vue'; import * as backend from '../backend'; 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) { if (event.key == "Enter" && event.ctrlKey) { event.preventDefault(); @@ -433,31 +422,13 @@
-
- - - - - - - - - -
+
@@ -511,50 +482,6 @@ 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 { padding: 10px; background-color: var(--color-background-soft);