tech-envision commited on
Commit
f9b0b58
·
1 Parent(s): 9fca29d

feat(bot): support text file uploads

Browse files
Files changed (2) hide show
  1. README.md +4 -0
  2. 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 or not message.content.strip():
 
 
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
- try:
45
- reply = await chat.chat(message.content)
46
- except Exception:
47
- self._log.exception("Failed to generate reply")
48
- return
49
-
 
 
 
 
 
 
 
 
 
50
  if reply:
51
- await message.reply(reply, mention_author=False)
 
 
 
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",