myagent-ai 1.16.2 → 1.16.4
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/package.json +1 -1
- package/skills/chromedev_mcp.py +55 -1
- package/web/api_server.py +10 -1
- package/web/ui/chat/chat.css +16 -4
- package/web/ui/chat/chat.js +13 -12
- package/web/ui/chat/chat_container.html +3 -3
- package/web/ui/chat/chat_main.js +11 -10
package/package.json
CHANGED
package/skills/chromedev_mcp.py
CHANGED
|
@@ -124,6 +124,16 @@ class MCPClient:
|
|
|
124
124
|
if self._slim:
|
|
125
125
|
args.append("--slim")
|
|
126
126
|
|
|
127
|
+
# [v1.16.2] 自动检测可用的浏览器,支持 chromium 等替代方案
|
|
128
|
+
browser_path = self._detect_browser()
|
|
129
|
+
env = {**os.environ, "NO_COLOR": "1"}
|
|
130
|
+
if browser_path:
|
|
131
|
+
# 优先使用 CHROME_PATH 环境变量传递给 chrome-devtools-mcp
|
|
132
|
+
env["CHROME_PATH"] = browser_path
|
|
133
|
+
logger.info(f"使用浏览器: {browser_path}")
|
|
134
|
+
else:
|
|
135
|
+
logger.warning("未找到 Chrome/Chromium 浏览器,尝试使用默认路径")
|
|
136
|
+
|
|
127
137
|
logger.info(f"启动 chrome-devtools-mcp: {' '.join(args)}")
|
|
128
138
|
|
|
129
139
|
try:
|
|
@@ -135,7 +145,7 @@ class MCPClient:
|
|
|
135
145
|
stdin=subprocess.PIPE,
|
|
136
146
|
stdout=subprocess.PIPE,
|
|
137
147
|
stderr=subprocess.DEVNULL,
|
|
138
|
-
env=
|
|
148
|
+
env=env,
|
|
139
149
|
)
|
|
140
150
|
|
|
141
151
|
# 启动读取线程
|
|
@@ -157,6 +167,50 @@ class MCPClient:
|
|
|
157
167
|
self._cleanup()
|
|
158
168
|
return False
|
|
159
169
|
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _detect_browser() -> Optional[str]:
|
|
172
|
+
"""[v1.16.2] 自动检测可用的浏览器(Chrome/Chromium/Brave 等)。
|
|
173
|
+
|
|
174
|
+
检测顺序:
|
|
175
|
+
1. 环境变量 CHROME_PATH / BROWSER_PATH
|
|
176
|
+
2. PATH 中的常见浏览器命令
|
|
177
|
+
3. Linux 常见安装路径
|
|
178
|
+
"""
|
|
179
|
+
# 1. 环境变量
|
|
180
|
+
for env_key in ("CHROME_PATH", "BROWSER_PATH"):
|
|
181
|
+
env_val = os.environ.get(env_key, "").strip()
|
|
182
|
+
if env_val and os.path.isfile(env_val):
|
|
183
|
+
return env_val
|
|
184
|
+
|
|
185
|
+
# 2. PATH 中的浏览器命令
|
|
186
|
+
path_candidates = [
|
|
187
|
+
"google-chrome", "google-chrome-stable", "google-chrome-beta",
|
|
188
|
+
"chromium-browser", "chromium",
|
|
189
|
+
"brave-browser", "microsoft-edge",
|
|
190
|
+
]
|
|
191
|
+
for cmd in path_candidates:
|
|
192
|
+
found = shutil.which(cmd)
|
|
193
|
+
if found:
|
|
194
|
+
return found
|
|
195
|
+
|
|
196
|
+
# 3. Linux 常见安装路径
|
|
197
|
+
linux_paths = [
|
|
198
|
+
"/usr/bin/google-chrome",
|
|
199
|
+
"/usr/bin/google-chrome-stable",
|
|
200
|
+
"/usr/bin/chromium-browser",
|
|
201
|
+
"/usr/bin/chromium",
|
|
202
|
+
"/snap/bin/chromium",
|
|
203
|
+
"/usr/bin/brave-browser",
|
|
204
|
+
"/usr/bin/microsoft-edge",
|
|
205
|
+
"/opt/google/chrome/chrome",
|
|
206
|
+
"/opt/microsoft/msedge/msedge",
|
|
207
|
+
]
|
|
208
|
+
for p in linux_paths:
|
|
209
|
+
if os.path.isfile(p) and os.access(p, os.X_OK):
|
|
210
|
+
return p
|
|
211
|
+
|
|
212
|
+
return None
|
|
213
|
+
|
|
160
214
|
def _read_stdout(self):
|
|
161
215
|
"""持续读取 MCP Server 的 stdout"""
|
|
162
216
|
if not self._process or not self._process.stdout:
|
package/web/api_server.py
CHANGED
|
@@ -1093,10 +1093,19 @@ class ApiServer:
|
|
|
1093
1093
|
|
|
1094
1094
|
# ── 尝试 faster-whisper ──
|
|
1095
1095
|
try:
|
|
1096
|
+
# [v1.16.3] 抑制 HuggingFace 未认证警告和 pydub ffmpeg 警告
|
|
1097
|
+
import warnings
|
|
1098
|
+
warnings.filterwarnings("ignore", message=".*HF_TOKEN.*", category=UserWarning)
|
|
1099
|
+
warnings.filterwarnings("ignore", message=".*huggingface_hub.*token.*", category=UserWarning)
|
|
1100
|
+
warnings.filterwarnings("ignore", message=".*ffmpeg or avconv.*", category=RuntimeWarning)
|
|
1101
|
+
# 抑制 HF Hub telemetry 和进度条
|
|
1102
|
+
os.environ.setdefault("HF_HUB_DISABLE_TELEMETRY", "1")
|
|
1103
|
+
os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1")
|
|
1104
|
+
os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
|
|
1105
|
+
|
|
1096
1106
|
from faster_whisper import WhisperModel
|
|
1097
1107
|
whisper_model = getattr(self, '_whisper_model', None)
|
|
1098
1108
|
if whisper_model is None:
|
|
1099
|
-
import os
|
|
1100
1109
|
model_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'models', 'whisper')
|
|
1101
1110
|
# 使用 tiny 模型(最轻量,~39MB),CPU int8 量化
|
|
1102
1111
|
self._whisper_model = WhisperModel("tiny", device="cpu", compute_type="int8",
|
package/web/ui/chat/chat.css
CHANGED
|
@@ -47,7 +47,7 @@ button{font:inherit;cursor:pointer;border:none;background:none;color:inherit}
|
|
|
47
47
|
input,textarea,select{font:inherit}
|
|
48
48
|
|
|
49
49
|
/* ── Layout ── */
|
|
50
|
-
.app{position:fixed;top:0;left:0;width:100vw;height:100vh;display:flex;overflow:hidden;background:var(--bg)}
|
|
50
|
+
.app{position:fixed;top:0;left:0;width:100vw;height:100vh;height:100dvh;display:flex;overflow:hidden;background:var(--bg)}
|
|
51
51
|
|
|
52
52
|
/* Layout panels */
|
|
53
53
|
.sidebar,
|
|
@@ -720,9 +720,21 @@ input,textarea,select{font:inherit}
|
|
|
720
720
|
line-height: 1.6;
|
|
721
721
|
white-space: pre-wrap;
|
|
722
722
|
word-break: break-word;
|
|
723
|
-
min-height:
|
|
723
|
+
min-height: 48px;
|
|
724
724
|
max-height: 120px;
|
|
725
|
-
|
|
725
|
+
width: 100%;
|
|
726
|
+
border: 1px solid var(--border);
|
|
727
|
+
border-radius: var(--radius-sm);
|
|
728
|
+
padding: 8px 10px;
|
|
729
|
+
background: var(--bg);
|
|
730
|
+
resize: none;
|
|
731
|
+
outline: none;
|
|
732
|
+
font-family: inherit;
|
|
733
|
+
box-sizing: border-box;
|
|
734
|
+
transition: border-color 0.15s;
|
|
735
|
+
}
|
|
736
|
+
.voice-preview-text:focus {
|
|
737
|
+
border-color: var(--accent);
|
|
726
738
|
}
|
|
727
739
|
.voice-preview-actions {
|
|
728
740
|
display: flex;
|
|
@@ -2145,7 +2157,7 @@ body.popout-mode #popoutBtn{display:none !important}
|
|
|
2145
2157
|
/* ── Input Area ── */
|
|
2146
2158
|
.input-area{
|
|
2147
2159
|
padding:10px 12px;
|
|
2148
|
-
padding-bottom: max(
|
|
2160
|
+
padding-bottom: max(12px, env(safe-area-inset-bottom));
|
|
2149
2161
|
}
|
|
2150
2162
|
.input-wrapper{
|
|
2151
2163
|
max-width:100%;
|
package/web/ui/chat/chat.js
CHANGED
|
@@ -25,25 +25,26 @@
|
|
|
25
25
|
})();
|
|
26
26
|
|
|
27
27
|
// ── Mobile keyboard / visualViewport handling ──
|
|
28
|
+
// [v1.16.2] CSS 已使用 100dvh 动态视口高度,浏览器会自动处理键盘弹出时的视口收缩。
|
|
29
|
+
// 此处仅作为兼容性补充:对不支持 100dvh 的旧浏览器,使用 visualViewport API 调整。
|
|
28
30
|
function onAllScriptsLoaded() {
|
|
29
31
|
if (!window.visualViewport) return;
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
// 检测是否支持 dvh(现代浏览器都支持)
|
|
34
|
+
var testEl = document.createElement('div');
|
|
35
|
+
var supportsDvh = CSS.supports && CSS.supports('height', '100dvh');
|
|
36
|
+
|
|
37
|
+
// 如果支持 dvh,CSS 已经处理了,不需要 JS 干预
|
|
38
|
+
if (supportsDvh) return;
|
|
39
|
+
|
|
40
|
+
var app = document.querySelector('.app');
|
|
41
|
+
if (!app) return;
|
|
33
42
|
|
|
34
43
|
var vv = window.visualViewport;
|
|
35
44
|
|
|
36
|
-
// When the virtual keyboard appears (viewport shrinks), the input area
|
|
37
|
-
// needs extra bottom padding so the send button stays above the keyboard.
|
|
38
45
|
function onViewportResize() {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Keyboard is open — add padding equal to keyboard height
|
|
42
|
-
inputArea.style.marginBottom = diff + 'px';
|
|
43
|
-
} else {
|
|
44
|
-
// Keyboard is closed
|
|
45
|
-
inputArea.style.marginBottom = '0px';
|
|
46
|
-
}
|
|
46
|
+
// 对不支持 dvh 的浏览器,用 visualViewport.height 手动设置高度
|
|
47
|
+
app.style.height = vv.height + 'px';
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
vv.addEventListener('resize', onViewportResize);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
6
|
<title>MyAgent - AI 助手</title>
|
|
7
|
-
<link rel="stylesheet" href="chat.css?v=
|
|
7
|
+
<link rel="stylesheet" href="chat.css?v=8">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div class="app">
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
</div>
|
|
178
178
|
<div class="voice-preview" id="voicePreview" style="display:none">
|
|
179
179
|
<div class="voice-preview-label">语音输入 · <span id="voicePreviewHint">识别中...</span></div>
|
|
180
|
-
<
|
|
180
|
+
<textarea class="voice-preview-text" id="voicePreviewText" placeholder="识别结果..." rows="2"></textarea>
|
|
181
181
|
<div class="voice-preview-actions">
|
|
182
182
|
<button class="voice-preview-cancel" onclick="cancelVoicePreview()">取消</button>
|
|
183
183
|
<button class="voice-preview-send" id="voicePreviewSend" onclick="sendVoiceMessage()">发送</button>
|
|
@@ -275,6 +275,6 @@
|
|
|
275
275
|
<div id="groupModalContainer"></div>
|
|
276
276
|
|
|
277
277
|
<!-- Load JS scripts (no HTML fragments to fetch) -->
|
|
278
|
-
<script src="chat.js?v=
|
|
278
|
+
<script src="chat.js?v=9"></script>
|
|
279
279
|
</body>
|
|
280
280
|
</html>
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -4590,7 +4590,7 @@ var VoiceInput = {
|
|
|
4590
4590
|
// 显示预览区域
|
|
4591
4591
|
if (voiceArea) voiceArea.style.display = 'none';
|
|
4592
4592
|
if (voicePreview) voicePreview.style.display = 'block';
|
|
4593
|
-
if (previewText) previewText.
|
|
4593
|
+
if (previewText) previewText.value = '识别中...';
|
|
4594
4594
|
if (previewHint) previewHint.textContent = '正在发送到本地STT引擎';
|
|
4595
4595
|
if (previewSend) previewSend.disabled = true;
|
|
4596
4596
|
|
|
@@ -4614,11 +4614,11 @@ var VoiceInput = {
|
|
|
4614
4614
|
if (data && data.text && data.text.trim()) {
|
|
4615
4615
|
this.rawText = data.text.trim();
|
|
4616
4616
|
this._sttEngine = data.engine || 'unknown';
|
|
4617
|
-
if (previewText) previewText.
|
|
4618
|
-
if (previewHint) previewHint.textContent = '已识别 (' + (this._sttEngine || 'local') + ')';
|
|
4617
|
+
if (previewText) previewText.value = this.rawText;
|
|
4618
|
+
if (previewHint) previewHint.textContent = '已识别 (' + (this._sttEngine || 'local') + '),可编辑后发送';
|
|
4619
4619
|
} else if (data && data.error) {
|
|
4620
4620
|
if (previewHint) previewHint.textContent = '识别失败';
|
|
4621
|
-
if (previewText) previewText.
|
|
4621
|
+
if (previewText) previewText.value = data.error;
|
|
4622
4622
|
console.warn('Voice STT error:', data.error);
|
|
4623
4623
|
// 如果没有STT引擎,给出提示
|
|
4624
4624
|
if (data.available === false) {
|
|
@@ -4629,12 +4629,12 @@ var VoiceInput = {
|
|
|
4629
4629
|
}
|
|
4630
4630
|
} else {
|
|
4631
4631
|
if (previewHint) previewHint.textContent = '未识别到文字';
|
|
4632
|
-
if (previewText) previewText.
|
|
4632
|
+
if (previewText) previewText.value = '未识别到文字,请重试';
|
|
4633
4633
|
}
|
|
4634
4634
|
} catch (e) {
|
|
4635
4635
|
console.error('Voice STT request error:', e);
|
|
4636
4636
|
if (previewHint) previewHint.textContent = '网络错误';
|
|
4637
|
-
if (previewText) previewText.
|
|
4637
|
+
if (previewText) previewText.value = 'STT请求失败,请重试';
|
|
4638
4638
|
}
|
|
4639
4639
|
|
|
4640
4640
|
this._cleanupStream();
|
|
@@ -4669,11 +4669,12 @@ var VoiceInput = {
|
|
|
4669
4669
|
this.audioChunks = [];
|
|
4670
4670
|
},
|
|
4671
4671
|
|
|
4672
|
-
/** Send the voice text as a message
|
|
4672
|
+
/** Send the voice text as a message(支持用户编辑识别结果后再发送) */
|
|
4673
4673
|
sendMessage: function() {
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
var text =
|
|
4674
|
+
// 从 textarea 读取编辑后的文本
|
|
4675
|
+
var previewText = document.getElementById('voicePreviewText');
|
|
4676
|
+
var text = previewText ? previewText.value.trim() : '';
|
|
4677
|
+
if (!text) return;
|
|
4677
4678
|
|
|
4678
4679
|
// 切回文本模式
|
|
4679
4680
|
this.switchMode('text');
|