myagent-ai 1.19.9 → 1.20.1
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/agents/main_agent.py +2 -2
- package/core/deps_checker.py +1 -2
- package/package.json +1 -1
- package/requirements-optional.txt +1 -1
- package/setup.py +1 -3
- package/web/api_server.py +28 -138
- package/web/ui/chat/chat_main.js +40 -4
- package/web/ui/chat/flow_engine.js +9 -0
- package/web/ui/index.html +32 -5
package/agents/main_agent.py
CHANGED
|
@@ -60,8 +60,8 @@ class MainAgent(BaseAgent):
|
|
|
60
60
|
<next_step>当 finish=false 时必填,描述下一步计划做什么(简洁明了,1-2句话)。finish=true 时为空。</next_step>
|
|
61
61
|
</output>
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
1. toolstocal标签:
|
|
63
|
+
注意事项:
|
|
64
|
+
1. toolstocal标签: 尽量一次性列出所有需执行工具调用的,多个"tool""工具调用只要按顺序重复堆叠tool标签即可,解析器会按顺序执行工具调用,最终全部执行完后,会连同所有结果,回调大语言模型。如果某个工具执行超时了,也会回调回调大模型,让大模型分析为什么超时,改用其他工具。如非必要,不要一次仅调用一个工具。
|
|
65
65
|
2. 上下文中的记忆系统说明
|
|
66
66
|
- <automemory>: 系统自动根据你通过 <remember> 保存的记忆和当前用户输入,搜索出的 top10 相关记忆。这些是你过去主动记住的内容(包含时间信息),可供参考。
|
|
67
67
|
- <recall_memory>: 你在上一轮通过 <recall> 指定的记忆搜索结果。系统根据你提供的关键字和时间点搜索了 top5 相关记忆。
|
package/core/deps_checker.py
CHANGED
|
@@ -107,8 +107,7 @@ DEPENDENCIES: List[DepInfo] = [
|
|
|
107
107
|
note="PyTorch 音频处理库 (SenseVoice 必需)"),
|
|
108
108
|
DepInfo("funasr", "funasr", "1.1.0", "stt", "all",
|
|
109
109
|
note="[v1.18.8] SenseVoice 中文语音识别(首选,阿里达摩院)"),
|
|
110
|
-
|
|
111
|
-
note="Whisper 本地语音识别引擎 (备选,需 C++ 编译)"),
|
|
110
|
+
|
|
112
111
|
DepInfo("speech_recognition", "SpeechRecognition", "3.10.0", "stt", "all",
|
|
113
112
|
note="在线语音识别 (Google API,纯 Python 无需编译,Termux 兼容)"),
|
|
114
113
|
|
package/package.json
CHANGED
package/setup.py
CHANGED
|
@@ -41,7 +41,6 @@ setup(
|
|
|
41
41
|
"funasr>=1.1.0",
|
|
42
42
|
"torch>=2.0.0",
|
|
43
43
|
"torchaudio>=2.0.0",
|
|
44
|
-
"faster-whisper>=1.0.0",
|
|
45
44
|
# 浏览器自动化 (ChromeDev MCP, 无需 Playwright)
|
|
46
45
|
# 桌面 GUI 自动化 (内置技能)
|
|
47
46
|
"pynput>=1.7.6",
|
|
@@ -53,7 +52,7 @@ setup(
|
|
|
53
52
|
"discord": ["discord.py>=2.3.0"],
|
|
54
53
|
"anthropic": ["anthropic>=0.18.0"],
|
|
55
54
|
"communication": ["cryptography>=41.0.0", "websockets>=12.0"],
|
|
56
|
-
"voice": ["funasr>=1.1.0", "torch>=2.0.0", "torchaudio>=2.0.0"
|
|
55
|
+
"voice": ["funasr>=1.1.0", "torch>=2.0.0", "torchaudio>=2.0.0"],
|
|
57
56
|
"all": [
|
|
58
57
|
"python-telegram-bot>=21.0",
|
|
59
58
|
"discord.py>=2.3.0",
|
|
@@ -63,7 +62,6 @@ setup(
|
|
|
63
62
|
"funasr>=1.1.0",
|
|
64
63
|
"torch>=2.0.0",
|
|
65
64
|
"torchaudio>=2.0.0",
|
|
66
|
-
"faster-whisper>=1.0.0",
|
|
67
65
|
],
|
|
68
66
|
},
|
|
69
67
|
entry_points={
|
package/web/api_server.py
CHANGED
|
@@ -1549,11 +1549,10 @@ window.toggleFullscreen = function() {{
|
|
|
1549
1549
|
|
|
1550
1550
|
接受音频文件(WAV/WEBM/OGG),使用本地 STT 引擎转录。
|
|
1551
1551
|
支持的引擎(按优先级):
|
|
1552
|
-
1.
|
|
1553
|
-
2.
|
|
1554
|
-
3.
|
|
1555
|
-
4.
|
|
1556
|
-
5. SpeechRecognition(Google,需外网)
|
|
1552
|
+
1. SenseVoice(推荐,中文识别最佳,需:pip install funasr torch torchaudio)
|
|
1553
|
+
2. vosk(备选,需安装:pip install vosk)
|
|
1554
|
+
3. LLM API Whisper 兼容端点
|
|
1555
|
+
4. SpeechRecognition(Google,需外网)
|
|
1557
1556
|
"""
|
|
1558
1557
|
try:
|
|
1559
1558
|
reader = await request.multipart()
|
|
@@ -1639,117 +1638,6 @@ window.toggleFullscreen = function() {{
|
|
|
1639
1638
|
except Exception as e:
|
|
1640
1639
|
logger.warning(f"SenseVoice 转录失败: {e}")
|
|
1641
1640
|
|
|
1642
|
-
# ── 尝试 faster-whisper ──
|
|
1643
|
-
try:
|
|
1644
|
-
whisper_model = self._whisper_model
|
|
1645
|
-
if whisper_model is None:
|
|
1646
|
-
# 预加载未完成或未安装,尝试懒加载
|
|
1647
|
-
import warnings as _w
|
|
1648
|
-
_w.filterwarnings("ignore", message=".*HF_TOKEN.*", category=UserWarning)
|
|
1649
|
-
_w.filterwarnings("ignore", message=".*huggingface_hub.*token.*", category=UserWarning)
|
|
1650
|
-
_w.filterwarnings("ignore", message=".*ffmpeg or avconv.*", category=RuntimeWarning)
|
|
1651
|
-
os.environ.setdefault("HF_HUB_DISABLE_TELEMETRY", "1")
|
|
1652
|
-
os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1")
|
|
1653
|
-
os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
|
|
1654
|
-
from faster_whisper import WhisperModel
|
|
1655
|
-
model_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'models', 'whisper')
|
|
1656
|
-
self._whisper_model = WhisperModel("tiny", device="cpu", compute_type="int8",
|
|
1657
|
-
download_root=model_dir)
|
|
1658
|
-
whisper_model = self._whisper_model
|
|
1659
|
-
logger.info("faster-whisper tiny 模型已加载 (CPU int8)")
|
|
1660
|
-
|
|
1661
|
-
# faster-whisper 需要 16kHz WAV
|
|
1662
|
-
# [v1.15.8] 使用 pydub+ffmpeg 正确转换 WebM/Opus/OGG 等格式
|
|
1663
|
-
wav_buf = io.BytesIO()
|
|
1664
|
-
try:
|
|
1665
|
-
from pydub import AudioSegment
|
|
1666
|
-
audio_buf = io.BytesIO(audio_data)
|
|
1667
|
-
seg = AudioSegment.from_file(audio_buf, format=audio_format or "webm")
|
|
1668
|
-
seg = seg.set_channels(1).set_frame_rate(16000).set_sample_width(2)
|
|
1669
|
-
seg.export(wav_buf, format="wav")
|
|
1670
|
-
wav_buf.seek(0)
|
|
1671
|
-
except Exception as _pydub_err:
|
|
1672
|
-
# pydub 不可用时 fallback:仅处理已是 WAV 的情况
|
|
1673
|
-
import wave
|
|
1674
|
-
audio_buf = io.BytesIO(audio_data)
|
|
1675
|
-
try:
|
|
1676
|
-
with wave.open(audio_buf, 'rb') as rf:
|
|
1677
|
-
wav_buf = io.BytesIO()
|
|
1678
|
-
with wave.open(wav_buf, 'wb') as wf:
|
|
1679
|
-
wf.setnchannels(1)
|
|
1680
|
-
wf.setsampwidth(2)
|
|
1681
|
-
wf.setframerate(16000)
|
|
1682
|
-
frames = rf.readframes(rf.getnframes())
|
|
1683
|
-
wf.writeframes(frames)
|
|
1684
|
-
wav_buf.seek(0)
|
|
1685
|
-
except Exception:
|
|
1686
|
-
logger.warning(f"音频格式转换失败(pydub: {_pydub_err})")
|
|
1687
|
-
return web.json_response({"error": "音频格式不支持,需要 WAV 或安装 pydub+ffmpeg"}, status=400)
|
|
1688
|
-
|
|
1689
|
-
wav_buf.seek(0)
|
|
1690
|
-
segments, info = whisper_model.transcribe(wav_buf, beam_size=1,
|
|
1691
|
-
language="zh",
|
|
1692
|
-
initial_prompt="以下是普通话的句子",
|
|
1693
|
-
vad_filter=True, vad_parameters=dict(
|
|
1694
|
-
min_silence_duration_ms=300))
|
|
1695
|
-
text = "".join(seg.text for seg in segments).strip()
|
|
1696
|
-
|
|
1697
|
-
if text:
|
|
1698
|
-
return web.json_response({"text": text, "engine": "faster-whisper"})
|
|
1699
|
-
except ImportError:
|
|
1700
|
-
logger.debug("faster-whisper 未安装,尝试自动安装...")
|
|
1701
|
-
try:
|
|
1702
|
-
from core.deps_checker import ensure_skill_deps
|
|
1703
|
-
installed = ensure_skill_deps("stt")
|
|
1704
|
-
if installed:
|
|
1705
|
-
logger.info("faster-whisper 自动安装成功,重新尝试转录")
|
|
1706
|
-
from faster_whisper import WhisperModel
|
|
1707
|
-
import os
|
|
1708
|
-
model_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'models', 'whisper')
|
|
1709
|
-
self._whisper_model = WhisperModel("tiny", device="cpu", compute_type="int8",
|
|
1710
|
-
download_root=model_dir)
|
|
1711
|
-
whisper_model = self._whisper_model
|
|
1712
|
-
# 重新执行转录(跳过上面的 try 已完成的逻辑,直接进入转录流程)
|
|
1713
|
-
import io
|
|
1714
|
-
# [v1.15.8] 使用 pydub+ffmpeg 正确转换音频格式
|
|
1715
|
-
wav_buf = io.BytesIO()
|
|
1716
|
-
try:
|
|
1717
|
-
from pydub import AudioSegment
|
|
1718
|
-
audio_buf = io.BytesIO(audio_data)
|
|
1719
|
-
seg = AudioSegment.from_file(audio_buf, format=audio_format or "webm")
|
|
1720
|
-
seg = seg.set_channels(1).set_frame_rate(16000).set_sample_width(2)
|
|
1721
|
-
seg.export(wav_buf, format="wav")
|
|
1722
|
-
wav_buf.seek(0)
|
|
1723
|
-
except Exception as _pydub_err2:
|
|
1724
|
-
import wave
|
|
1725
|
-
audio_buf = io.BytesIO(audio_data)
|
|
1726
|
-
try:
|
|
1727
|
-
with wave.open(audio_buf, 'rb') as rf:
|
|
1728
|
-
wav_buf = io.BytesIO()
|
|
1729
|
-
with wave.open(wav_buf, 'wb') as wf:
|
|
1730
|
-
wf.setnchannels(1)
|
|
1731
|
-
wf.setsampwidth(2)
|
|
1732
|
-
wf.setframerate(16000)
|
|
1733
|
-
frames = rf.readframes(rf.getnframes())
|
|
1734
|
-
wf.writeframes(frames)
|
|
1735
|
-
wav_buf.seek(0)
|
|
1736
|
-
except Exception:
|
|
1737
|
-
logger.warning(f"音频格式转换失败(pydub: {_pydub_err2})")
|
|
1738
|
-
return web.json_response({"error": "音频格式不支持"}, status=400)
|
|
1739
|
-
wav_buf.seek(0)
|
|
1740
|
-
segments, info = whisper_model.transcribe(wav_buf, beam_size=1,
|
|
1741
|
-
language="zh",
|
|
1742
|
-
initial_prompt="以下是普通话的句子",
|
|
1743
|
-
vad_filter=True, vad_parameters=dict(
|
|
1744
|
-
min_silence_duration_ms=300))
|
|
1745
|
-
text = "".join(seg.text for seg in segments).strip()
|
|
1746
|
-
if text:
|
|
1747
|
-
return web.json_response({"text": text, "engine": "faster-whisper"})
|
|
1748
|
-
except Exception as inst_err:
|
|
1749
|
-
logger.warning(f"faster-whisper 自动安装/转录失败: {inst_err}")
|
|
1750
|
-
except Exception as e:
|
|
1751
|
-
logger.warning(f"faster-whisper 转录失败: {e}")
|
|
1752
|
-
|
|
1753
1641
|
# ── 尝试 vosk ──
|
|
1754
1642
|
try:
|
|
1755
1643
|
import vosk
|
|
@@ -1872,9 +1760,8 @@ window.toggleFullscreen = function() {{
|
|
|
1872
1760
|
"error": "未检测到可用的 STT 引擎。请尝试以下方案:\n"
|
|
1873
1761
|
" 1. pip install funasr torch torchaudio (SenseVoice,中文最佳,推荐)\n"
|
|
1874
1762
|
" 2. 配置支持 Whisper 的 LLM API(自动使用,无需安装)\n"
|
|
1875
|
-
" 3. pip install
|
|
1876
|
-
" 4. pip install
|
|
1877
|
-
" 5. pip install SpeechRecognition (需外网,国内不可用)",
|
|
1763
|
+
" 3. pip install vosk (离线本地,需下载模型)\n"
|
|
1764
|
+
" 4. pip install SpeechRecognition (需外网,国内不可用)",
|
|
1878
1765
|
"available": False,
|
|
1879
1766
|
}, status=503)
|
|
1880
1767
|
|
|
@@ -2134,6 +2021,23 @@ window.toggleFullscreen = function() {{
|
|
|
2134
2021
|
"status": "done",
|
|
2135
2022
|
})
|
|
2136
2023
|
|
|
2024
|
+
# ── 硬上限:任务列表不允许超过 MAX_TASK_ITEMS 条 ──
|
|
2025
|
+
# 系统提示词虽然要求 LLM 精简到 8 条,但 LLM 经常忽略,
|
|
2026
|
+
# 加上 merge 保留 done 项,会导致列表无限增长。
|
|
2027
|
+
# 策略:优先保留未完成项 + 最近完成的项,移除最早的已完成项
|
|
2028
|
+
MAX_TASK_ITEMS = 10
|
|
2029
|
+
if len(merged) > MAX_TASK_ITEMS:
|
|
2030
|
+
# 分离未完成和已完成
|
|
2031
|
+
unfinished = [t for t in merged if t.get("status") != "done"]
|
|
2032
|
+
finished = [t for t in merged if t.get("status") == "done"]
|
|
2033
|
+
# 保留所有未完成项 + 尽可能多的已完成项(保留最新的)
|
|
2034
|
+
remaining = MAX_TASK_ITEMS - len(unfinished)
|
|
2035
|
+
if remaining > 0:
|
|
2036
|
+
finished = finished[-remaining:] # 保留最近完成的
|
|
2037
|
+
else:
|
|
2038
|
+
finished = []
|
|
2039
|
+
merged = unfinished + finished
|
|
2040
|
+
|
|
2137
2041
|
return merged
|
|
2138
2042
|
|
|
2139
2043
|
async def handle_get_task_plan(self, request):
|
|
@@ -6772,8 +6676,8 @@ window.toggleFullscreen = function() {{
|
|
|
6772
6676
|
except Exception:
|
|
6773
6677
|
pass
|
|
6774
6678
|
|
|
6775
|
-
#
|
|
6776
|
-
#
|
|
6679
|
+
# 后台预加载 STT 模型,避免首次语音识别时等待数秒
|
|
6680
|
+
# 仅加载 SenseVoice(中文识别最佳)
|
|
6777
6681
|
try:
|
|
6778
6682
|
import threading
|
|
6779
6683
|
def _preload_stt():
|
|
@@ -6784,30 +6688,16 @@ window.toggleFullscreen = function() {{
|
|
|
6784
6688
|
os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1")
|
|
6785
6689
|
os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
|
|
6786
6690
|
|
|
6787
|
-
# 首选: SenseVoice (funasr)
|
|
6788
6691
|
try:
|
|
6789
6692
|
from funasr import AutoModel
|
|
6790
6693
|
model_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'models', 'sensevoice')
|
|
6791
6694
|
self._sensevoice_model = AutoModel(model="iic/SenseVoiceSmall", model_dir=model_dir,
|
|
6792
6695
|
device="cpu", disable_pbar=True, disable_update=True)
|
|
6793
|
-
logger.info("STT SenseVoice 模型预加载完成
|
|
6794
|
-
return # 成功则不加载 whisper
|
|
6795
|
-
except ImportError:
|
|
6796
|
-
logger.debug("SenseVoice (funasr) 未安装,尝试 faster-whisper")
|
|
6797
|
-
except Exception as e:
|
|
6798
|
-
logger.debug(f"SenseVoice 预加载失败: {e},尝试 faster-whisper")
|
|
6799
|
-
|
|
6800
|
-
# 备选: faster-whisper
|
|
6801
|
-
try:
|
|
6802
|
-
from faster_whisper import WhisperModel
|
|
6803
|
-
model_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'models', 'whisper')
|
|
6804
|
-
self._whisper_model = WhisperModel("tiny", device="cpu", compute_type="int8",
|
|
6805
|
-
download_root=model_dir)
|
|
6806
|
-
logger.info("STT faster-whisper 模型预加载完成 (备选引擎)")
|
|
6696
|
+
logger.info("STT SenseVoice 模型预加载完成")
|
|
6807
6697
|
except ImportError:
|
|
6808
|
-
logger.debug("
|
|
6698
|
+
logger.debug("SenseVoice (funasr) 未安装,跳过 STT 预加载")
|
|
6809
6699
|
except Exception as e:
|
|
6810
|
-
logger.debug(f"
|
|
6700
|
+
logger.debug(f"SenseVoice 预加载失败(不影响使用): {e}")
|
|
6811
6701
|
except Exception as e:
|
|
6812
6702
|
logger.debug(f"STT 预加载异常(不影响使用): {e}")
|
|
6813
6703
|
threading.Thread(target=_preload_stt, daemon=True).start()
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -1998,9 +1998,45 @@ function newChat() {
|
|
|
1998
1998
|
var _sessionPollTimers = {};
|
|
1999
1999
|
function _pollSessionCompletion(sessionId) {
|
|
2000
2000
|
if (_sessionPollTimers[sessionId]) clearInterval(_sessionPollTimers[sessionId]);
|
|
2001
|
+
var _lastMsgCount = state.messages.length;
|
|
2001
2002
|
_sessionPollTimers[sessionId] = setInterval(async function() {
|
|
2002
2003
|
try {
|
|
2003
2004
|
const data = await api('/api/session/status?sid=' + encodeURIComponent(sessionId));
|
|
2005
|
+
// 每次轮询都重新加载消息,实时显示执行过程中产生的中间输出
|
|
2006
|
+
try {
|
|
2007
|
+
const msgs = await api('/api/session/messages?sid=' + encodeURIComponent(sessionId) + '&limit=500');
|
|
2008
|
+
if (Array.isArray(msgs) && msgs.length > 0) {
|
|
2009
|
+
var loaded = msgs.filter(function(m) {
|
|
2010
|
+
return m && (m.role === 'user' || m.role === 'assistant' || m.role === 'tool');
|
|
2011
|
+
}).map(function(m) {
|
|
2012
|
+
var content = (m.content != null) ? String(m.content) : '';
|
|
2013
|
+
var mkey = (m.key || '').toLowerCase();
|
|
2014
|
+
if (m.role === 'assistant' && mkey !== 'tool_call' && mkey !== 'reasoning' && content && content.trim().startsWith('<')) {
|
|
2015
|
+
content = (typeof _stripXmlTags === 'function') ? _stripXmlTags(content) : content;
|
|
2016
|
+
}
|
|
2017
|
+
var mapped = {
|
|
2018
|
+
role: m.role || 'assistant',
|
|
2019
|
+
content: content,
|
|
2020
|
+
time: m.time || m.created_at || '',
|
|
2021
|
+
key: m.key || '',
|
|
2022
|
+
};
|
|
2023
|
+
if (m.images && m.images.length > 0) mapped.images = m.images;
|
|
2024
|
+
if (m.files && m.files.length > 0) mapped.files = m.files;
|
|
2025
|
+
return mapped;
|
|
2026
|
+
});
|
|
2027
|
+
var newMsgs = groupHistoryMessages(loaded);
|
|
2028
|
+
if (newMsgs.length !== _lastMsgCount) {
|
|
2029
|
+
// 只在有新消息时才重新渲染,避免闪烁
|
|
2030
|
+
state.messages = newMsgs;
|
|
2031
|
+
_lastMsgCount = newMsgs.length;
|
|
2032
|
+
renderMessages();
|
|
2033
|
+
// 自动滚动到底部,显示最新输出
|
|
2034
|
+
var mi = document.getElementById('messagesInner');
|
|
2035
|
+
if (mi) mi.scrollTop = mi.scrollHeight;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
} catch (_) {}
|
|
2039
|
+
|
|
2004
2040
|
if (!data.running || data.done) {
|
|
2005
2041
|
clearInterval(_sessionPollTimers[sessionId]);
|
|
2006
2042
|
delete _sessionPollTimers[sessionId];
|
|
@@ -2009,7 +2045,7 @@ function _pollSessionCompletion(sessionId) {
|
|
|
2009
2045
|
document.getElementById('sendBtn').style.display = '';
|
|
2010
2046
|
document.getElementById('sendBtn').disabled = !document.getElementById('userInput').value.trim();
|
|
2011
2047
|
document.getElementById('stopBtn').style.display = 'none';
|
|
2012
|
-
//
|
|
2048
|
+
// 最终重新加载消息以获取完整内容
|
|
2013
2049
|
await selectSession(sessionId);
|
|
2014
2050
|
if (data.error) {
|
|
2015
2051
|
toast('⚠️ 后台任务完成但出错: ' + data.error, 'error');
|
|
@@ -2681,17 +2717,17 @@ function _renderMessagesInner() {
|
|
|
2681
2717
|
</details>`;
|
|
2682
2718
|
})() : '';
|
|
2683
2719
|
const _isSpeakingThis = ttsManager && ttsManager.isPlaying && ttsManager.currentMsgIndex === i;
|
|
2684
|
-
const actionBtns =
|
|
2720
|
+
const actionBtns = msg.content ? `
|
|
2685
2721
|
<div class="msg-actions">
|
|
2686
2722
|
<button class="msg-action-btn" onclick="copyMessage(${i})" title="复制">
|
|
2687
2723
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
2688
|
-
</button
|
|
2724
|
+
</button>${!isUser ? `
|
|
2689
2725
|
<button class="msg-action-btn${_isSpeakingThis ? ' tts-speaking' : ''}" onclick="speakMessage(${i})" title="${_isSpeakingThis ? '停止朗读' : '朗读'}">
|
|
2690
2726
|
${_isSpeakingThis
|
|
2691
2727
|
? '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>'
|
|
2692
2728
|
: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>'
|
|
2693
2729
|
}
|
|
2694
|
-
</button
|
|
2730
|
+
</button>` : ''}
|
|
2695
2731
|
</div>` : '';
|
|
2696
2732
|
const ttsIndicator = _isSpeakingThis ?
|
|
2697
2733
|
' <span class="tts-playing-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg></span>' : '';
|
|
@@ -1620,6 +1620,15 @@ async function sendMessage(opts) {
|
|
|
1620
1620
|
// 任务列表 JSON 直推更新(exec 模式)
|
|
1621
1621
|
// 如果已收到 finish 标签,忽略后续 task_list_update,防止覆盖已清空的任务列表
|
|
1622
1622
|
if (evt.tasks && Array.isArray(evt.tasks) && !_finishReceived) {
|
|
1623
|
+
// 前端安全上限:防止服务端限制失效时列表无限增长
|
|
1624
|
+
if (evt.tasks.length > 15) {
|
|
1625
|
+
var unfinished = evt.tasks.filter(function(t) { return t.status !== 'done'; });
|
|
1626
|
+
var finished = evt.tasks.filter(function(t) { return t.status === 'done'; });
|
|
1627
|
+
var remaining = 15 - unfinished.length;
|
|
1628
|
+
if (remaining > 0) finished = finished.slice(-remaining);
|
|
1629
|
+
else finished = [];
|
|
1630
|
+
evt.tasks = unfinished.concat(finished);
|
|
1631
|
+
}
|
|
1623
1632
|
state.taskItems = evt.tasks;
|
|
1624
1633
|
renderTaskList();
|
|
1625
1634
|
var panel = document.getElementById('taskPanel');
|
package/web/ui/index.html
CHANGED
|
@@ -274,6 +274,25 @@ tr:hover{background:var(--surface2)}
|
|
|
274
274
|
/* 任务表格优化 */
|
|
275
275
|
.table-wrap table{min-width:auto}
|
|
276
276
|
.table-wrap td[style*="max-width:300px"]{max-width:150px!important;white-space:normal!important;word-break:break-all}
|
|
277
|
+
/* ── 会话管理移动端:隐藏"最后活动"列,操作按钮堆叠 ── */
|
|
278
|
+
#content[data-page="sessions"] .table-wrap th:nth-child(4),
|
|
279
|
+
#content[data-page="sessions"] .table-wrap td:nth-child(4){display:none}
|
|
280
|
+
#content[data-page="sessions"] .table-wrap td:last-child{white-space:normal;min-width:auto}
|
|
281
|
+
#content[data-page="sessions"] .table-wrap td:last-child .btn{display:inline-block;margin:2px}
|
|
282
|
+
/* ── 任务记录移动端:隐藏"来源"列,操作按钮堆叠 ── */
|
|
283
|
+
#content[data-page="tasks"] .table-wrap th:nth-child(3),
|
|
284
|
+
#content[data-page="tasks"] .table-wrap td:nth-child(3){display:none}
|
|
285
|
+
#content[data-page="tasks"] .table-wrap td:last-child{white-space:normal;min-width:auto}
|
|
286
|
+
#content[data-page="tasks"] .table-wrap td:last-child .btn{display:inline-block;margin:2px}
|
|
287
|
+
/* ── 会话查看:消息气泡在窄屏撑满宽度 ── */
|
|
288
|
+
.msg-bubble-wrap{max-width:75%}
|
|
289
|
+
#content[data-page="sessions"] .msg-bubble-wrap{max-width:88%!important}
|
|
290
|
+
/* ── 任务计划卡片移动端堆叠 ── */
|
|
291
|
+
.card .flex.justify-between{flex-direction:column;align-items:flex-start;gap:6px}
|
|
292
|
+
/* ── 时间索引自适应换行 ── */
|
|
293
|
+
.raw-time-nav{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;margin-bottom:8px}
|
|
294
|
+
.raw-time-link{color:var(--accent);text-decoration:none;font-size:12px;padding:2px 6px;border-radius:4px;background:var(--surface2);white-space:nowrap;transition:background .15s}
|
|
295
|
+
.raw-time-link:hover{background:var(--accent);color:#fff}
|
|
277
296
|
}
|
|
278
297
|
@media(max-width:480px){
|
|
279
298
|
.content{padding:12px}
|
|
@@ -287,6 +306,12 @@ tr:hover{background:var(--surface2)}
|
|
|
287
306
|
.form-group{margin-bottom:10px}
|
|
288
307
|
.form-group label{font-size:12px}
|
|
289
308
|
.raw-collapsed{max-height:0 !important;padding:0 10px !important;overflow:hidden}
|
|
309
|
+
/* ── 480px:任务表进一步精简,隐藏任务ID列 ── */
|
|
310
|
+
#content[data-page="tasks"] .table-wrap th:nth-child(1),
|
|
311
|
+
#content[data-page="tasks"] .table-wrap td:nth-child(1){display:none}
|
|
312
|
+
/* ── 480px:会话表隐藏 Agent 列 ── */
|
|
313
|
+
#content[data-page="sessions"] .table-wrap th:nth-child(2),
|
|
314
|
+
#content[data-page="sessions"] .table-wrap td:nth-child(2){display:none}
|
|
290
315
|
}
|
|
291
316
|
</style>
|
|
292
317
|
</head>
|
|
@@ -443,6 +468,7 @@ function showConfirm(title,msg,onOk){
|
|
|
443
468
|
function showPage(page, addHistory){
|
|
444
469
|
closeMobileSidebar();
|
|
445
470
|
currentPage=page;
|
|
471
|
+
$('content').setAttribute('data-page',page);
|
|
446
472
|
document.querySelectorAll('.nav-item').forEach((n,i)=>n.classList.toggle('active',Object.keys(pages)[i]===page));
|
|
447
473
|
$('pageTitle').textContent=pages[page]||page;
|
|
448
474
|
const renderers={dashboard:renderDashboard,agents:renderAgents,platforms:renderPlatforms,organization:renderOrganization,departments:renderDepartments,sessions:renderSessions,memory:renderMemory,permissions:renderPermissions,llm:renderLLM,system:renderSystem,executor:renderExecutor,skills:renderSkills,files:renderFiles,logs:renderLogs,tasks:renderTasks};
|
|
@@ -468,6 +494,7 @@ function navigateTo(page, sub, renderFn){
|
|
|
468
494
|
if(_navHistory.length>6)_navHistory.shift();
|
|
469
495
|
currentPage=page;
|
|
470
496
|
_navSubState=sub;
|
|
497
|
+
$('content').setAttribute('data-page',page);
|
|
471
498
|
// 更新 URL hash
|
|
472
499
|
var hash=page+(sub?'~'+sub:'');
|
|
473
500
|
history.pushState({page:page,sub:sub},'','#'+hash);
|
|
@@ -1073,7 +1100,7 @@ async function viewSessionMsgs(sid){
|
|
|
1073
1100
|
const displayContent=content.length>500?content.slice(0,500)+'...':content;
|
|
1074
1101
|
html+=`<div style="margin:8px 0;display:flex;gap:8px;${align}">`;
|
|
1075
1102
|
if(!isUser)html+=`<div style="flex-shrink:0;font-size:16px;margin-top:2px">${avatar}</div>`;
|
|
1076
|
-
html+=`<div style="
|
|
1103
|
+
html+=`<div class="msg-bubble-wrap" style="min-width:0"><div style="font-size:11px;color:var(--text3);margin-bottom:2px">${isUser?'用户':'助手'} <span style="margin-left:4px">${time}</span></div><div style="background:${bg};color:${fg};padding:10px 14px;border-radius:var(--radius);font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-word">${escHtml(displayContent)}</div></div>`;
|
|
1077
1104
|
if(isUser)html+=`<div style="flex-shrink:0;font-size:16px;margin-top:2px">${avatar}</div>`;
|
|
1078
1105
|
html+=`</div>`;
|
|
1079
1106
|
}
|
|
@@ -1311,7 +1338,7 @@ async function _loadSessionMessages(){
|
|
|
1311
1338
|
const displayContent=long?content.slice(0,500)+'... (共'+content.length+'字符)':content;
|
|
1312
1339
|
html+=`<div style="margin:8px 0;display:flex;gap:8px;${isUser?'flex-direction:row-reverse':''}">`;
|
|
1313
1340
|
html+=`<div style="flex-shrink:0;font-size:16px;margin-top:2px">${avatar}</div>`;
|
|
1314
|
-
html+=`<div style="
|
|
1341
|
+
html+=`<div class="msg-bubble-wrap" style="min-width:0"><div style="font-size:11px;color:var(--text3);margin-bottom:2px;${isUser?'text-align:right':''}">${isUser?'用户':'助手'} <span style="margin-left:4px">${time}</span></div>`;
|
|
1315
1342
|
html+=`<div style="background:${bg};color:${fg};padding:10px 14px;border-radius:var(--radius);font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-word">${escHtml(displayContent)}</div>`;
|
|
1316
1343
|
if(long)html+=`<button class="btn btn-sm btn-ghost" style="margin-top:4px;font-size:11px" onclick="this.previousElementSibling.textContent=this.dataset.full;this.remove()" data-full="${escHtml(content).replace(/"/g,'"')}">展开全部 (${content.length}字符)</button>`;
|
|
1317
1344
|
html+=`</div></div>`;
|
|
@@ -1351,7 +1378,7 @@ async function viewSessionRaw(sid){
|
|
|
1351
1378
|
}
|
|
1352
1379
|
html+=`</div>`;
|
|
1353
1380
|
// 时间索引导航
|
|
1354
|
-
html+=`<div
|
|
1381
|
+
html+=`<div class="raw-time-nav" id="rawTimeNav"></div>`;
|
|
1355
1382
|
// 消息列表
|
|
1356
1383
|
html+=`<div style="max-height:65vh;overflow-y:auto;font-family:monospace" id="rawMsgList">`;
|
|
1357
1384
|
for(let i=0;i<msgs.length;i++){
|
|
@@ -1419,9 +1446,9 @@ function _buildTimeNav(msgs){
|
|
|
1419
1446
|
}
|
|
1420
1447
|
const times=Object.keys(minutes);
|
|
1421
1448
|
if(times.length<=1)return;
|
|
1422
|
-
let navHtml='
|
|
1449
|
+
let navHtml='<span style="color:var(--text3);font-size:12px">时间索引:</span> ';
|
|
1423
1450
|
for(const t of times){
|
|
1424
|
-
navHtml+=`<a href="javascript:void(0)"
|
|
1451
|
+
navHtml+=`<a href="javascript:void(0)" class="raw-time-link" onclick="document.querySelectorAll('.raw-item')[${minutes[t][0]}].scrollIntoView({behavior:'smooth',block:'center'})">${escHtml(t.slice(11))}</a>`;
|
|
1425
1452
|
}
|
|
1426
1453
|
nav.innerHTML=navHtml;
|
|
1427
1454
|
}
|