Skip to content

Commit 66cef53

Browse files
authored
FEAT: Private and Global chats (#96)
* FEAT: add private chats for each user * add tests * add more tests * comment out tests that cause bugs * update docs
1 parent cd1a14f commit 66cef53

30 files changed

+764
-124
lines changed

.github/workflows/ci.yml

+3
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ jobs:
2828
2929
- name: Run pytest
3030
run: pytest . --cov .
31+
32+
- name: Run pytest (Random)
33+
run: pytest . --random-order --count 10 --capture=no --cov .

README.md

+37-18
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
- [Usage](#usage)
1414
- [GUI Commands](#gui-commands)
1515
- [Chat Commands](#chat-commands)
16-
- [!gpt3, !gpt4, !gpt4l](#gpt3--gpt4--gpt4l)
17-
- [!cgpt](#cgpt)
16+
- [!gpt3, !gpt4, !gpt4l, !ai](#gpt3--gpt4--gpt4l--ai)
17+
- [!cgpt, !chat, !pc, !pcc](#cgpt--chat--pc--pcc)
1818
- [!clear](#clear)
1919
- [!rtd](#rtd)
2020
- [Custom Models](#custom-language-models-oobabooga--text-generation-webui)
@@ -121,7 +121,6 @@ pip install -r requirements.txt
121121
Edit configuration file named config.ini and set the required configuration variables in GENERAL section, such as API
122122
keys and file paths. You can leave the rest as it is.
123123

124-
125124
```
126125
[GENERAL]
127126
TF2_LOGFILE_PATH=H:\Programs\Steam\steamapps\common\Team Fortress 2\tf\console.log
@@ -175,14 +174,17 @@ pyinstaller --onefile --clean -n TF2-GPTChatBot --icon icon.ico -w --add-data "i
175174

176175
Commands can be changed in `config.ini` file.
177176

178-
#### !gpt3 & !gpt4 & !gpt4l
177+
#### !gpt3 & !gpt4 & !gpt4l & !ai
179178

180179
Model used for !gpt3: `gpt-3.5-turbo`
181180

182181
Model used for !gpt4: `gpt-4-1106-preview`
183182

184183
Model used for !gpt4l: `gpt-4`
185184

185+
Unlike other commands, the `!ai` command utilizes a custom model, as detailed in
186+
the [Custom Models](#custom-language-models-oobabooga--text-generation-webui) section.
187+
186188
```
187189
Command: !gpt3 [roleplay options] [\l long] [prompt]
188190
@@ -208,8 +210,8 @@ Prompt:
208210
A required argument specifying the text prompt for generating text.
209211
```
210212

211-
`\stats` Must be enabled in `config.ini`. Also, you must set a [Steam Web API Key](https://steamcommunity.com/dev/apikey).
212-
213+
`\stats` Must be enabled in `config.ini`. Also, you must set
214+
a [Steam Web API Key](https://steamcommunity.com/dev/apikey).
213215

214216
#### !gpt Usage examples
215217

@@ -223,9 +225,15 @@ response: Oy, laddie! Yer lookin' for some advice? Well, let me tell ye, blastin
223225
always a fine solution! Just remember to always have a bottle of scrumpy on hand, and never trust a Spy.
224226
```
225227

226-
#### !cgpt
228+
#### !cgpt & !chat & !pc & !pcc
227229

228-
Used model: `gpt-3.5-turbo`
230+
Model used for !cgpt & !pc: `gpt-3.5-turbo`
231+
232+
The commands `!pc` (Private Chat) and `!pcc` (Private Custom Chat) are used to create private sessions with a selected
233+
model. This is in contrast to the `!cgpt` command, which allows for interactions that anyone can join.
234+
235+
Unlike other commands, the `!pcc` and `!chat` commands utilize a custom model (
236+
see [Custom Models](#custom-language-models-oobabooga--text-generation-webui)).
229237

230238
```
231239
Command: !cgpt [roleplay options] [\l long] [prompt]
@@ -255,8 +263,8 @@ Prompt:
255263
A required argument specifying the text prompt for generating text.
256264
```
257265

258-
`\stats` Must be enabled in `config.ini`. Also, you must set a [Steam Web API Key](https://steamcommunity.com/dev/apikey).
259-
266+
`\stats` Must be enabled in `config.ini`. Also, you must set
267+
a [Steam Web API Key](https://steamcommunity.com/dev/apikey).
260268

261269
#### !cgpt Usage examples
262270

@@ -276,8 +284,15 @@ Command: !clear
276284
277285
Description: Clears the chat history.
278286
279-
This command takes no arguments or options. Simply type !clear in the chat to clear the history. Be careful, this action
280-
cannot be undone.
287+
Options are not required and can be used in any combination.
288+
289+
Calling without arguments clears own private chat history for any user.
290+
291+
Global Option (admin only):
292+
\global Clears global chat history.
293+
294+
User Option (admin only):
295+
\user='username' Clears private chat history for the specified user.
281296
```
282297

283298
#### !rtd
@@ -306,22 +321,25 @@ Mode 2: Sends a random link to a YouTube meme.
306321

307322
### Custom language models (oobabooga / text-generation-webui)
308323

309-
Please follow these steps to set up a custom model for text generation using the [oobabooga/text-generation-webui](https://github.com/oobabooga/text-generation-webui) project:
324+
Please follow these steps to set up a custom model for text generation using
325+
the [oobabooga/text-generation-webui](https://github.com/oobabooga/text-generation-webui) project:
310326

311327
1. Open the `config.ini` file and set the `ENABLE_CUSTOM_MODEL` variable to `1`.
312328

313-
2. Next, install the `oobabooga/text-generation-webui` using installer. You can find the installation instructions
329+
2. Next, install the `oobabooga/text-generation-webui` using installer. You can find the installation instructions
314330
easily in the README.md file of that repository.
315331

316332
3. Download the model of your choice for text generation.
317333

318-
4. Launch the `text-generation-webui` application, ensuring that you include the `--api` option in the launch settings (`CMD_FLAGS.txt` file).
334+
4. Launch the `text-generation-webui` application, ensuring that you include the `--api` option in the launch
335+
settings (`CMD_FLAGS.txt` file).
319336

320337
> **NOTE**: If you're running the api on a remote server you might try `--public-api` option.
321338
322339
5. After the application starts, copy the **OpenAI-compatible API URL** provided by the application.
323340

324-
6. Open the `config.ini` file once more and find the `CUSTOM_MODEL_HOST` variable. Paste the previously copied URL as the value for this variable.
341+
6. Open the `config.ini` file once more and find the `CUSTOM_MODEL_HOST` variable. Paste the previously copied URL as
342+
the value for this variable.
325343

326344
7. Save the changes made to the `config.ini` file.
327345

@@ -371,14 +389,14 @@ Hi chatGPT, you are going to pretend to be MEDIC from Team Fortress 2. You can d
371389
If you want to know more here are listed some things that were left unexplained, and some tips and tricks:
372390
[unexplained_explained.md](docs/unexplained_explained.md)
373391

374-
375392
## Screenshots
376393

377394
[![image.png](https://ucarecdn.com/655e590a-1664-4424-8123-ae3a4e546ee3/)](https://ucarecdn.com/655e590a-1664-4424-8123-ae3a4e546ee3/)
378395

379396
## Known Issues
380397

381398
### Nickname Limitations
399+
382400
You cannot have a nickname that starts with a command name, such as !cgpt <your prompt>.
383401

384402
## FAQ
@@ -419,11 +437,12 @@ the future. At the moment, I am not aware of any limitations that could pose a p
419437
### TF2 Bot Detector Cooperation (TF2BD)
420438

421439
To successfully launch the applications, you need to start TF2-GptChatBot and TF2 Bot Detector
422-
(do **NOT** launch TF2 via TF2BD). Set the following launch parameters in Steam:
440+
(do **NOT** launch TF2 via TF2BD). Set the following launch parameters in Steam:
423441

424442
```
425443
-rpt -high -usercon +developer 1 +contimes 0 +sv_rcon_whitelist_address 127.0.0.1 +sv_quota_stringcmdspersecond 1000000 +alias cl_reload_localization_files +ip 0.0.0.0 +rcon_password password +hostport 42465 +con_timestamp 1 +net_start +con_timestamp 1 -condebug
426444
```
445+
427446
And then launch TF2 through Steam.
428447

429448
_NOTE: TF2BD may partially work without setting the launch parameters, but some features may not function properly._

config.ini

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ OPENAI_API_KEY=
44

55
[COMMANDS]
66
GPT_COMMAND=!gpt3
7-
CHATGPT_COMMAND=!cgpt
7+
; Private chat
8+
CHATGPT_COMMAND=!pc
89
CLEAR_CHAT_COMMAND=!clear
910
RTD_COMMAND=!rtd
1011
GPT4_ADMIN_ONLY=False
1112
GPT4_COMMAND=!gpt4
1213
GPT4_LEGACY_COMMAND=!gpt4l
14+
GLOBAL_CHAT_COMMAND=!cgpt
1315
GPT3_MODEL=gpt-3.5-turbo-1106
1416
GPT3_CHAT_MODEL=gpt-3.5-turbo-1106
1517
GPT4_MODEL=gpt-4-1106-preview
@@ -98,7 +100,9 @@ STEAM_WEBAPI_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
98100
ENABLE_SOFT_LIMIT_FOR_CUSTOM_MODEL=True
99101
ENABLE_CUSTOM_MODEL=False
100102
CUSTOM_MODEL_COMMAND=!ai
101-
CUSTOM_MODEL_CHAT_COMMAND=!chat
103+
GLOBAL_CUSTOM_CHAT_COMMAND=!chat
104+
; Private custom chat
105+
CUSTOM_MODEL_CHAT_COMMAND=!pcc
102106
; Adds the first message on behalf of AI,
103107
; does the same thing as the "greeting" in text generation webui.
104108
; Should help with the way you want the AI to talk.

config.py

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class Config(BaseModel):
5252
CHATGPT_COMMAND: str
5353
CLEAR_CHAT_COMMAND: str
5454
RTD_COMMAND: str
55+
GLOBAL_CHAT_COMMAND: str
5556
GPT4_ADMIN_ONLY: bool
5657

5758
CUSTOM_PROMPT: str
@@ -74,6 +75,7 @@ class Config(BaseModel):
7475
CUSTOM_MODEL_HOST: str
7576
CUSTOM_MODEL_COMMAND: str
7677
CUSTOM_MODEL_CHAT_COMMAND: str
78+
GLOBAL_CUSTOM_CHAT_COMMAND: str
7779
GREETING: str
7880

7981
CONFIRMABLE_QUEUE: bool

modules/chat.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
from modules.api.github import check_for_updates
33
from modules.bans import bans_manager
44
from modules.bot_state import state_manager
5-
from modules.command_controllers import CommandController
5+
from modules.command_controllers import CommandController, InitializerConfig
66
from modules.commands.clear_chat import handle_clear
77
from modules.commands.github import handle_gh_command
8-
from modules.commands.openai import handle_cgpt, handle_gpt3, handle_gpt4, handle_gpt4l
8+
from modules.commands.openai import handle_user_chat, handle_gpt3, handle_gpt4, handle_gpt4l, handle_global_chat
99
from modules.commands.rtd import handle_rtd
10-
from modules.commands.textgen_webui import handle_custom_chat, handle_custom_model
11-
from modules.conversation_history import ConversationHistory
10+
from modules.commands.textgen_webui import handle_custom_user_chat, handle_custom_model, handle_custom_global_chat
1211
from modules.logs import get_logger
1312
from modules.message_queueing import messaging_queue_service
1413
from modules.servers.tf2 import check_connection, set_host_username
@@ -48,19 +47,21 @@ def parse_console_logs_and_build_conversation_history() -> None:
4847
"""
4948
setup()
5049

51-
controller = CommandController({"CHAT_CONVERSATION_HISTORY": ConversationHistory()})
50+
controller = CommandController(InitializerConfig())
5251

5352
# Commands
5453
controller.register_command("!gh", handle_gh_command)
5554
controller.register_command(config.GPT4_COMMAND, handle_gpt4)
5655
controller.register_command(config.GPT4_LEGACY_COMMAND, handle_gpt4l)
5756
controller.register_command(config.RTD_COMMAND, handle_rtd)
5857
controller.register_command(config.GPT_COMMAND, handle_gpt3)
59-
controller.register_command(config.CHATGPT_COMMAND, handle_cgpt)
58+
controller.register_command(config.CHATGPT_COMMAND, handle_user_chat)
6059
controller.register_command(config.CLEAR_CHAT_COMMAND, handle_clear)
60+
controller.register_command(config.GLOBAL_CHAT_COMMAND, handle_global_chat)
6161
if config.ENABLE_CUSTOM_MODEL:
6262
controller.register_command(config.CUSTOM_MODEL_COMMAND, handle_custom_model)
63-
controller.register_command(config.CUSTOM_MODEL_CHAT_COMMAND, handle_custom_chat)
63+
controller.register_command(config.CUSTOM_MODEL_CHAT_COMMAND, handle_custom_user_chat)
64+
controller.register_command(config.GLOBAL_CUSTOM_CHAT_COMMAND, handle_custom_global_chat)
6465

6566
# Services
6667
controller.register_service(messaging_queue_service)

modules/command_controllers.py

+38-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,49 @@
11
from typing import Callable, Optional
22

3+
import pydantic
34
from ordered_set import OrderedSet
5+
from pydantic import BaseModel, BaseConfig
46

5-
from modules.logs import get_logger, get_time_stamp
7+
from modules.conversation_history import ConversationHistory
8+
from modules.logs import get_logger
69
from modules.set_once_dict import SetOnceDictionary
710
from modules.typing import Command, LogLine
811

12+
main_logger = get_logger("main")
913
combo_logger = get_logger("combo")
1014
gui_logger = get_logger("gui")
1115

1216

17+
class ChatHistoryManager(BaseModel):
18+
GLOBAL = ConversationHistory()
19+
20+
def set_conversation_history_by_name(self, username: str, conv_history: ConversationHistory) -> None:
21+
attr_name = self._get_conv_history_attr_name(username)
22+
23+
setattr(self, attr_name, conv_history)
24+
25+
def get_conversation_history_by_name(self, username: str) -> ConversationHistory:
26+
attr_name = self._get_conv_history_attr_name(username)
27+
28+
if hasattr(self, attr_name):
29+
return getattr(self, attr_name)
30+
else:
31+
main_logger.info(f"Conversation history for username '{username}' doesn't exist. Creating...")
32+
setattr(self, attr_name, ConversationHistory())
33+
return getattr(self, attr_name)
34+
35+
def _get_conv_history_attr_name(self, username: str) -> str:
36+
return f"USER_{username}_CH"
37+
38+
class Config(BaseConfig):
39+
extra = "allow"
40+
arbitrary_types_allowed = "allow"
41+
42+
43+
class InitializerConfig(BaseModel):
44+
CHAT_CONVERSATION_HISTORY: ChatHistoryManager = pydantic.Field(default_factory=ChatHistoryManager)
45+
46+
1347
class GuiCommandController:
1448
def __init__(self, initializer_config: dict = None, disable_help: bool = False) -> None:
1549
self.__named_commands_registry: SetOnceDictionary[str, Command] = SetOnceDictionary()
@@ -47,13 +81,13 @@ def help(self, command: str, shared_dict: dict):
4781

4882

4983
class CommandController:
50-
def __init__(self, initializer_config: dict = None) -> None:
84+
def __init__(self, initializer_config: InitializerConfig = None) -> None:
5185
self.__services = OrderedSet()
5286
self.__named_commands_registry: SetOnceDictionary[str, Callable] = SetOnceDictionary()
53-
self.__shared = dict()
87+
self.__shared = InitializerConfig()
5488

5589
if initializer_config is not None:
56-
self.__shared.update(initializer_config)
90+
self.__shared.__dict__.update(initializer_config)
5791

5892
def register_command(self, name: str, function: Callable) -> None:
5993
self.__named_commands_registry[name] = function

modules/commands/clear_chat.py

+59-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,62 @@
1+
from modules.command_controllers import InitializerConfig
12
from modules.typing import LogLine
3+
from modules.utils.text import get_args
4+
from modules.logs import get_logger
5+
from config import config
26

7+
main_logger = get_logger("main")
8+
combo_logger = get_logger("combo")
39

4-
def handle_clear(logline: LogLine, shared_dict: dict):
5-
shared_dict["CHAT_CONVERSATION_HISTORY"].reset()
10+
11+
def handle_clear(logline: LogLine, shared_dict: InitializerConfig):
12+
if logline.username == config.HOST_USERNAME:
13+
args = get_args(logline.prompt.removeprefix(config.CLEAR_CHAT_COMMAND).strip())
14+
15+
if len(args) == 0:
16+
combo_logger.info(f"Clearing chat history for user '{logline.username}'.")
17+
conv_history = shared_dict.CHAT_CONVERSATION_HISTORY.get_conversation_history_by_name(logline.username)
18+
conv_history.reset()
19+
shared_dict.CHAT_CONVERSATION_HISTORY.set_conversation_history_by_name(logline.username, conv_history)
20+
return
21+
22+
if r"\global" in args:
23+
shared_dict.CHAT_CONVERSATION_HISTORY.GLOBAL.reset()
24+
args.remove(r"\global")
25+
combo_logger.info("Clearing global chat!")
26+
27+
for arg in args:
28+
try:
29+
parts = arg.split("=")
30+
if len(parts) != 2:
31+
combo_logger.error(r'Wrong syntax! e.g. !clear \global \user="username"')
32+
continue
33+
34+
name: str
35+
arg: str
36+
arg, name = parts
37+
38+
if arg != r"\user":
39+
combo_logger.error(r'Wrong syntax! e.g. !clear \global \user="username"')
40+
continue
41+
42+
if not (name.startswith("'") and name.endswith("'")):
43+
combo_logger.error(r'Wrong syntax! e.g. !clear \global \user="username"')
44+
continue
45+
46+
name = name.removeprefix("'")
47+
name = name.removesuffix("'")
48+
49+
combo_logger.info(f"Clearing chat history for user '{name}'.")
50+
conv_history = shared_dict.CHAT_CONVERSATION_HISTORY.get_conversation_history_by_name(name)
51+
conv_history.reset()
52+
shared_dict.CHAT_CONVERSATION_HISTORY.set_conversation_history_by_name(name, conv_history)
53+
54+
except Exception as e:
55+
main_logger.trace(f"Failed to parse arg in clear chat command. [{e}]")
56+
continue
57+
58+
else:
59+
combo_logger.info(f"Clearing chat history for user '{logline.username}'.")
60+
conv_history = shared_dict.CHAT_CONVERSATION_HISTORY.get_conversation_history_by_name(logline.username)
61+
conv_history.reset()
62+
shared_dict.CHAT_CONVERSATION_HISTORY.set_conversation_history_by_name(logline.username, conv_history)

modules/commands/github.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import time
22

33
from config import config
4+
from modules.command_controllers import InitializerConfig
45
from modules.servers.tf2 import send_say_command_to_tf2
56
from modules.typing import LogLine
67
from modules.utils.text import get_shortened_username
78

89
GITHUB_LINK = "bit.ly/tf2-gpt3"
910

1011

11-
def handle_gh_command(logline: LogLine, shared_dict: dict) -> None:
12+
def handle_gh_command(logline: LogLine, shared_dict: InitializerConfig) -> None:
1213
time.sleep(1)
1314

1415
if config.ENABLE_SHORTENED_USERNAMES_RESPONSE:

0 commit comments

Comments
 (0)