myagent-ai 1.9.1 → 1.9.3

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.
package/core/llm.py CHANGED
@@ -649,28 +649,36 @@ class LLMClient:
649
649
  logger.error(f"流式 LLM 调用失败: {e}")
650
650
 
651
651
  async def _stream_openai(self, kwargs: dict) -> AsyncGenerator[str, None]:
652
- """OpenAI / 兼容接口 (含 Zhipu) 流式调用"""
653
- loop = asyncio.get_running_loop()
654
-
655
- def _create_stream():
656
- return self._client.chat.completions.create(**kwargs)
657
-
658
- stream = await loop.run_in_executor(None, _create_stream)
652
+ """OpenAI / 兼容接口 (含 Zhipu) 流式调用
659
653
 
660
- # 使用迭代器在 executor 中逐步获取
661
- def _next_chunk(it):
662
- try:
663
- return next(it)
664
- except StopIteration:
665
- return None
654
+ 支持两种客户端:
655
+ - AsyncOpenAI: 使用 async for 直接异步迭代
656
+ - 同步 OpenAI: 在 executor 中同步迭代
657
+ """
658
+ # 判断客户端类型,选择合适的流式迭代方式
659
+ is_async = hasattr(self._client, '__aenter__')
660
+
661
+ if is_async:
662
+ # AsyncOpenAI: 直接异步迭代
663
+ stream = await self._client.chat.completions.create(**kwargs)
664
+ async for chunk in stream:
665
+ if chunk.choices and chunk.choices[0].delta.content:
666
+ yield chunk.choices[0].delta.content
667
+ else:
668
+ # 同步 OpenAI: 在 executor 中迭代
669
+ loop = asyncio.get_running_loop()
666
670
 
667
- iterator = iter(stream)
668
- while True:
669
- chunk = await loop.run_in_executor(None, _next_chunk, iterator)
670
- if chunk is None:
671
- break
672
- if chunk.choices and chunk.choices[0].delta.content:
673
- yield chunk.choices[0].delta.content
671
+ def _create_and_iterate():
672
+ stream = self._client.chat.completions.create(**kwargs)
673
+ results = []
674
+ for chunk in stream:
675
+ if chunk.choices and chunk.choices[0].delta.content:
676
+ results.append(chunk.choices[0].delta.content)
677
+ return results
678
+
679
+ chunks = await loop.run_in_executor(None, _create_and_iterate)
680
+ for content in chunks:
681
+ yield content
674
682
 
