dmpantiu commited on
Commit
b0bd6c6
Β·
verified Β·
1 Parent(s): 7d5ca8f

Upload folder using huggingface_hub

Browse files
web/agent_wrapper.py CHANGED
@@ -134,6 +134,13 @@ class AgentSession:
134
  """Check if the agent is ready."""
135
  return self._initialized and self._agent is not None
136
 
 
 
 
 
 
 
 
137
  def clear_messages(self):
138
  """Clear conversation messages."""
139
  self._messages = []
@@ -157,7 +164,11 @@ class AgentSession:
157
  Process a user message and stream the response.
158
  """
159
  if not self.is_ready():
160
- raise RuntimeError("Agent not initialized")
 
 
 
 
161
 
162
  # Clear any old plots from queue
163
  self.get_pending_plots()
@@ -176,6 +187,9 @@ class AgentSession:
176
 
177
  # Stream status updates while agent is working
178
  await stream_callback("status", "πŸ€– Processing with AI...")
 
 
 
179
 
180
  result = await asyncio.get_event_loop().run_in_executor(
181
  None,
@@ -275,6 +289,8 @@ class AgentSession:
275
  return response_text
276
 
277
  except Exception as e:
 
 
278
  logger.exception(f"Error processing message: {e}")
279
  raise
280
 
 
134
  """Check if the agent is ready."""
135
  return self._initialized and self._agent is not None
136
 
137
+ def reinitialize(self):
138
+ """Retry initialization (e.g., after transient failure)."""
139
+ logger.warning("Attempting agent reinitialization...")
140
+ self._initialized = False
141
+ self._agent = None
142
+ self._initialize()
143
+
144
  def clear_messages(self):
145
  """Clear conversation messages."""
146
  self._messages = []
 
164
  Process a user message and stream the response.
165
  """
166
  if not self.is_ready():
167
+ # Try to reinitialize once before giving up
168
+ logger.warning("Agent not ready, attempting reinitialization...")
169
+ self.reinitialize()
170
+ if not self.is_ready():
171
+ raise RuntimeError("Agent not initialized")
172
 
173
  # Clear any old plots from queue
174
  self.get_pending_plots()
 
187
 
188
  # Stream status updates while agent is working
189
  await stream_callback("status", "πŸ€– Processing with AI...")
190
+
191
+ # Save message state before invoke (protect against corruption)
192
+ messages_backup = list(self._messages)
193
 
