Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
MindStudio WebSocket Streaming Client — Flask API Endpoint
|
| 3 |
-
FIXED:
|
|
|
|
| 4 |
FIXED: WebSocket compatibility
|
| 5 |
"""
|
| 6 |
|
|
@@ -72,7 +73,7 @@ class MindStudioClient:
|
|
| 72 |
self.access_token = access_token
|
| 73 |
self.model = model
|
| 74 |
|
| 75 |
-
#
|
| 76 |
self._continuation_tokens: dict = {}
|
| 77 |
self._tokens_lock = threading.Lock()
|
| 78 |
|
|
@@ -115,16 +116,35 @@ class MindStudioClient:
|
|
| 115 |
return check_ws_open(self.ws)
|
| 116 |
|
| 117 |
def get_continuation_token(self, thread_id: str) -> str:
|
| 118 |
-
"""✅ Get the continuation token for a specific thread."""
|
| 119 |
with self._tokens_lock:
|
| 120 |
return self._continuation_tokens.get(thread_id, INITIAL_CONTINUATION_TOKEN)
|
| 121 |
|
| 122 |
def set_continuation_token(self, thread_id: str, token: str):
|
| 123 |
-
"""✅ Save the continuation token for a specific thread."""
|
| 124 |
with self._tokens_lock:
|
| 125 |
self._continuation_tokens[thread_id] = token
|
| 126 |
print(f"[TOKEN] Thread {thread_id[:8]}... token updated")
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
def send_message_stream(self, message: str, thread_id: str):
|
| 129 |
import queue
|
| 130 |
|
|
@@ -133,12 +153,18 @@ class MindStudioClient:
|
|
| 133 |
|
| 134 |
async def _run():
|
| 135 |
try:
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
await self._post_message(message, thread_id)
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
except Exception as e:
|
| 143 |
print(f"[ERROR] send_message_stream: {e}")
|
| 144 |
chunk_queue.put(f"[ERROR] {e}")
|
|
@@ -158,7 +184,6 @@ class MindStudioClient:
|
|
| 158 |
async def _post_message(self, message: str, thread_id: str):
|
| 159 |
post_url = f"https://v1.mindstudio-api.com/v1/apps/load/{APP_ID}/threads/post"
|
| 160 |
|
| 161 |
-
# ✅ FIX: Use per-thread continuation token
|
| 162 |
continuation_token = self.get_continuation_token(thread_id)
|
| 163 |
print(f"[POST] Using token: {continuation_token[:30]}... for thread: {thread_id[:8]}...")
|
| 164 |
|
|
@@ -187,7 +212,6 @@ class MindStudioClient:
|
|
| 187 |
print(f"[POST] Body: {resp.text}")
|
| 188 |
|
| 189 |
async def _listen_for_response(self, chunk_queue, done_event, thread_id: str):
|
| 190 |
-
"""✅ FIX: Accept thread_id to update its specific continuation token."""
|
| 191 |
full_response = ""
|
| 192 |
previous_content_length = 0
|
| 193 |
system_post_id = None
|
|
@@ -216,7 +240,6 @@ class MindStudioClient:
|
|
| 216 |
data = event.get("appThreadContinuationActionUpdated", {})
|
| 217 |
new_token = data.get("continuationAction", {}).get("token")
|
| 218 |
if new_token:
|
| 219 |
-
# ✅ FIX: Save token per thread, not globally
|
| 220 |
self.set_continuation_token(thread_id, new_token)
|
| 221 |
|
| 222 |
underlying = (
|
|
|
|
| 1 |
"""
|
| 2 |
MindStudio WebSocket Streaming Client — Flask API Endpoint
|
| 3 |
+
FIXED: Race condition — AI was answering previous message (buffer drain fix)
|
| 4 |
+
FIXED: Per-thread continuation tokens
|
| 5 |
FIXED: WebSocket compatibility
|
| 6 |
"""
|
| 7 |
|
|
|
|
| 73 |
self.access_token = access_token
|
| 74 |
self.model = model
|
| 75 |
|
| 76 |
+
# Per-thread continuation tokens
|
| 77 |
self._continuation_tokens: dict = {}
|
| 78 |
self._tokens_lock = threading.Lock()
|
| 79 |
|
|
|
|
| 116 |
return check_ws_open(self.ws)
|
| 117 |
|
| 118 |
def get_continuation_token(self, thread_id: str) -> str:
|
|
|
|
| 119 |
with self._tokens_lock:
|
| 120 |
return self._continuation_tokens.get(thread_id, INITIAL_CONTINUATION_TOKEN)
|
| 121 |
|
| 122 |
def set_continuation_token(self, thread_id: str, token: str):
|
|
|
|
| 123 |
with self._tokens_lock:
|
| 124 |
self._continuation_tokens[thread_id] = token
|
| 125 |
print(f"[TOKEN] Thread {thread_id[:8]}... token updated")
|
| 126 |
|
| 127 |
+
# ─────────────────────────────────────────
|
| 128 |
+
# ✅ FIX: Drain leftover WS buffer
|
| 129 |
+
# ─────────────────────────────────────────
|
| 130 |
+
async def _drain_ws_buffer(self):
|
| 131 |
+
"""Flush any leftover WebSocket messages from a previous response."""
|
| 132 |
+
flushed = 0
|
| 133 |
+
try:
|
| 134 |
+
while True:
|
| 135 |
+
raw = await asyncio.wait_for(self.ws.recv(), timeout=0.05)
|
| 136 |
+
flushed += 1
|
| 137 |
+
print(f"[DRAIN] Flushed leftover msg #{flushed}: {str(raw)[:80]}...")
|
| 138 |
+
except asyncio.TimeoutError:
|
| 139 |
+
pass
|
| 140 |
+
except Exception:
|
| 141 |
+
pass
|
| 142 |
+
if flushed:
|
| 143 |
+
print(f"[DRAIN] Total flushed: {flushed} leftover message(s)")
|
| 144 |
+
|
| 145 |
+
# ─────────────────────────────────────────
|
| 146 |
+
# ✅ FIX: Post FIRST, then listen
|
| 147 |
+
# ─────────────────────────────────────────
|
| 148 |
def send_message_stream(self, message: str, thread_id: str):
|
| 149 |
import queue
|
| 150 |
|
|
|
|
| 153 |
|
| 154 |
async def _run():
|
| 155 |
try:
|
| 156 |
+
# ✅ STEP 1: Drain any leftover buffer from previous message
|
| 157 |
+
await self._drain_ws_buffer()
|
| 158 |
+
|
| 159 |
+
# ✅ STEP 2: Post the new message
|
| 160 |
await self._post_message(message, thread_id)
|
| 161 |
+
|
| 162 |
+
# ✅ STEP 3: Small wait for server to process
|
| 163 |
+
await asyncio.sleep(0.15)
|
| 164 |
+
|
| 165 |
+
# ✅ STEP 4: Listen for THIS message's response only
|
| 166 |
+
await self._listen_for_response(chunk_queue, done_event, thread_id)
|
| 167 |
+
|
| 168 |
except Exception as e:
|
| 169 |
print(f"[ERROR] send_message_stream: {e}")
|
| 170 |
chunk_queue.put(f"[ERROR] {e}")
|
|
|
|
| 184 |
async def _post_message(self, message: str, thread_id: str):
|
| 185 |
post_url = f"https://v1.mindstudio-api.com/v1/apps/load/{APP_ID}/threads/post"
|
| 186 |
|
|
|
|
| 187 |
continuation_token = self.get_continuation_token(thread_id)
|
| 188 |
print(f"[POST] Using token: {continuation_token[:30]}... for thread: {thread_id[:8]}...")
|
| 189 |
|
|
|
|
| 212 |
print(f"[POST] Body: {resp.text}")
|
| 213 |
|
| 214 |
async def _listen_for_response(self, chunk_queue, done_event, thread_id: str):
|
|
|
|
| 215 |
full_response = ""
|
| 216 |
previous_content_length = 0
|
| 217 |
system_post_id = None
|
|
|
|
| 240 |
data = event.get("appThreadContinuationActionUpdated", {})
|
| 241 |
new_token = data.get("continuationAction", {}).get("token")
|
| 242 |
if new_token:
|
|
|
|
| 243 |
self.set_continuation_token(thread_id, new_token)
|
| 244 |
|
| 245 |
underlying = (
|