Spaces:
Runtime error
Runtime error
tech-envision
commited on
Commit
·
f9b0b58
1
Parent(s):
9fca29d
feat(bot): support text file uploads
Browse files- README.md +4 -0
- bot/discord_bot.py +60 -8
README.md
CHANGED
@@ -31,6 +31,10 @@ async with ChatSession() as chat:
|
|
31 |
reply = await chat.chat(f"Summarize {path_in_vm}")
|
32 |
```
|
33 |
|
|
|
|
|
|
|
|
|
34 |
## Docker
|
35 |
|
36 |
A Dockerfile is provided to run the Discord bot along with an Ollama server. The image installs Ollama, pulls the LLM and embedding models, and starts both the server and the bot.
|
|
|
31 |
reply = await chat.chat(f"Summarize {path_in_vm}")
|
32 |
```
|
33 |
|
34 |
+
When using the Discord bot, attach one or more text files to a message to
|
35 |
+
upload them automatically. The bot responds with the location of each document
|
36 |
+
inside the VM so they can be referenced in subsequent prompts.
|
37 |
+
|
38 |
## Docker
|
39 |
|
40 |
A Dockerfile is provided to run the Discord bot along with an Ollama server. The image installs Ollama, pulls the LLM and embedding models, and starts both the server and the bot.
|
bot/discord_bot.py
CHANGED
@@ -3,6 +3,10 @@
|
|
3 |
from __future__ import annotations
|
4 |
|
5 |
|
|
|
|
|
|
|
|
|
6 |
import discord
|
7 |
from discord import app_commands
|
8 |
from discord.ext import commands
|
@@ -25,6 +29,40 @@ class LLMDiscordBot(commands.Bot):
|
|
25 |
self._log = get_logger(self.__class__.__name__)
|
26 |
self.tree.add_command(self.reset_conversation)
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
async def setup_hook(self) -> None: # noqa: D401
|
29 |
await self.tree.sync()
|
30 |
|
@@ -32,7 +70,9 @@ class LLMDiscordBot(commands.Bot):
|
|
32 |
self._log.info("Logged in as %s (%s)", self.user, self.user.id)
|
33 |
|
34 |
async def on_message(self, message: discord.Message) -> None: # noqa: D401
|
35 |
-
if message.author.bot
|
|
|
|
|
36 |
return
|
37 |
|
38 |
user_id = f"{DEFAULT_USER_PREFIX}{message.author.id}"
|
@@ -41,14 +81,26 @@ class LLMDiscordBot(commands.Bot):
|
|
41 |
self._log.debug("Received message from %s: %s", user_id, message.content)
|
42 |
|
43 |
async with ChatSession(user=user_id, session=session_id) as chat:
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
if reply:
|
51 |
-
|
|
|
|
|
|
|
52 |
|
53 |
@app_commands.command(
|
54 |
name="reset",
|
|
|
3 |
from __future__ import annotations
|
4 |
|
5 |
|
6 |
+
import os
|
7 |
+
import tempfile
|
8 |
+
from pathlib import Path
|
9 |
+
|
10 |
import discord
|
11 |
from discord import app_commands
|
12 |
from discord.ext import commands
|
|
|
29 |
self._log = get_logger(self.__class__.__name__)
|
30 |
self.tree.add_command(self.reset_conversation)
|
31 |
|
32 |
+
async def _upload_attachments(
|
33 |
+
self, chat: ChatSession, attachments: list[discord.Attachment]
|
34 |
+
) -> list[str]:
|
35 |
+
"""Persist text attachments and return their VM paths."""
|
36 |
+
|
37 |
+
uploaded: list[str] = []
|
38 |
+
for att in attachments:
|
39 |
+
if att.content_type and not att.content_type.startswith("text"):
|
40 |
+
continue
|
41 |
+
if not att.filename.lower().endswith(".txt") and not (
|
42 |
+
att.content_type and att.content_type.startswith("text")
|
43 |
+
):
|
44 |
+
continue
|
45 |
+
try:
|
46 |
+
data = await att.read()
|
47 |
+
except Exception:
|
48 |
+
self._log.exception("Failed to download attachment %s", att.filename)
|
49 |
+
continue
|
50 |
+
|
51 |
+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
52 |
+
tmp.write(data)
|
53 |
+
tmp_path = Path(tmp.name)
|
54 |
+
|
55 |
+
try:
|
56 |
+
vm_path = chat.upload_document(str(tmp_path))
|
57 |
+
finally:
|
58 |
+
try:
|
59 |
+
os.remove(tmp_path)
|
60 |
+
except OSError:
|
61 |
+
pass
|
62 |
+
|
63 |
+
uploaded.append(f"{att.filename} -> {vm_path}")
|
64 |
+
return uploaded
|
65 |
+
|
66 |
async def setup_hook(self) -> None: # noqa: D401
|
67 |
await self.tree.sync()
|
68 |
|
|
|
70 |
self._log.info("Logged in as %s (%s)", self.user, self.user.id)
|
71 |
|
72 |
async def on_message(self, message: discord.Message) -> None: # noqa: D401
|
73 |
+
if message.author.bot:
|
74 |
+
return
|
75 |
+
if not message.content.strip() and not message.attachments:
|
76 |
return
|
77 |
|
78 |
user_id = f"{DEFAULT_USER_PREFIX}{message.author.id}"
|
|
|
81 |
self._log.debug("Received message from %s: %s", user_id, message.content)
|
82 |
|
83 |
async with ChatSession(user=user_id, session=session_id) as chat:
|
84 |
+
uploaded_paths: list[str] = []
|
85 |
+
if message.attachments:
|
86 |
+
uploaded_paths = await self._upload_attachments(chat, message.attachments)
|
87 |
+
|
88 |
+
reply: str | None = None
|
89 |
+
if message.content.strip():
|
90 |
+
try:
|
91 |
+
reply = await chat.chat(message.content)
|
92 |
+
except Exception:
|
93 |
+
self._log.exception("Failed to generate reply")
|
94 |
+
return
|
95 |
+
|
96 |
+
responses: list[str] = []
|
97 |
+
if uploaded_paths:
|
98 |
+
responses.append("Uploaded:\n" + "\n".join(uploaded_paths))
|
99 |
if reply:
|
100 |
+
responses.append(reply)
|
101 |
+
|
102 |
+
if responses:
|
103 |
+
await message.reply("\n\n".join(responses), mention_author=False)
|
104 |
|
105 |
@app_commands.command(
|
106 |
name="reset",
|