myagent-ai 1.23.18 → 1.23.19

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.
@@ -361,6 +361,17 @@ class ToolDispatcher:
361
361
  embed_url = media_url
362
362
  embed_title = embed_title or ("在线音乐" if media_type == "audio" else "在线视频")
363
363
 
364
+ # [v1.23.19] 构建媒体元数据,持久化到 sent_files 以支持历史消息恢复
365
+ _media_meta = {
366
+ "_type": "media", # 标记为媒体类型(区别于文件)
367
+ "media_type": media_type,
368
+ "embed_url": embed_url or "",
369
+ "title": embed_title,
370
+ "original_url": media_url or fallback_link or "",
371
+ }
372
+ if sent_files is not None:
373
+ sent_files.append(_media_meta)
374
+
364
375
  if embed_url and stream_callback:
365
376
  await self._emit_sse("v2_media", {
366
377
  "media_type": media_type,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.23.18",
3
+ "version": "1.23.19",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/web/api_server.py CHANGED
@@ -4252,8 +4252,15 @@ window.addEventListener('beforeunload', function() {{
4252
4252
  meta = e.metadata or {}
4253
4253
  if meta.get("images"):
4254
4254
  msg["images"] = meta["images"]
4255
- if meta.get("files"):
4256
- msg["files"] = meta["files"]
4255
+ # [v1.23.19] 分离 _files 和 _media(前端用 msg._files 和 msg._media)
4256
+ _raw_files = meta.get("files") or []
4257
+ if _raw_files:
4258
+ _file_items = [f for f in _raw_files if f.get("_type") != "media"]
4259
+ _media_items = [f for f in _raw_files if f.get("_type") == "media"]
4260
+ if _file_items:
4261
+ msg["_files"] = _file_items
4262
+ if _media_items:
4263
+ msg["_media"] = _media_items
4257
4264
  result.append(msg)
4258
4265
  return web.json_response(result)
4259
4266
 
@@ -4306,8 +4313,15 @@ window.addEventListener('beforeunload', function() {{
4306
4313
  meta = e.metadata or {}
4307
4314
  if meta.get("images"):
4308
4315
  msg["images"] = meta["images"]
4309
- if meta.get("files"):
4310
- msg["files"] = meta["files"]
4316
+ # [v1.23.19] 分离 _files 和 _media(前端用 msg._files 和 msg._media)
4317
+ _raw_files = meta.get("files") or []
4318
+ if _raw_files:
4319
+ _file_items = [f for f in _raw_files if f.get("_type") != "media"]
4320
+ _media_items = [f for f in _raw_files if f.get("_type") == "media"]
4321
+ if _file_items:
4322
+ msg["_files"] = _file_items
4323
+ if _media_items:
4324
+ msg["_media"] = _media_items
4311
4325
  result.append(msg)
4312
4326
  return web.json_response(result)
4313
4327
 
@@ -2246,10 +2246,15 @@ async function selectSession(id) {
2246
2246
  if (m.images && m.images.length > 0) {
2247
2247
  mapped.images = m.images;
2248
2248
  }
2249
- if (m.files && m.files.length > 0) {
2250
- mapped.files = m.files;
2251
- // [v1.23.13] 同时映射到 _files,使历史消息中的文件卡片可渲染
2252
- mapped._files = m.files;
2249
+ // [v1.23.19] 兼容旧格式(m.files)和新格式(m._files / m._media)
2250
+ var _rawFiles = m._files || m.files || [];
2251
+ if (_rawFiles.length > 0) {
2252
+ mapped.files = _rawFiles;
2253
+ mapped._files = _rawFiles;
2254
+ }
2255
+ // [v1.23.19] 在线媒体嵌入
2256
+ if (m._media && m._media.length > 0) {
2257
+ mapped._media = m._media;
2253
2258
  }
2254
2259
  return mapped;
2255
2260
  });
@@ -2625,6 +2630,8 @@ function groupHistoryMessages(messages) {
2625
2630
  var firstMsg = msg;
2626
2631
  // [v1.19.3] 收集组内所有消息的文件(agent 通过 file_send 发送的文件存在 metadata.files 中)
2627
2632
  var allAgentFiles = [];
2633
+ // [v1.23.19] 收集组内所有消息的在线媒体嵌入
2634
+ var allAgentMedia = [];
2628
2635
 
2629
2636
  while (i < messages.length && messages[i].role !== 'user') {
2630
2637
  var m = messages[i];
@@ -2643,6 +2650,12 @@ function groupHistoryMessages(messages) {
2643
2650
  allAgentFiles.push(m._files[_fi]);
2644
2651
  }
2645
2652
  }
2653
+ // [v1.23.19] 收集后端持久化的 _media 数据
2654
+ if (m._media && Array.isArray(m._media) && m._media.length > 0) {
2655
+ for (var _mi = 0; _mi < m._media.length; _mi++) {
2656
+ allAgentMedia.push(m._media[_mi]);
2657
+ }
2658
+ }
2646
2659
 
2647
2660
  if (mkey === 'reasoning') {
2648
2661
  reasoningText = reasoningText ? (reasoningText + '\n\n' + (m.content || '')) : (m.content || '');
@@ -2677,6 +2690,10 @@ function groupHistoryMessages(messages) {
2677
2690
  if (allAgentFiles.length > 0) entry._files = allAgentFiles;
2678
2691
  // [v1.20.3] 从 playaudio/playvideo 工具调用中重建 _media 数据(历史回放支持)
2679
2692
  var mediaEmbeds = [];
2693
+ // [v1.23.19] 先收集后端持久化的 _media 数据
2694
+ for (var _pmi = 0; _pmi < allAgentMedia.length; _pmi++) {
2695
+ mediaEmbeds.push(allAgentMedia[_pmi]);
2696
+ }
2680
2697
  for (var ei = 0; ei < parts.length; ei++) {
2681
2698
  var ep = parts[ei];
2682
2699
  if (ep.type === 'exec' && ep.data.tool_name && (ep.data.tool_name === 'playaudio' || ep.data.tool_name === 'playvideo')) {
@@ -628,7 +628,31 @@ function updateStreamingMessage(msgIdx) {
628
628
  var _isImg = _f.type && _f.type.indexOf('image/') === 0;
629
629
  var _isAud = _f.type && _f.type.indexOf('audio/') === 0;
630
630
  var _isVid = _f.type && _f.type.indexOf('video/') === 0;
631
- if (_isImg || _isAud || _isVid) { renderedIds.push(_fId); continue; }
631
+ // [v1.23.19] 图片/音频/视频也直接渲染(不再跳过等 renderMessages)
632
+ if (_isImg && _fId) {
633
+ var _imgDiv = document.createElement('div');
634
+ _imgDiv.className = 'msg-image-wrapper agent-image';
635
+ _imgDiv.innerHTML = '<img src="/api/file/' + _fId + '" class="msg-image" loading="lazy" alt="' + escapeHtml(_f.name || 'image') + '" onerror="this.onerror=null;this.style.background=\'var(--bg3)\';this.style.minHeight=\'60px\';this.alt=\'[图片加载失败]\'" onclick="openFileViewer(\'' + _fId + '\', this.src, \'' + escapeHtml(_f.name) + '\')" />';
636
+ fileContainer.appendChild(_imgDiv);
637
+ renderedIds.push(_fId);
638
+ continue;
639
+ }
640
+ if (_isAud && _fId) {
641
+ var _audDiv = document.createElement('div');
642
+ _audDiv.className = 'msg-media-player';
643
+ _audDiv.innerHTML = '<audio controls src="/api/file/' + _fId + '" style="width:100%;max-width:480px" preload="metadata" onplay="if(typeof muteTTS===\'function\')muteTTS()" onended="if(typeof unmuteTTS===\'function\')unmuteTTS()"></audio><div class="msg-media-title">' + escapeHtml(_f.name || '音频') + '</div>';
644
+ fileContainer.appendChild(_audDiv);
645
+ renderedIds.push(_fId);
646
+ continue;
647
+ }
648
+ if (_isVid && _fId) {
649
+ var _vidDiv = document.createElement('div');
650
+ _vidDiv.className = 'msg-media-player';
651
+ _vidDiv.innerHTML = '<video controls src="/api/file/' + _fId + '" style="width:100%;max-width:640px;border-radius:8px" preload="metadata" onplay="if(typeof muteTTS===\'function\')muteTTS()" onended="if(typeof unmuteTTS===\'function\')unmuteTTS()"></video><div class="msg-media-title">' + escapeHtml(_f.name || '视频') + '</div>';
652
+ fileContainer.appendChild(_vidDiv);
653
+ renderedIds.push(_fId);
654
+ continue;
655
+ }
632
656
  var _icon = _getFileIcon(_f.name || _f.type || '');
633
657
  var _sizeStr = _f.size ? formatFileSize(_f.size) : '';
634
658
  var _fDiv = document.createElement('div');
@@ -647,6 +671,48 @@ function updateStreamingMessage(msgIdx) {
647
671
  fileContainer._renderedFileIds = renderedIds;
648
672
  }
649
673
 
674
+ // ── [v1.23.19] 增量渲染在线媒体嵌入播放器(v2_media 事件) ──
675
+ if (msg._media && msg._media.length > 0) {
676
+ var existingMedia = bubbleWrapper ? bubbleWrapper.querySelectorAll(':scope > .msg-attachments-media') : contentArea.querySelectorAll(':scope > .msg-attachments-media');
677
+ var mediaContainer = existingMedia.length > 0 ? existingMedia[0] : null;
678
+ if (!mediaContainer) {
679
+ mediaContainer = document.createElement('div');
680
+ mediaContainer.className = 'msg-attachments msg-attachments-media';
681
+ if (bubbleWrapper) {
682
+ bubbleWrapper.appendChild(mediaContainer);
683
+ } else {
684
+ contentArea.appendChild(mediaContainer);
685
+ }
686
+ }
687
+ var renderedMediaUrls = mediaContainer._renderedMediaUrls || [];
688
+ for (var _mIdx = 0; _mIdx < msg._media.length; _mIdx++) {
689
+ var _m = msg._media[_mIdx];
690
+ var _mUrl = _m.embed_url || _m.original_url || '';
691
+ if (!_mUrl || renderedMediaUrls.indexOf(_mUrl) >= 0) continue;
692
+ var _isAud2 = _m.media_type === 'audio';
693
+ var _mTitle = _m.title || (_isAud2 ? '在线音乐' : '在线视频');
694
+ var _mOrig = _m.original_url || _mUrl;
695
+ var _mDiv = document.createElement('div');
696
+ _mDiv.className = 'msg-media-embed' + (_isAud2 ? ' msg-media-audio' : ' msg-media-video');
697
+ if (!_m.embed_url && _mOrig) {
698
+ // 无嵌入 URL,渲染链接卡片
699
+ _mDiv.className += ' msg-media-link-card';
700
+ _mDiv.style.cssText = 'padding:10px 14px;border-radius:8px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);cursor:pointer';
701
+ _mDiv.setAttribute('onclick', "window.open('" + _mOrig.replace(/'/g, "\\'") + "','_blank')");
702
+ _mDiv.innerHTML = '<div class="msg-media-header"><span class="msg-media-icon">' + (_isAud2 ? '🎵' : '🎬') + '</span><span class="msg-media-label">' + escapeHtml(_mTitle) + '</span><span style="margin-left:auto;font-size:12px;opacity:0.6">点击在新窗口打开 ↗</span></div><div style="font-size:12px;opacity:0.5;margin-top:4px;word-break:break-all">' + escapeHtml(_mOrig) + '</div>';
703
+ } else if (_isAud2) {
704
+ // 音频嵌入播放器
705
+ _mDiv.innerHTML = '<div class="msg-media-header"><span class="msg-media-icon">🎵</span><span class="msg-media-label">' + escapeHtml(_mTitle) + '</span><a class="msg-media-link" href="' + escapeHtml(_mOrig) + '" target="_blank" rel="noopener" title="在新窗口打开">↗</a></div><iframe src="' + escapeHtml(_mUrl) + '" style="width:100%;max-width:480px;height:80px;border:none;border-radius:8px" loading="lazy" allow="autoplay" sandbox="allow-scripts allow-same-origin allow-presentation"></iframe>';
706
+ } else {
707
+ // 视频嵌入播放器
708
+ _mDiv.innerHTML = '<div class="msg-media-header"><span class="msg-media-icon">🎬</span><span class="msg-media-label">' + escapeHtml(_mTitle) + '</span><a class="msg-media-link" href="' + escapeHtml(_mOrig) + '" target="_blank" rel="noopener" title="在新窗口打开">↗</a></div><iframe src="' + escapeHtml(_mUrl) + '" style="width:100%;max-width:640px;height:360px;border:none;border-radius:8px" loading="lazy" allow="autoplay;encrypted-media;picture-in-picture" allowfullscreen></iframe>';
709
+ }
710
+ mediaContainer.appendChild(_mDiv);
711
+ renderedMediaUrls.push(_mUrl);
712
+ }
713
+ mediaContainer._renderedMediaUrls = renderedMediaUrls;
714
+ }
715
+
650
716
  // Remove exec events panel if present (events are now inline in timeline)
651
717
  const execPanel = contentArea.querySelector('.exec-events-panel');
652
718
  if (execPanel) execPanel.remove();