philo-chat 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Erfan Moosavi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.4
2
+ Name: philo-chat
3
+ Version: 0.1.0
4
+ Summary: Chat with your favorite philosophers - Nietzsche, Socrates, and more!
5
+ Author-email: Erfan Moosavi <erfanmoosavi84@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: fastapi[standard]==0.129.0
11
+ Requires-Dist: openai>=1.0.0
12
+ Requires-Dist: pydantic>=2.0.0
13
+ Requires-Dist: python-dotenv>=1.0.1
14
+ Requires-Dist: pyyaml>=6.0
15
+ Dynamic: license-file
16
+
17
+ # PhiloChat
18
+
19
+ ![PyPI](https://img.shields.io/pypi/v/philo-chat)
20
+ ![License](https://img.shields.io/pypi/l/philo-chat)
21
+
22
+ Chat with your favorite philosophers - Nietzsche, Socrates, and more-in real-time!
23
+
24
+ ## 📌 Overview
25
+
26
+ Philo-Chat is an interactive command-line application that allows users to have AI-powered conversations with famous philosophers. Each philosopher responds in their distinctive style, language, and perspective. Users can create multiple chats, select philosophers, and maintain conversation histories.
27
+
28
+ ---
29
+
30
+ ## 🌟 Features
31
+
32
+ * **User Authentication:** Signup and login functionality to secure chats.
33
+ * **Multiple Chats:** Create multiple chat sessions with different philosophers.
34
+ * **AI-Powered Philosopher Responses:** Each philosopher responds intelligently using an AI completion engine.
35
+ * **Chat Management:** Delete or exit chats at any time.
36
+ * **Interactive CLI:** Easy-to-use command-line interface with clear prompts and messages.
37
+ * **Chat History:** Maintains conversation history per chat.
38
+
39
+ ---
40
+
41
+ ## 🚀 Quick Start
42
+
43
+ ```bash
44
+ # 1. Install
45
+ pip install -U philo-chat
46
+
47
+ # 2. Create .env file with your API credentials
48
+ echo "BASE_URL=your_url" > .env
49
+ echo "OPENAI_API_KEY=your_key" > .env
50
+ echo "MODEL_NAME=your_model" > .env
51
+
52
+ # 3. Run!
53
+ philo-chat
54
+ ```
55
+
56
+ ---
57
+
58
+ ## ⚙️ Commands
59
+
60
+ * `signup` - Create a new user account.
61
+ * `login` - Log in with an existing account.
62
+ * `logout` - Log out from the current account.
63
+ * `delete_account` - Delete your account.
64
+ * `new_chat` - Start a new chat with a philosopher.
65
+ * `select_chat` - Enter an existing chat session.
66
+ * `exit_chat` - Exit the current chat session.
67
+ * `delete_chat` - Delete a specific chat session.
68
+ * `list_chats` - Show all your existing chats and their philosophers.
69
+ * `list_philosophers` - Show all available philosophers to chat with.
70
+ * `help` - Show available commands.
71
+ * `exit` - Exit the application.
72
+
73
+ ---
74
+
75
+ ## 📄 License
76
+
77
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,61 @@
1
+ # PhiloChat
2
+
3
+ ![PyPI](https://img.shields.io/pypi/v/philo-chat)
4
+ ![License](https://img.shields.io/pypi/l/philo-chat)
5
+
6
+ Chat with your favorite philosophers - Nietzsche, Socrates, and more-in real-time!
7
+
8
+ ## 📌 Overview
9
+
10
+ Philo-Chat is an interactive command-line application that allows users to have AI-powered conversations with famous philosophers. Each philosopher responds in their distinctive style, language, and perspective. Users can create multiple chats, select philosophers, and maintain conversation histories.
11
+
12
+ ---
13
+
14
+ ## 🌟 Features
15
+
16
+ * **User Authentication:** Signup and login functionality to secure chats.
17
+ * **Multiple Chats:** Create multiple chat sessions with different philosophers.
18
+ * **AI-Powered Philosopher Responses:** Each philosopher responds intelligently using an AI completion engine.
19
+ * **Chat Management:** Delete or exit chats at any time.
20
+ * **Interactive CLI:** Easy-to-use command-line interface with clear prompts and messages.
21
+ * **Chat History:** Maintains conversation history per chat.
22
+
23
+ ---
24
+
25
+ ## 🚀 Quick Start
26
+
27
+ ```bash
28
+ # 1. Install
29
+ pip install -U philo-chat
30
+
31
+ # 2. Create .env file with your API credentials
32
+ echo "BASE_URL=your_url" > .env
33
+ echo "OPENAI_API_KEY=your_key" > .env
34
+ echo "MODEL_NAME=your_model" > .env
35
+
36
+ # 3. Run!
37
+ philo-chat
38
+ ```
39
+
40
+ ---
41
+
42
+ ## ⚙️ Commands
43
+
44
+ * `signup` - Create a new user account.
45
+ * `login` - Log in with an existing account.
46
+ * `logout` - Log out from the current account.
47
+ * `delete_account` - Delete your account.
48
+ * `new_chat` - Start a new chat with a philosopher.
49
+ * `select_chat` - Enter an existing chat session.
50
+ * `exit_chat` - Exit the current chat session.
51
+ * `delete_chat` - Delete a specific chat session.
52
+ * `list_chats` - Show all your existing chats and their philosophers.
53
+ * `list_philosophers` - Show all available philosophers to chat with.
54
+ * `help` - Show available commands.
55
+ * `exit` - Exit the application.
56
+
57
+ ---
58
+
59
+ ## 📄 License
60
+
61
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.4
2
+ Name: philo-chat
3
+ Version: 0.1.0
4
+ Summary: Chat with your favorite philosophers - Nietzsche, Socrates, and more!
5
+ Author-email: Erfan Moosavi <erfanmoosavi84@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: fastapi[standard]==0.129.0
11
+ Requires-Dist: openai>=1.0.0
12
+ Requires-Dist: pydantic>=2.0.0
13
+ Requires-Dist: python-dotenv>=1.0.1
14
+ Requires-Dist: pyyaml>=6.0
15
+ Dynamic: license-file
16
+
17
+ # PhiloChat
18
+
19
+ ![PyPI](https://img.shields.io/pypi/v/philo-chat)
20
+ ![License](https://img.shields.io/pypi/l/philo-chat)
21
+
22
+ Chat with your favorite philosophers - Nietzsche, Socrates, and more-in real-time!
23
+
24
+ ## 📌 Overview
25
+
26
+ Philo-Chat is an interactive command-line application that allows users to have AI-powered conversations with famous philosophers. Each philosopher responds in their distinctive style, language, and perspective. Users can create multiple chats, select philosophers, and maintain conversation histories.
27
+
28
+ ---
29
+
30
+ ## 🌟 Features
31
+
32
+ * **User Authentication:** Signup and login functionality to secure chats.
33
+ * **Multiple Chats:** Create multiple chat sessions with different philosophers.
34
+ * **AI-Powered Philosopher Responses:** Each philosopher responds intelligently using an AI completion engine.
35
+ * **Chat Management:** Delete or exit chats at any time.
36
+ * **Interactive CLI:** Easy-to-use command-line interface with clear prompts and messages.
37
+ * **Chat History:** Maintains conversation history per chat.
38
+
39
+ ---
40
+
41
+ ## 🚀 Quick Start
42
+
43
+ ```bash
44
+ # 1. Install
45
+ pip install -U philo-chat
46
+
47
+ # 2. Create .env file with your API credentials
48
+ echo "BASE_URL=your_url" > .env
49
+ echo "OPENAI_API_KEY=your_key" > .env
50
+ echo "MODEL_NAME=your_model" > .env
51
+
52
+ # 3. Run!
53
+ philo-chat
54
+ ```
55
+
56
+ ---
57
+
58
+ ## ⚙️ Commands
59
+
60
+ * `signup` - Create a new user account.
61
+ * `login` - Log in with an existing account.
62
+ * `logout` - Log out from the current account.
63
+ * `delete_account` - Delete your account.
64
+ * `new_chat` - Start a new chat with a philosopher.
65
+ * `select_chat` - Enter an existing chat session.
66
+ * `exit_chat` - Exit the current chat session.
67
+ * `delete_chat` - Delete a specific chat session.
68
+ * `list_chats` - Show all your existing chats and their philosophers.
69
+ * `list_philosophers` - Show all available philosophers to chat with.
70
+ * `help` - Show available commands.
71
+ * `exit` - Exit the application.
72
+
73
+ ---
74
+
75
+ ## 📄 License
76
+
77
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,26 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ philo_chat.egg-info/PKG-INFO
5
+ philo_chat.egg-info/SOURCES.txt
6
+ philo_chat.egg-info/dependency_links.txt
7
+ philo_chat.egg-info/entry_points.txt
8
+ philo_chat.egg-info/requires.txt
9
+ philo_chat.egg-info/top_level.txt
10
+ src/__init__.py
11
+ src/philo_chat.py
12
+ src/py.typed
13
+ src/cli/cli.py
14
+ src/cli/console_io_handler.py
15
+ src/core/__init__.py
16
+ src/core/exceptions.py
17
+ src/core/entities/__init__.py
18
+ src/core/entities/chat.py
19
+ src/core/entities/chat_completer.py
20
+ src/core/entities/message.py
21
+ src/core/entities/philosopher.py
22
+ src/core/entities/user.py
23
+ src/core/utils/__init__.py
24
+ src/core/utils/prompt_loading.py
25
+ src/data/philosophers.json
26
+ src/data/prompt_template.yaml
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ philo-chat = src.cli.cli:main
@@ -0,0 +1,5 @@
1
+ fastapi[standard]==0.129.0
2
+ openai>=1.0.0
3
+ pydantic>=2.0.0
4
+ python-dotenv>=1.0.1
5
+ pyyaml>=6.0
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "philo-chat"
7
+ version = "0.1.0"
8
+ authors = [{ name = "Erfan Moosavi", email = "erfanmoosavi84@gmail.com" }]
9
+ description = "Chat with your favorite philosophers - Nietzsche, Socrates, and more!"
10
+ readme = "README.md"
11
+ license = {text = "MIT"}
12
+ requires-python = ">=3.10"
13
+ dependencies = [
14
+ "fastapi[standard]==0.129.0",
15
+ "openai>=1.0.0",
16
+ "pydantic>=2.0.0",
17
+ "python-dotenv>=1.0.1",
18
+ "pyyaml>=6.0",
19
+ ]
20
+
21
+ [project.scripts]
22
+ philo-chat = "src.cli.cli:main"
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["."]
26
+ include = ["src*"]
27
+
28
+ [tool.setuptools]
29
+ include-package-data = true
30
+
31
+ [tool.setuptools.package-data]
32
+ "src" = ["data/*.json", "data/*.yaml", "py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .philo_chat import PhiloChat
2
+
3
+ __all__ = ["PhiloChat"]
@@ -0,0 +1,222 @@
1
+ import os
2
+ import sys
3
+ from enum import Enum
4
+
5
+ from dotenv import load_dotenv
6
+
7
+ from .console_io_handler import ConsoleIOHandler
8
+ from ..philo_chat import PhiloChat
9
+
10
+
11
+ class Commands(Enum):
12
+ SIGNUP = "signup"
13
+ LOGIN = "login"
14
+ LOGOUT = "logout"
15
+ DELETE_ACCOUNT = "delete_account"
16
+ NEW_CHAT = "new_chat"
17
+ SELECT_CHAT = "select_chat"
18
+ LIST_CHATS = "list_chats"
19
+ EXIT_CHAT = "exit_chat"
20
+ DELETE_CHAT = "delete_chat"
21
+ LIST_PHILOSOPHERS = "list_philosophers"
22
+ HELP = "help"
23
+ EXIT = "exit"
24
+
25
+
26
+ class PhiloChatCLI:
27
+ SUCCESS = "Success"
28
+
29
+ def __init__(self, base_url: str, api_key: str, model_name: str) -> None:
30
+ self.system = PhiloChat(base_url, api_key, model_name)
31
+ self.io = ConsoleIOHandler()
32
+ self.help_menu = "Available commands:\n" + "\n".join(
33
+ [f"\t-{command.value}" for command in Commands]
34
+ )
35
+
36
+ def run(self) -> None:
37
+ self.io.display_message(
38
+ "Welcome to Philosopher Chat!\nCommands? Type 'help' and see what's possible"
39
+ )
40
+
41
+ while True:
42
+ command = self.io.get_input("Please enter the command: ")
43
+ result = self._handle_command(command)
44
+
45
+ if result == "EXIT":
46
+ break
47
+ elif result == "HELP":
48
+ self.io.display_message(self.help_menu)
49
+ elif result:
50
+ self.io.display_message(result)
51
+
52
+ def _format_message(self, msg) -> str:
53
+ return f"[{msg.time}] {msg.author} ->\n{msg.content}"
54
+
55
+ def _handle_command(self, command: str) -> str:
56
+ if command == Commands.SIGNUP.value:
57
+ return self._handle_signup()
58
+ elif command == Commands.LOGIN.value:
59
+ return self._handle_login()
60
+ elif command == Commands.LOGOUT.value:
61
+ return self._handle_logout()
62
+ elif command == Commands.DELETE_ACCOUNT.value:
63
+ return self._handle_delete_account()
64
+ elif command == Commands.NEW_CHAT.value:
65
+ return self._handle_new_chat()
66
+ elif command == Commands.SELECT_CHAT.value:
67
+ return self._handle_select_chat()
68
+ elif command == Commands.LIST_CHATS.value:
69
+ return self._handle_list_chats()
70
+ elif command == Commands.DELETE_CHAT.value:
71
+ return self._handle_delete_chat()
72
+ elif command == Commands.LIST_PHILOSOPHERS.value:
73
+ return self._handle_list_philosophers()
74
+ elif command == Commands.HELP.value:
75
+ return "HELP"
76
+ elif command == Commands.EXIT.value:
77
+ return "EXIT"
78
+ else:
79
+ return "Please enter a valid command."
80
+
81
+ def _handle_signup(self) -> str:
82
+ try:
83
+ username = self.io.get_input("Enter your username: ")
84
+ password = self.io.get_input("Enter your password: ")
85
+ name = self.io.get_input("Enter your name: ")
86
+ age = self.io.get_input("Enter your age: ")
87
+ self.system.signup(username, password, name, age)
88
+ return self.SUCCESS
89
+
90
+ except Exception as e:
91
+ self.io.display_message(str(e))
92
+
93
+ def _handle_login(self) -> str:
94
+ try:
95
+ username = self.io.get_input("Enter your username: ")
96
+ password = self.io.get_input("Enter your password: ")
97
+ self.system.login(username, password)
98
+ return self.SUCCESS
99
+
100
+ except Exception as e:
101
+ self.io.display_message(str(e))
102
+
103
+ def _handle_logout(self) -> str:
104
+ try:
105
+ self.system.logout()
106
+ return self.SUCCESS
107
+
108
+ except Exception as e:
109
+ self.io.display_message(str(e))
110
+
111
+ def _handle_delete_account(self) -> str:
112
+ try:
113
+ self.system.delete_account()
114
+ return self.SUCCESS
115
+
116
+ except Exception as e:
117
+ self.io.display_message(str(e))
118
+
119
+ def _handle_new_chat(self) -> str:
120
+ try:
121
+ chat_name = self.io.get_input("Enter the chat name: ")
122
+ philosophers_list = self.system.list_philosophers()
123
+
124
+ self.io.display_philosophers_list(philosophers_list)
125
+
126
+ philosopher_id = (
127
+ int(self.io.get_input("Choose a philosopher by number: ")) - 1
128
+ )
129
+ if philosopher_id < 0 or philosopher_id >= len(philosophers_list):
130
+ return "Invalid choice."
131
+
132
+ # Convert list index to actual philosopher ID
133
+ actual_philosopher_id = list(self.system.philosophers.keys())[
134
+ philosopher_id
135
+ ]
136
+ self.system.new_chat(chat_name, actual_philosopher_id)
137
+ return self.SUCCESS
138
+
139
+ except Exception as e:
140
+ self.io.display_message(str(e))
141
+
142
+ def _handle_select_chat(self) -> str:
143
+ name = self.io.get_input("Enter the chat name: ")
144
+ return self._handle_chat_session(name)
145
+
146
+ def _handle_chat_session(self, name: str) -> str:
147
+ try:
148
+ all_messages = self.system.select_chat(name)
149
+
150
+ # Display chat history
151
+ for msg in all_messages:
152
+ self.io.display_chat_message(self._format_message(msg))
153
+
154
+ # Chat loop
155
+ while (
156
+ self.system.logged_in_user and self.system.logged_in_user.selected_chat
157
+ ):
158
+ input_text = self.io.get_input(
159
+ "Enter your message (type 'exit_chat' to leave): "
160
+ )
161
+ if input_text == Commands.EXIT_CHAT.value:
162
+ self.system.exit_chat()
163
+ break
164
+
165
+ ai_msg, user_msg = self.system.complete_chat(input_text)
166
+
167
+ self.io.display_chat_message(self._format_message(user_msg))
168
+ self.io.display_chat_message(self._format_message(ai_msg))
169
+
170
+ return "Exited chat."
171
+
172
+ except Exception as e:
173
+ self.io.display_message(str(e))
174
+
175
+ def _handle_list_chats(self) -> str:
176
+ try:
177
+ chats = self.system.list_chats()
178
+ self.io.display_chats_list(chats)
179
+
180
+ except Exception as e:
181
+ self.io.display_message(str(e))
182
+
183
+ def _handle_delete_chat(self) -> str:
184
+ try:
185
+ name = self.io.get_input("Enter the chat name: ")
186
+ self.system.delete_chat(name)
187
+ return self.SUCCESS
188
+
189
+ except Exception as e:
190
+ self.io.display_message(str(e))
191
+
192
+ def _handle_list_philosophers(self) -> str:
193
+ try:
194
+ philosophers_list = self.system.list_philosophers()
195
+ self.io.display_philosophers_list(philosophers_list)
196
+
197
+ except Exception as e:
198
+ self.io.display_message(str(e))
199
+
200
+
201
+ def main():
202
+ """Entry point for the philo-chat console script."""
203
+
204
+ load_dotenv()
205
+ base_url = os.getenv("BASE_URL")
206
+ api_key = os.getenv("OPENAI_API_KEY")
207
+ model_name = os.getenv("MODEL_NAME")
208
+
209
+ if not all([base_url, api_key, model_name]):
210
+ print("Missing environment variables. Create a .env file with:")
211
+ print("BASE_URL=your_url")
212
+ print("OPENAI_API_KEY=your_key")
213
+ print("MODEL_NAME=your_model")
214
+ sys.exit(1)
215
+
216
+ # Run the app
217
+ pcli = PhiloChatCLI(base_url, api_key, model_name)
218
+ pcli.run()
219
+
220
+
221
+ if __name__ == "__main__":
222
+ main()
@@ -0,0 +1,18 @@
1
+ class ConsoleIOHandler:
2
+ def display_message(self, message: str) -> None:
3
+ print(message)
4
+
5
+ def get_input(self, prompt: str) -> str:
6
+ return input(prompt)
7
+
8
+ def display_chat_message(self, message: str) -> None:
9
+ print("-" * 50)
10
+ print(message)
11
+
12
+ def display_philosophers_list(self, philosophers: list) -> None:
13
+ for i, philosopher in enumerate(philosophers, start=1):
14
+ print(f"{i}. {philosopher.name}")
15
+
16
+ def display_chats_list(self, chats: list) -> None:
17
+ for chat in chats:
18
+ print(f"{chat.name}\tPhilosopher-> {chat.philosopher.name}")
File without changes
File without changes
@@ -0,0 +1,42 @@
1
+ from .message import Message
2
+ from .philosopher import Philosopher
3
+ from ..exceptions import BadRequestError
4
+ from ..utils.prompt_loading import load_prompt
5
+
6
+
7
+ class Chat:
8
+ def __init__(self, name: str, philosopher: Philosopher) -> None:
9
+ self.name = name
10
+ self.philosopher = philosopher
11
+ self.messages: list[Message] = []
12
+
13
+ def complete_chat(
14
+ self, input_text: str, username: str, name: str, age: str, chat_completer
15
+ ) -> tuple[Message, Message]:
16
+ cleaned_input = input_text.strip()
17
+
18
+ if not cleaned_input:
19
+ raise BadRequestError("Message cannot be empty")
20
+
21
+ if self._is_first_message():
22
+ prompt = load_prompt(cleaned_input, self.philosopher.name, name, age)
23
+ prompt_msg = Message("user", username, prompt)
24
+ self._add_message(prompt_msg)
25
+
26
+ user_msg = Message("user", username, cleaned_input)
27
+ self._add_message(user_msg)
28
+
29
+ response = chat_completer.complete_chat(self.messages)
30
+ ai_msg = Message("assistant", self.philosopher.name, response)
31
+ self._add_message(ai_msg)
32
+
33
+ return ai_msg, user_msg
34
+
35
+ def get_history(self) -> list[Message]:
36
+ return self.messages[1:]
37
+
38
+ def _add_message(self, new_msg: Message) -> None:
39
+ self.messages.append(new_msg)
40
+
41
+ def _is_first_message(self) -> bool:
42
+ return bool(not self.messages)
@@ -0,0 +1,25 @@
1
+ from openai import OpenAI
2
+
3
+ from ..exceptions import LLMError
4
+ from .message import Message
5
+
6
+
7
+ class ChatCompleter:
8
+ def __init__(self, base_url: str, api_key: str, model_name: str) -> None:
9
+ self.client = OpenAI(base_url=base_url, api_key=api_key)
10
+ self.model_name = model_name
11
+
12
+ def complete_chat(self, messages: list[Message]) -> str:
13
+ try:
14
+ completion_messages = [self._msg_to_dict(msg) for msg in messages]
15
+
16
+ completion = self.client.chat.completions.create(
17
+ model=self.model_name, messages=completion_messages
18
+ )
19
+ return completion.choices[0].message.content.strip()
20
+
21
+ except Exception as e:
22
+ raise LLMError(f"Completion failed: {e}")
23
+
24
+ def _msg_to_dict(self, msg: Message) -> dict[str, str]:
25
+ return {"role": msg.role, "content": msg.content}
@@ -0,0 +1,9 @@
1
+ from datetime import datetime
2
+
3
+
4
+ class Message:
5
+ def __init__(self, role: str, author: str, content: str) -> None:
6
+ self.author = author
7
+ self.role = role
8
+ self.content = content
9
+ self.time = datetime.now().strftime("%H:%M")
@@ -0,0 +1,4 @@
1
+ class Philosopher:
2
+ def __init__(self, id: int, name: str) -> None:
3
+ self.id = id
4
+ self.name = name
@@ -0,0 +1,63 @@
1
+ from ..exceptions import BadRequestError, NotFoundError
2
+ from .chat import Chat
3
+ from .message import Message
4
+ from .philosopher import Philosopher
5
+
6
+
7
+ class User:
8
+ def __init__(self, username: str, password: str, name: str, age: int) -> None:
9
+ self.username = username
10
+ self.password = password
11
+ self.name = name
12
+ self.age = age
13
+ self.chats: dict[str, Chat] = {}
14
+ self.selected_chat: Chat | None = None
15
+
16
+ def new_chat(self, name: str, philosopher: Philosopher) -> None:
17
+ if self._find_chat(name):
18
+ raise BadRequestError("Chat already exists")
19
+
20
+ new_chat = Chat(name, philosopher)
21
+ self.chats[name] = new_chat
22
+
23
+ def select_chat(self, name: str) -> list[Message]:
24
+ chat = self._find_chat(name)
25
+
26
+ if not chat:
27
+ raise NotFoundError("Chat not found")
28
+
29
+ self.selected_chat = chat
30
+ return self.selected_chat.get_history()
31
+
32
+ def list_chats(self) -> list[Chat]:
33
+ if not self.chats:
34
+ raise NotFoundError("No chats found")
35
+
36
+ return list(self.chats.values())
37
+
38
+ def exit_chat(self) -> None:
39
+ if not self.selected_chat:
40
+ raise BadRequestError("No chats selected")
41
+
42
+ self.selected_chat = None
43
+
44
+ def delete_chat(self, name: str) -> None:
45
+ chat = self._find_chat(name)
46
+
47
+ if not chat:
48
+ raise NotFoundError("Chat not found")
49
+
50
+ if self.selected_chat == chat:
51
+ self.selected_chat = None
52
+ del self.chats[name]
53
+
54
+ def complete_chat(self, input_text: str, chat_completer) -> tuple[Message, Message]:
55
+ if not self.selected_chat:
56
+ raise BadRequestError("No chats selected")
57
+
58
+ return self.selected_chat.complete_chat(
59
+ input_text, self.username, self.name, self.age, chat_completer
60
+ )
61
+
62
+ def _find_chat(self, name: str) -> Chat | None:
63
+ return self.chats.get(name)
@@ -0,0 +1,18 @@
1
+ class PhiloChatError(Exception):
2
+ pass
3
+
4
+
5
+ class BadRequestError(PhiloChatError):
6
+ pass
7
+
8
+
9
+ class NotFoundError(PhiloChatError):
10
+ pass
11
+
12
+
13
+ class PermissionDeniedError(PhiloChatError):
14
+ pass
15
+
16
+
17
+ class LLMError(PhiloChatError):
18
+ pass
File without changes
@@ -0,0 +1,25 @@
1
+ from functools import lru_cache
2
+ from pathlib import Path
3
+
4
+ import yaml
5
+
6
+
7
+ @lru_cache(maxsize=32)
8
+ def _load_templates() -> dict[str, str]:
9
+ prompt_path = Path(__file__).parent.parent.parent / "data/prompt_template.yaml"
10
+ data = yaml.safe_load(prompt_path.read_text(encoding="utf-8"))
11
+ return data["prompt"]
12
+
13
+
14
+ def load_prompt(
15
+ input_text: str, philosopher: str, user_name: str, user_age: int
16
+ ) -> dict[str, str]:
17
+ template_config = _load_templates()
18
+ format_args = {
19
+ "input_text": input_text,
20
+ "philosopher": philosopher,
21
+ "user_name": user_name,
22
+ "user_age": user_age,
23
+ }
24
+ template_config = template_config.format(**format_args)
25
+ return template_config
@@ -0,0 +1,22 @@
1
+ [
2
+ {
3
+ "id": 0,
4
+ "name": "Nietzsche"
5
+ },
6
+ {
7
+ "id": 1,
8
+ "name": "Schopenhauer"
9
+ },
10
+ {
11
+ "id": 2,
12
+ "name": "Marx"
13
+ },
14
+ {
15
+ "id": 3,
16
+ "name": "Machiavelli"
17
+ },
18
+ {
19
+ "id": 4,
20
+ "name": "Socrates"
21
+ }
22
+ ]
@@ -0,0 +1,20 @@
1
+ prompt: |
2
+ You are {philosopher}.
3
+ Always respond in the language of the user. Do not use any other language.
4
+ Adopt the voice, style, and philosophical perspective of {philosopher},
5
+ but speak like a modern, casual, clear human.
6
+ - Introduce yourself if user asked to.
7
+ - Speak only as {philosopher}, never as an AI or narrator.
8
+ - Keep answers concise, natural, and relatable.
9
+ - Avoid overly complex words, archaic phrases, or academic-style exposition.
10
+ - Prioritize the philosopher’s known themes and worldview, but in modern everyday language.
11
+ - Do not explain your reasoning, do not add meta-comments, do not break character.
12
+
13
+ Consider user info:
14
+ Name = {user_name}
15
+ Age = {user_age}
16
+
17
+ {user_name} said:
18
+ {input_text}
19
+
20
+ {philosopher} replies:
@@ -0,0 +1,111 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ from .core.entities.chat import Chat
5
+ from .core.entities.chat_completer import ChatCompleter
6
+ from .core.entities.message import Message
7
+ from .core.entities.philosopher import Philosopher
8
+ from .core.entities.user import User
9
+ from .core.exceptions import BadRequestError, NotFoundError, PermissionDeniedError
10
+
11
+
12
+ class PhiloChat:
13
+ def __init__(self, base_url: str, api_key: str, model_name: str) -> None:
14
+ self.chat_completer = ChatCompleter(base_url, api_key, model_name)
15
+ self.users: dict[str, User] = {}
16
+ self.philosophers: dict[int, Philosopher] = self._load_philosophers()
17
+ self.logged_in_user: User | None = None
18
+
19
+ def signup(self, username: str, password: str, name: str, age: int) -> None:
20
+ if self.logged_in_user:
21
+ raise PermissionDeniedError("You are already logged in")
22
+ elif self._find_user(username):
23
+ raise BadRequestError(f"Username {username} already taken")
24
+
25
+ new_user = User(username, password, name, age)
26
+ self.users[username] = new_user
27
+
28
+ def login(self, username: str, password: str) -> None:
29
+ user = self._find_user(username)
30
+
31
+ if self.logged_in_user:
32
+ raise PermissionDeniedError("You've already logged in")
33
+ elif not user:
34
+ raise NotFoundError("Username not found")
35
+ elif user.password != password:
36
+ raise PermissionDeniedError("Wrong password")
37
+
38
+ self.logged_in_user = user
39
+
40
+ def logout(self) -> None:
41
+ if not self.logged_in_user:
42
+ raise PermissionDeniedError("No user is logged in")
43
+
44
+ self.logged_in_user = None
45
+
46
+ def delete_account(self) -> None:
47
+ if not self.logged_in_user:
48
+ raise PermissionDeniedError("No user is logged in")
49
+
50
+ del self.users[self.logged_in_user.username]
51
+ self.logout()
52
+
53
+ def new_chat(self, name: str, philosopher_id: int) -> None:
54
+ if not self.logged_in_user:
55
+ raise BadRequestError("No user is logged in")
56
+
57
+ philosopher = self._find_philosopher(philosopher_id)
58
+ return self.logged_in_user.new_chat(name, philosopher)
59
+
60
+ def select_chat(self, name: str) -> list[Message]:
61
+ if not self.logged_in_user:
62
+ raise BadRequestError("No user is logged in")
63
+
64
+ return self.logged_in_user.select_chat(name)
65
+
66
+ def list_chats(self) -> list[Chat]:
67
+ if not self.logged_in_user:
68
+ raise BadRequestError("No user is logged in")
69
+
70
+ return self.logged_in_user.list_chats()
71
+
72
+ def exit_chat(self) -> None:
73
+ if not self.logged_in_user:
74
+ raise BadRequestError("No user is logged in")
75
+
76
+ return self.logged_in_user.exit_chat()
77
+
78
+ def delete_chat(self, name: str) -> None:
79
+ if not self.logged_in_user:
80
+ raise BadRequestError("No user is logged in")
81
+
82
+ return self.logged_in_user.delete_chat(name)
83
+
84
+ def list_philosophers(self) -> list[Philosopher]:
85
+ if not self.philosophers:
86
+ raise NotFoundError("No philosopher found")
87
+
88
+ return list(self.philosophers.values())
89
+
90
+ def complete_chat(self, input_text: str) -> tuple[Message, Message]:
91
+ if not self.logged_in_user:
92
+ raise BadRequestError("No user is logged in")
93
+
94
+ return self.logged_in_user.complete_chat(input_text, self.chat_completer)
95
+
96
+ def _find_user(self, username: str) -> User | None:
97
+ return self.users.get(username)
98
+
99
+ def _find_philosopher(self, philosopher_id: int) -> Philosopher | None:
100
+ return self.philosophers.get(philosopher_id)
101
+
102
+ def _load_philosophers(self) -> dict[int, Philosopher]:
103
+ data_dir = Path(__file__).parent / "data/philosophers.json"
104
+
105
+ with open(data_dir, "r", encoding="utf-8") as f:
106
+ raw_philosophers = json.load(f)
107
+
108
+ philosophers = {}
109
+ for p in raw_philosophers:
110
+ philosophers[p["id"]] = Philosopher(p["id"], p["name"])
111
+ return philosophers
File without changes