675
683
  async def _stream_anthropic(
676
684
  self, messages: List[Message], kwargs: dict
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -3,7 +3,7 @@
3
3
 
4
4
  (function() {
5
5
  var base = new URL('.', window.location.href).href;
6
- var v = '?v=5';
6
+ var v = '?v=6';
7
7
  var files = [
8
8
  { url: base + 'left_sessions.html' + v, container: 'leftSessionsContainer' },
9
9
  { url: base + 'middle_chat.html' + v, container: 'middleChatContainer' },
@@ -620,17 +620,17 @@ function toggleExecEventsPanel(header) {
620
620
  function renderInlineExecEvent(data, msgIdx) {
621
621
  // V2 Tool Event handling (called with full part: {type:'v2_tool', data:{...}})
622
622
  if (data.type === 'v2_tool') {
623
- var inner = data.data;
624
- var isStart = inner.status === 'running' || inner.type === 'tool_start';
625
- var iconEmoji = isStart ? '⚡' : (inner.success ? '✅' : '❌');
626
- var title = inner.title || (inner.tool_name || '工具调用');
627
- var metaParts = [];
623
+ let inner = data.data;
624
+ let isStart = inner.status === 'running' || inner.type === 'tool_start';
625
+ let iconEmoji = isStart ? '⚡' : (inner.success ? '✅' : '❌');
626
+ let title = inner.title || (inner.tool_name || '工具调用');
627
+ let metaParts = [];
628
628
  if (inner.tool_name) metaParts.push(escapeHtml(inner.tool_name));
629
629
  if (inner.timeout) metaParts.push('超时 ' + inner.timeout + 's');
630
630
  if (inner.callback !== undefined) metaParts.push(inner.callback ? '回调LLM' : '无回调');
631
- var metaText = metaParts.join(' · ');
631
+ let metaText = metaParts.join(' · ');
632
632
 
633
- var bodyHtml = '';
633
+ let bodyHtml = '';
634
634
  if (inner.type === 'tool_result' && inner.summary) {
635
635
  bodyHtml += '<div class="inline-exec-summary">' + escapeHtml(inner.summary) + '</div>';
636
636
  }
@@ -639,7 +639,7 @@ function renderInlineExecEvent(data, msgIdx) {
639
639
  }
640
640
  // Show params for tool_start
641
641
  if (inner.type === 'tool_start' && inner.params) {
642
- var paramPreview = typeof inner.params === 'string' ? inner.params : JSON.stringify(inner.params);
642
+ let paramPreview = typeof inner.params === 'string' ? inner.params : JSON.stringify(inner.params);
643
643
  if (paramPreview.length > 200) paramPreview = paramPreview.substring(0, 200) + '...';
644
644
  bodyHtml += '<div class="inline-exec-code">' + escapeHtml(paramPreview) + '</div>';
645
645
  }
@@ -827,7 +827,8 @@ async function sendMessage() {
827
827
  // ── 如果正在生成,弹出处理选择框 ──
828
828
  if (state.isGenerating) {
829
829
  state.tempInputText = text;
830
- document.getElementById('injectModal').style.display = 'flex';
830
+ var modal = document.getElementById('injectModal');
831
+ if (modal) { modal.style.display = 'flex'; }
831
832
  return;
832
833
  }
833
834
 
@@ -1230,6 +1231,19 @@ function stopGenerating() {
1230
1231
  if (state.abortController) {
1231
1232
  state.abortController.abort();
1232
1233
  }
1234
+ // 立即重置 UI 状态(防止 abort 后 finally 未执行导致 UI 卡死)
1235
+ state.isGenerating = false;
1236
+ state.abortController = null;
1237
+ hideTypingIndicator();
1238
+ stopExecTimerPolling();
1239
+ var sendBtn = document.getElementById('sendBtn');
1240
+ var stopBtn = document.getElementById('stopBtn');
1241
+ var input = document.getElementById('userInput');
1242
+ if (sendBtn) { sendBtn.style.display = ''; sendBtn.disabled = !(input && input.value.trim()); }
1243
+ if (stopBtn) stopBtn.style.display = 'none';
1244
+ // 关闭可能打开的 injectModal
1245
+ var injectModal = document.getElementById('injectModal');
1246
+ if (injectModal) injectModal.style.display = 'none';
1233
1247
  }
1234
1248
 
1235
1249
  // ══════════════════════════════════════════════════════
@@ -1237,7 +1251,8 @@ function stopGenerating() {
1237
1251
  // ══════════════════════════════════════════════════════
1238
1252
 
1239
1253
  function closeInjectModal() {
1240
- document.getElementById('injectModal').style.display = 'none';
1254
+ var modal = document.getElementById('injectModal');
1255
+ if (modal) modal.style.display = 'none';
1241
1256
  state.tempInputText = '';
1242
1257
  }
1243
1258
 
@@ -1438,7 +1453,8 @@ function handleKeyDown(e) {
1438
1453
  if (state.isGenerating) {
1439
1454
  // 任务执行中,使用统一的 injectModal 弹出选择对话框
1440
1455
  state.tempInputText = text;
1441
- document.getElementById('injectModal').style.display = 'flex';
1456
+ var modal = document.getElementById('injectModal');
1457
+ if (modal) { modal.style.display = 'flex'; }
1442
1458
  } else {
1443
1459
  sendMessage();
1444
1460
  }
@@ -142,4 +142,17 @@
142
142
  <div class="input-hint">MyAgent 可能会出错,请核实重要信息</div>
143
143
  </div>
144
144
  </div>
145
+
146
+ <!-- Inject Modal (生成中消息注入选择) -->
147
+ <div id="injectModal" class="modal-overlay" style="display:none">
148
+ <div class="modal">
149
+ <h3>任务执行中</h3>
150
+ <p>Agent 正在执行任务,请选择如何处理你的消息:</p>
151
+ <div style="display:flex;flex-direction:column;gap:8px;margin-bottom:0">
152
+ <button class="modal-btn" style="width:100%;justify-content:center;background:var(--accent);color:#fff" onclick="handleInjectChoice('queue')">排队等待</button>
153
+ <button class="modal-btn" style="width:100%;justify-content:center" onclick="handleInjectChoice('inject')">注入当前任务</button>
154
+ <button class="modal-btn" style="width:100%;justify-content:center;color:var(--text2)" onclick="closeInjectModal()">取消</button>
155
+ </div>
156
+ </div>
157
+ </div>
145
158
  </div>