neuro-simulator 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- neuro_simulator/api/stream.py +2 -2
- neuro_simulator/core/application.py +47 -12
- neuro_simulator/services/letta.py +32 -2
- neuro_simulator/utils/process.py +8 -3
- neuro_simulator/utils/queue.py +3 -1
- neuro_simulator/utils/state.py +5 -1
- {neuro_simulator-0.2.1.dist-info → neuro_simulator-0.3.0.dist-info}/METADATA +1 -1
- {neuro_simulator-0.2.1.dist-info → neuro_simulator-0.3.0.dist-info}/RECORD +11 -11
- {neuro_simulator-0.2.1.dist-info → neuro_simulator-0.3.0.dist-info}/WHEEL +0 -0
- {neuro_simulator-0.2.1.dist-info → neuro_simulator-0.3.0.dist-info}/entry_points.txt +0 -0
- {neuro_simulator-0.2.1.dist-info → neuro_simulator-0.3.0.dist-info}/top_level.txt +0 -0
neuro_simulator/api/stream.py
CHANGED
@@ -33,7 +33,7 @@ async def start_stream():
|
|
33
33
|
async def stop_stream():
|
34
34
|
"""Stops the live stream processes."""
|
35
35
|
if process_manager.is_running:
|
36
|
-
process_manager.stop_live_processes()
|
36
|
+
await process_manager.stop_live_processes()
|
37
37
|
return {"status": "success", "message": "Stream stopped"}
|
38
38
|
else:
|
39
39
|
return {"status": "info", "message": "Stream is not running"}
|
@@ -41,7 +41,7 @@ async def stop_stream():
|
|
41
41
|
@router.post("/restart", dependencies=[Depends(get_api_token)])
|
42
42
|
async def restart_stream():
|
43
43
|
"""Restarts the live stream processes."""
|
44
|
-
process_manager.stop_live_processes()
|
44
|
+
await process_manager.stop_live_processes()
|
45
45
|
await asyncio.sleep(1) # Give time for tasks to cancel
|
46
46
|
process_manager.start_live_processes()
|
47
47
|
return {"status": "success", "message": "Stream restarted"}
|
@@ -17,6 +17,8 @@ from starlette.websockets import WebSocketState
|
|
17
17
|
# --- Core Imports ---
|
18
18
|
from .config import config_manager, AppSettings
|
19
19
|
from ..core.agent_factory import create_agent
|
20
|
+
from ..services.letta import LettaAgent
|
21
|
+
from ..services.builtin import BuiltinAgentWrapper
|
20
22
|
|
21
23
|
# --- API Routers ---
|
22
24
|
from ..api.agent import router as agent_router
|
@@ -130,16 +132,40 @@ async def neuro_response_cycle():
|
|
130
132
|
|
131
133
|
while True:
|
132
134
|
try:
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
135
|
+
selected_chats = []
|
136
|
+
# Superchat logic
|
137
|
+
if app_state.superchat_queue and (time.time() - app_state.last_superchat_time > 10):
|
138
|
+
sc = app_state.superchat_queue.popleft()
|
139
|
+
app_state.last_superchat_time = time.time()
|
140
|
+
await connection_manager.broadcast({"type": "processing_superchat", "data": sc})
|
141
|
+
|
142
|
+
# Agent-specific payload generation for superchats
|
143
|
+
if isinstance(agent, LettaAgent):
|
144
|
+
selected_chats = [
|
145
|
+
{"role": "system", "content": "=== RANDOM 10 MSG IN CHATROOM ===\nNO MSG FETCH DUE TO UNPROCESSED HIGHLIGHTED MESSAGE"},
|
146
|
+
{"role": "system", "content": f"=== HIGHLIGHTED MESSAGE ===\n{sc['username']}: {sc['text']}"}
|
147
|
+
]
|
148
|
+
else: # For BuiltinAgent and any other future agents
|
149
|
+
selected_chats = [{'username': sc['username'], 'text': sc['text']}]
|
150
|
+
|
151
|
+
# Clear the regular input queue to prevent immediate follow-up with normal chats
|
152
|
+
get_all_neuro_input_chats()
|
153
|
+
else:
|
154
|
+
if is_first_response:
|
155
|
+
add_to_neuro_input_queue({"username": "System", "text": config_manager.settings.neuro_behavior.initial_greeting})
|
156
|
+
is_first_response = False
|
157
|
+
elif is_neuro_input_queue_empty():
|
158
|
+
await asyncio.sleep(1)
|
159
|
+
continue
|
160
|
+
|
161
|
+
current_queue_snapshot = get_all_neuro_input_chats()
|
162
|
+
if not current_queue_snapshot:
|
163
|
+
continue
|
164
|
+
sample_size = min(config_manager.settings.neuro_behavior.input_chat_sample_size, len(current_queue_snapshot))
|
165
|
+
selected_chats = random.sample(current_queue_snapshot, sample_size)
|
139
166
|
|
140
|
-
|
141
|
-
|
142
|
-
selected_chats = random.sample(current_queue_snapshot, sample_size)
|
167
|
+
if not selected_chats:
|
168
|
+
continue
|
143
169
|
|
144
170
|
response_result = await asyncio.wait_for(agent.process_messages(selected_chats), timeout=20.0)
|
145
171
|
|
@@ -208,7 +234,7 @@ async def startup_event():
|
|
208
234
|
logger.info("FastAPI application has started.")
|
209
235
|
|
210
236
|
@app.on_event("shutdown")
|
211
|
-
|
237
|
+
def shutdown_event():
|
212
238
|
"""Actions to perform on application shutdown."""
|
213
239
|
if process_manager.is_running:
|
214
240
|
process_manager.stop_live_processes()
|
@@ -232,11 +258,20 @@ async def websocket_stream_endpoint(websocket: WebSocket):
|
|
232
258
|
raw_data = await websocket.receive_text()
|
233
259
|
data = json.loads(raw_data)
|
234
260
|
if data.get("type") == "user_message":
|
235
|
-
user_message = {"username": data.get("username", "User"), "text": data.get("
|
261
|
+
user_message = {"username": data.get("username", "User"), "text": data.get("text", "").strip()}
|
236
262
|
if user_message["text"]:
|
237
263
|
add_to_audience_buffer(user_message)
|
238
264
|
add_to_neuro_input_queue(user_message)
|
239
265
|
await connection_manager.broadcast({"type": "chat_message", **user_message, "is_user_message": True})
|
266
|
+
elif data.get("type") == "superchat":
|
267
|
+
sc_message = {
|
268
|
+
"username": data.get("username", "User"),
|
269
|
+
"text": data.get("text", "").strip(),
|
270
|
+
"sc_type": data.get("sc_type", "bits")
|
271
|
+
}
|
272
|
+
if sc_message["text"]:
|
273
|
+
app_state.superchat_queue.append(sc_message)
|
274
|
+
|
240
275
|
except WebSocketDisconnect:
|
241
276
|
pass
|
242
277
|
finally:
|
@@ -275,4 +310,4 @@ def run_server(host: str = None, port: int = None):
|
|
275
310
|
host=server_host,
|
276
311
|
port=server_port,
|
277
312
|
reload=False
|
278
|
-
)
|
313
|
+
)
|
@@ -74,8 +74,38 @@ class LettaAgent(BaseAgent):
|
|
74
74
|
logger.warning(f"Failed to reset Letta Agent message history: {e}")
|
75
75
|
|
76
76
|
async def process_messages(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
77
|
-
if
|
78
|
-
|
77
|
+
# Check if this is a superchat message based on the specific structure
|
78
|
+
# from neuro_response_cycle
|
79
|
+
is_superchat = (
|
80
|
+
len(messages) == 2 and
|
81
|
+
messages[0].get("role") == "system" and
|
82
|
+
messages[1].get("role") == "system" and
|
83
|
+
"HIGHLIGHTED MESSAGE" in messages[1].get("content", "")
|
84
|
+
)
|
85
|
+
|
86
|
+
if is_superchat:
|
87
|
+
try:
|
88
|
+
# Extract username and text from the superchat message
|
89
|
+
# Format is "=== HIGHLIGHTED MESSAGE ===\n{username}: {text}"
|
90
|
+
content_lines = messages[1]["content"].split('\n', 1)
|
91
|
+
user_and_text = content_lines[1]
|
92
|
+
parts = user_and_text.split(':', 1)
|
93
|
+
sc_username = parts[0].strip()
|
94
|
+
sc_text = parts[1].strip()
|
95
|
+
injected_chat_lines = [f"{sc_username}: {sc_text}"]
|
96
|
+
injected_chat_text = (
|
97
|
+
"Here is a highlighted message from my Twitch chat:\n---\n" +
|
98
|
+
"\n".join(injected_chat_lines) +
|
99
|
+
"\n---\nNow, as the streamer Neuro-Sama, please continue the conversation naturally."
|
100
|
+
)
|
101
|
+
logger.info(f"Processing highlighted message for Letta: {injected_chat_lines[0]}")
|
102
|
+
except (IndexError, AttributeError) as e:
|
103
|
+
logger.error(f"Failed to parse superchat for Letta, falling back. Error: {e}")
|
104
|
+
# Fallback to default empty prompt if parsing fails
|
105
|
+
injected_chat_text = "My chat is quiet right now. As Neuro-Sama, what should I say to engage them?"
|
106
|
+
|
107
|
+
elif messages:
|
108
|
+
injected_chat_lines = [f"{chat['username']}: {chat['text']}" for chat in messages if 'username' in chat and 'text' in chat]
|
79
109
|
injected_chat_text = (
|
80
110
|
"Here are some recent messages from my Twitch chat:\n---\n" +
|
81
111
|
"\n".join(injected_chat_lines) +
|
neuro_simulator/utils/process.py
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
import asyncio
|
3
3
|
import logging
|
4
4
|
|
5
|
+
from ..services.stream import live_stream_manager
|
6
|
+
from .websocket import connection_manager
|
7
|
+
|
5
8
|
logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
|
6
9
|
|
7
10
|
class ProcessManager:
|
@@ -28,7 +31,6 @@ class ProcessManager:
|
|
28
31
|
|
29
32
|
logger.info("Starting core stream processes...")
|
30
33
|
from ..core.application import generate_audience_chat_task, neuro_response_cycle, broadcast_events_task
|
31
|
-
from ..services.stream import live_stream_manager
|
32
34
|
from ..utils.queue import clear_all_queues
|
33
35
|
from ..core.agent_factory import create_agent
|
34
36
|
|
@@ -45,10 +47,14 @@ class ProcessManager:
|
|
45
47
|
self._is_running = True
|
46
48
|
logger.info(f"Core processes started: {len(self._tasks)} tasks.")
|
47
49
|
|
48
|
-
def stop_live_processes(self):
|
50
|
+
async def stop_live_processes(self):
|
49
51
|
"""Stops and cleans up all running background tasks."""
|
50
52
|
if not self.is_running:
|
51
53
|
return
|
54
|
+
|
55
|
+
logger.info("Broadcasting offline message before stopping tasks...")
|
56
|
+
await connection_manager.broadcast({"type": "offline"})
|
57
|
+
await asyncio.sleep(0.1) # Give a brief moment for the message to be sent
|
52
58
|
|
53
59
|
logger.info(f"Stopping {len(self._tasks)} core tasks...")
|
54
60
|
for task in self._tasks:
|
@@ -58,7 +64,6 @@ class ProcessManager:
|
|
58
64
|
self._tasks.clear()
|
59
65
|
self._is_running = False
|
60
66
|
|
61
|
-
from ..services.stream import live_stream_manager
|
62
67
|
live_stream_manager.reset_stream_state()
|
63
68
|
|
64
69
|
logger.info("All core tasks have been stopped.")
|
neuro_simulator/utils/queue.py
CHANGED
@@ -6,6 +6,7 @@ from collections import deque
|
|
6
6
|
from pathlib import Path
|
7
7
|
|
8
8
|
from ..core.config import config_manager
|
9
|
+
from ..utils.state import app_state
|
9
10
|
|
10
11
|
logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
|
11
12
|
|
@@ -17,7 +18,8 @@ def clear_all_queues():
|
|
17
18
|
"""Clears all chat queues."""
|
18
19
|
audience_chat_buffer.clear()
|
19
20
|
neuro_input_queue.clear()
|
20
|
-
|
21
|
+
app_state.superchat_queue.clear()
|
22
|
+
logger.info("All chat queues (including superchats) have been cleared.")
|
21
23
|
|
22
24
|
def add_to_audience_buffer(chat_item: dict):
|
23
25
|
"""Adds a chat item to the audience buffer."""
|
neuro_simulator/utils/state.py
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
"""Manages the shared state of the application using a singleton class."""
|
3
3
|
|
4
4
|
import asyncio
|
5
|
+
import time
|
6
|
+
from collections import deque
|
5
7
|
|
6
8
|
class AppState:
|
7
9
|
"""A singleton class to hold all shared application state."""
|
@@ -9,6 +11,8 @@ class AppState:
|
|
9
11
|
self.live_phase_started_event = asyncio.Event()
|
10
12
|
self.neuro_last_speech_lock = asyncio.Lock()
|
11
13
|
self.neuro_last_speech: str = "Neuro-Sama has just started the stream and hasn't said anything yet."
|
14
|
+
self.superchat_queue = deque()
|
15
|
+
self.last_superchat_time: float = 0.0
|
12
16
|
|
13
17
|
# Create a single, globally accessible instance of the AppState.
|
14
|
-
app_state = AppState()
|
18
|
+
app_state = AppState()
|
@@ -11,27 +11,27 @@ neuro_simulator/agent/tools/__init__.py,sha256=1WZy6PADfi6o1avyy1y-ThWBFAPJ_bBqt
|
|
11
11
|
neuro_simulator/agent/tools/core.py,sha256=o6Oyis-HFD-g6Z_u3T--tkmr9ylKJvybKqMRSMUwi1Q,5555
|
12
12
|
neuro_simulator/api/__init__.py,sha256=5LWyDSayPGdQS8Rv13nmAKLyhPnMVPyTYDdvoMPB4xw,56
|
13
13
|
neuro_simulator/api/agent.py,sha256=ABl_JoIxB4wW_J2J52bWndmTXkfGJBS5LZmbGuh7zv8,6343
|
14
|
-
neuro_simulator/api/stream.py,sha256=
|
14
|
+
neuro_simulator/api/stream.py,sha256=Yg-cwjVI2HTLAt7moWbDQ1ZbbP6QE1ZB8LrW6A3tcQk,2081
|
15
15
|
neuro_simulator/api/system.py,sha256=hXznMcThuFhwopYWgpzrRxwtBuFnF_b_vinkOaE5XOs,3712
|
16
16
|
neuro_simulator/core/__init__.py,sha256=-ojq25c8XA0CU25b0OxcGjH4IWFEDHR-HXSRSZIuKe8,57
|
17
17
|
neuro_simulator/core/agent_factory.py,sha256=qMFidwT5IrOkyNHwmpO8_fRv20KLbaIBfWF-VTFCLNA,1742
|
18
18
|
neuro_simulator/core/agent_interface.py,sha256=r58Opcgs7SWVovYTjMWuxF8AiTy9QfRz276_YGmSei0,2791
|
19
|
-
neuro_simulator/core/application.py,sha256=
|
19
|
+
neuro_simulator/core/application.py,sha256=fM9XgYBvx-mNB173rYDezbKdLJRTJ5Mr0nOtxNlSfSc,13365
|
20
20
|
neuro_simulator/core/config.py,sha256=brA8kiekV_995mpz04JiGj1swIWbZZuWWLNYtbroMyE,14884
|
21
21
|
neuro_simulator/services/__init__.py,sha256=s3ZrAHg5TpJakadAAGY1h0wDw_xqN4Je4aJwJyRBmk4,61
|
22
22
|
neuro_simulator/services/audience.py,sha256=0phlhsujh_GMXm_UMiyOntY-ZMtoseRa_FroIfc5A6w,5028
|
23
23
|
neuro_simulator/services/audio.py,sha256=ZscQA25wVYpm9FUl4Hya7tKH8t0TjR3th9-OEZ0G7xk,2934
|
24
24
|
neuro_simulator/services/builtin.py,sha256=7ePxEom5HIK6wGto_H5M8JOnAjyiHqUuE381DEGgzjE,3821
|
25
|
-
neuro_simulator/services/letta.py,sha256=
|
25
|
+
neuro_simulator/services/letta.py,sha256=6jBvOTsLMlRILDv-fvX9fhHMONSYeu-ImJGFcKU00kc,11067
|
26
26
|
neuro_simulator/services/stream.py,sha256=dG7RuNI_ICohPkqKZ-zlBppo54BgWm_KYBs-ezzc73E,5907
|
27
27
|
neuro_simulator/utils/__init__.py,sha256=xSEFzjT827W81mNyQ_DLtr00TgFlttqfFgpz9pSxFXQ,58
|
28
28
|
neuro_simulator/utils/logging.py,sha256=BO-q_cCcoeamsc8eJqq2-L3Z8nhApze_v6LnmD-O8Ww,3411
|
29
|
-
neuro_simulator/utils/process.py,sha256=
|
30
|
-
neuro_simulator/utils/queue.py,sha256=
|
31
|
-
neuro_simulator/utils/state.py,sha256=
|
29
|
+
neuro_simulator/utils/process.py,sha256=9w2JQH59Wy6L8ADrig2QF88iajdykGPZIYJVJe6Al2Y,2603
|
30
|
+
neuro_simulator/utils/queue.py,sha256=vSkh-BrgfsGN_gDAx2mfK44ydmMapdVyLsDG-7LIJZQ,1643
|
31
|
+
neuro_simulator/utils/state.py,sha256=DdBqSAYfjOFtJfB1hEGhYPh32r1ZvFuVlN_-29_-luA,664
|
32
32
|
neuro_simulator/utils/websocket.py,sha256=yOdFvJzbNhcUn5EAuyS55G_R8q-snas5OvkOtS8g19E,2292
|
33
|
-
neuro_simulator-0.
|
34
|
-
neuro_simulator-0.
|
35
|
-
neuro_simulator-0.
|
36
|
-
neuro_simulator-0.
|
37
|
-
neuro_simulator-0.
|
33
|
+
neuro_simulator-0.3.0.dist-info/METADATA,sha256=PELlUyyTi3i1u1_EOLIntPwd7-FAXAoy6uiSs2QA2uE,8676
|
34
|
+
neuro_simulator-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
35
|
+
neuro_simulator-0.3.0.dist-info/entry_points.txt,sha256=qVd5ypnRRgU8Cw7rWfZ7o0OXyS9P9hgY-cRoN_mgz9g,51
|
36
|
+
neuro_simulator-0.3.0.dist-info/top_level.txt,sha256=V8awSKpcrFnjJDiJxSfy7jtOrnuE2BgAR9hLmfMDWK8,16
|
37
|
+
neuro_simulator-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|