194
  result = await asyncio.get_event_loop().run_in_executor(
195
  None,
 
289
  return response_text
290
 
291
  except Exception as e:
292
+ # Restore clean message state to prevent corruption on next call
293
+ self._messages = messages_backup
294
  logger.exception(f"Error processing message: {e}")
295
  raise
296
 
web/routes/websocket.py CHANGED
@@ -95,10 +95,11 @@ async def websocket_chat(websocket: WebSocket):
95
  await manager.send_json(websocket, {"type": "thinking"})
96
 
97
  try:
98
- # Get session for this connection
99
  session = get_session(connection_id)
100
  if not session:
101
- raise RuntimeError("Session not found")
 
102
 
103
  # Callback for streaming
104
  async def stream_callback(event_type: str, content: str, **kwargs):
 
95
  await manager.send_json(websocket, {"type": "thinking"})
96
 
97
  try:
98
+ # Get session for this connection (auto-recreate if lost)
99
  session = get_session(connection_id)
100
  if not session:
101
+ logger.warning(f"Session lost for {connection_id[:8]}, recreating...")
102
+ session = create_session(connection_id)
103
 
104
  # Callback for streaming
105
  async def stream_callback(event_type: str, content: str, **kwargs):
web/static/js/chat.js CHANGED
@@ -106,8 +106,9 @@ class EurusChat {
106
  }
107
 
108
  autoSendSessionKeys() {
109
- // After WS connects, if we have session-stored keys and server has none, auto-send them
110
- if (this.serverKeysPresent.openai || this.keysConfigured) return;
 
111
  const saved = sessionStorage.getItem('eurus-keys');
112
  if (!saved) return;
113
  try {
@@ -192,11 +193,13 @@ class EurusChat {
192
  this.reconnectAttempts = 0;
193
  this.updateConnectionStatus('connected');
194
 
 
 
 
 
 
195
  if (this.serverKeysPresent.openai || this.keysConfigured) {
196
  this.sendBtn.disabled = false;
197
- } else {
198
- // Auto-send keys from sessionStorage on reconnect/refresh
199
- this.autoSendSessionKeys();
200
  }
201
  };
202
 
@@ -700,17 +703,47 @@ class EurusChat {
700
  const actionsDiv = lastFigure ? lastFigure.querySelector('.plot-actions') : null;
701
 
702
  if (actionsDiv) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  // Add button inline with Enlarge/Download/Show Code
704
  const btn = document.createElement('button');
705
- btn.className = 'code-btn';
706
  btn.title = 'Arraylake Code';
707
  btn.textContent = 'πŸ“¦ Arraylake Code';
708
  actionsDiv.appendChild(btn);
709
 
710
  // Add code block to figure (same pattern as Show Code)
711
  const codeDiv = document.createElement('div');
712
- codeDiv.className = 'plot-code';
713
  codeDiv.style.display = 'none';
 
714
 
715
  const pre = document.createElement('pre');
716
  const codeEl = document.createElement('code');
@@ -728,7 +761,8 @@ class EurusChat {
728
  copyBtn.className = 'copy-snippet-btn';
729
  copyBtn.textContent = 'Copy Code';
730
  copyBtn.addEventListener('click', () => {
731
- navigator.clipboard.writeText(cleanCode).then(() => {
 
732
  copyBtn.textContent = 'βœ“ Copied!';
733
  setTimeout(() => copyBtn.textContent = 'Copy Code', 2000);
734
  });
@@ -747,17 +781,35 @@ class EurusChat {
747
  }
748
  });
749
  } else {
750
- // No plot figure β€” add as standalone section under the message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
  const wrapper = document.createElement('div');
752
  wrapper.className = 'arraylake-snippet-section';
753
  wrapper.innerHTML = `
754
  <div class="plot-actions">
755
- <button class="code-btn" title="Arraylake Code">πŸ“¦ Arraylake Code</button>
756
  </div>
757
  <div class="plot-code" style="display: none;">
758
  <pre><code class="language-python hljs"></code></pre>
759
  </div>
760
  `;
 
 
761
  const codeEl = wrapper.querySelector('code');
762
  try {
763
  codeEl.innerHTML = hljs.highlight(cleanCode, { language: 'python' }).value;
 
106
  }
107
 
108
  autoSendSessionKeys() {
109
+ // After WS (re)connects, resend saved keys so the new server-side session gets them.
110
+ // Only skip if the server has pre-configured env keys (not user-provided).
111
+ if (this.serverKeysPresent.openai) return;
112
  const saved = sessionStorage.getItem('eurus-keys');
113
  if (!saved) return;
114
  try {
 
193
  this.reconnectAttempts = 0;
194
  this.updateConnectionStatus('connected');
195
 
196
+ // Always resend keys on (re)connect β€” server session may have been
197
+ // destroyed on disconnect, so keys stored in sessionStorage must be
198
+ // re-sent even if keysConfigured is true from the previous session.
199
+ this.autoSendSessionKeys();
200
+
201
  if (this.serverKeysPresent.openai || this.keysConfigured) {
202
  this.sendBtn.disabled = false;
 
 
 
203
  }
204
  };
205
 
 
703
  const actionsDiv = lastFigure ? lastFigure.querySelector('.plot-actions') : null;
704
 
705
  if (actionsDiv) {
706
+ // Check if an Arraylake button already exists on this figure
707
+ const existingBtn = actionsDiv.querySelector('.arraylake-btn');
708
+ if (existingBtn) {
709
+ // Append new snippet to the existing code block
710
+ const existingCodeDiv = lastFigure.querySelector('.arraylake-code-block');
711
+ if (existingCodeDiv) {
712
+ const codeEl = existingCodeDiv.querySelector('code');
713
+ const prevRaw = existingCodeDiv.getAttribute('data-raw-code') || '';
714
+ const combined = prevRaw + '\n\n# ---\n\n' + cleanCode;
715
+ existingCodeDiv.setAttribute('data-raw-code', combined);
716
+ try {
717
+ codeEl.innerHTML = hljs.highlight(combined, { language: 'python' }).value;
718
+ } catch (e) {
719
+ codeEl.textContent = combined;
720
+ }
721
+ // Update copy button target
722
+ const copyBtn = existingCodeDiv.querySelector('.copy-snippet-btn');
723
+ if (copyBtn) {
724
+ copyBtn.onclick = () => {
725
+ navigator.clipboard.writeText(combined).then(() => {
726
+ copyBtn.textContent = 'βœ“ Copied!';
727
+ setTimeout(() => copyBtn.textContent = 'Copy Code', 2000);
728
+ });
729
+ };
730
+ }
731
+ }
732
+ return;
733
+ }
734
+
735
  // Add button inline with Enlarge/Download/Show Code
736
  const btn = document.createElement('button');
737
+ btn.className = 'code-btn arraylake-btn';
738
  btn.title = 'Arraylake Code';
739
  btn.textContent = 'πŸ“¦ Arraylake Code';
740
  actionsDiv.appendChild(btn);
741
 
742
  // Add code block to figure (same pattern as Show Code)
743
  const codeDiv = document.createElement('div');
744
+ codeDiv.className = 'plot-code arraylake-code-block';
745
  codeDiv.style.display = 'none';
746
+ codeDiv.setAttribute('data-raw-code', cleanCode);
747
 
748
  const pre = document.createElement('pre');
749
  const codeEl = document.createElement('code');
 
761
  copyBtn.className = 'copy-snippet-btn';
762
  copyBtn.textContent = 'Copy Code';
763
  copyBtn.addEventListener('click', () => {
764
+ const rawCode = codeDiv.getAttribute('data-raw-code') || cleanCode;
765
+ navigator.clipboard.writeText(rawCode).then(() => {
766
  copyBtn.textContent = 'βœ“ Copied!';
767
  setTimeout(() => copyBtn.textContent = 'Copy Code', 2000);
768
  });
 
781
  }
782
  });
783
  } else {
784
+ // No plot figure β€” check if standalone section already exists
785
+ const existingWrapper = targetMsg.querySelector('.arraylake-snippet-section');
786
+ if (existingWrapper) {
787
+ // Append to existing standalone snippet
788
+ const codeEl = existingWrapper.querySelector('code');
789
+ const existingCodeDiv = existingWrapper.querySelector('.plot-code');
790
+ const prevRaw = existingCodeDiv.getAttribute('data-raw-code') || '';
791
+ const combined = prevRaw + '\n\n# ---\n\n' + cleanCode;
792
+ existingCodeDiv.setAttribute('data-raw-code', combined);
793
+ try {
794
+ codeEl.innerHTML = hljs.highlight(combined, { language: 'python' }).value;
795
+ } catch (e) {
796
+ codeEl.textContent = combined;
797
+ }
798
+ return;
799
+ }
800
+
801
  const wrapper = document.createElement('div');
802
  wrapper.className = 'arraylake-snippet-section';
803
  wrapper.innerHTML = `
804
  <div class="plot-actions">
805
+ <button class="code-btn arraylake-btn" title="Arraylake Code">πŸ“¦ Arraylake Code</button>
806
  </div>
807
  <div class="plot-code" style="display: none;">
808
  <pre><code class="language-python hljs"></code></pre>
809
  </div>
810
  `;
811
+ const codeDivEl = wrapper.querySelector('.plot-code');
812
+ codeDivEl.setAttribute('data-raw-code', cleanCode);
813
  const codeEl = wrapper.querySelector('code');
814
  try {
815
  codeEl.innerHTML = hljs.highlight(cleanCode, { language: 'python' }).value;