@wendongfly/myhi 1.3.59 → 1.3.61
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/dist/chat.html +237 -0
- package/dist/index.js +1 -1
- package/dist/index.min.js +117 -104
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -350,6 +350,7 @@
|
|
|
350
350
|
<button class="sk sk-claude" onclick="doSlashCmd('/rename')">命名</button>
|
|
351
351
|
<button class="sk sk-claude" onclick="openGitSheet()" style="color:#3fb950">提交</button>
|
|
352
352
|
<button class="sk sk-claude" onclick="showMemory()">记忆</button>
|
|
353
|
+
<button class="sk sk-claude" onclick="openVaSheet()" style="color:#1f6feb">语音</button>
|
|
353
354
|
<button class="sk sk-claude" onclick="openSkillSheet()" style="color:#9d5cf5">技能</button>
|
|
354
355
|
<span id="sk-builtin-skills"></span>
|
|
355
356
|
<span id="sk-custom-skills"></span>
|
|
@@ -391,6 +392,19 @@
|
|
|
391
392
|
<label class="setting-toggle"><span>显示思考过程</span><input type="checkbox" id="set-thinking" checked></label>
|
|
392
393
|
<label class="setting-toggle"><span>工具调用默认折叠</span><input type="checkbox" id="set-collapse" checked></label>
|
|
393
394
|
</div>
|
|
395
|
+
<div class="setting-group">
|
|
396
|
+
<h4>语音识别 (ASR)</h4>
|
|
397
|
+
<div style="font-size:0.72rem;color:#8b949e;margin-bottom:0.35rem">SenseVoice 或兼容服务的地址,留空表示禁用语音</div>
|
|
398
|
+
<div style="display:flex;gap:0.4rem">
|
|
399
|
+
<input id="set-asr-url" class="slash-inp" placeholder="http://192.168.x.x:8765" autocomplete="off" style="flex:1;font-size:0.82rem">
|
|
400
|
+
<button onclick="saveAsrUrl()" style="background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:0.4rem 0.8rem;font-size:0.78rem;cursor:pointer;white-space:nowrap">保存</button>
|
|
401
|
+
</div>
|
|
402
|
+
<div id="set-asr-status" style="font-size:0.72rem;min-height:1.2em;margin-top:0.25rem;color:#8b949e"></div>
|
|
403
|
+
</div>
|
|
404
|
+
<div class="setting-group">
|
|
405
|
+
<h4>服务管理</h4>
|
|
406
|
+
<button onclick="restartMyhi()" style="width:100%;padding:0.6rem;background:#da3633;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:0.85rem;font-weight:600;margin-top:0.1rem">重启 myhi</button>
|
|
407
|
+
</div>
|
|
394
408
|
</div>
|
|
395
409
|
|
|
396
410
|
<!-- QR弹窗 -->
|
|
@@ -569,6 +583,36 @@
|
|
|
569
583
|
</div>
|
|
570
584
|
</div>
|
|
571
585
|
|
|
586
|
+
<!-- 语音需求助手面板 -->
|
|
587
|
+
<div id="va-sheet" class="action-sheet">
|
|
588
|
+
<div class="action-sheet-backdrop" onclick="closeVaSheet()"></div>
|
|
589
|
+
<div class="action-sheet-box" style="max-height:90vh;display:flex;flex-direction:column">
|
|
590
|
+
<div class="action-sheet-title" style="display:flex;align-items:center;justify-content:space-between">
|
|
591
|
+
<span>语音需求助手</span>
|
|
592
|
+
<button onclick="vaClear()" style="background:none;border:none;color:#8b949e;font-size:0.78rem;cursor:pointer;padding:0.2rem 0.4rem">清除</button>
|
|
593
|
+
</div>
|
|
594
|
+
<div id="va-transcript" style="flex:1;overflow-y:auto;min-height:80px;max-height:28vh;padding:0.6rem 0.8rem;background:#0d1117;border:1px solid #30363d;border-radius:8px;margin:0 0.2rem 0.4rem;font-size:0.85rem;color:#c9d1d9;line-height:1.65;scrollbar-width:thin;white-space:pre-wrap">
|
|
595
|
+
<span style="color:#8b949e">按住大按钮说话,松手自动识别,多轮累积后整理成需求...</span>
|
|
596
|
+
</div>
|
|
597
|
+
<div id="va-result-wrap" style="display:none;padding:0 0.2rem 0.3rem">
|
|
598
|
+
<div style="font-size:0.72rem;color:#8b949e;margin-bottom:0.2rem">整理后的需求</div>
|
|
599
|
+
<textarea id="va-result" class="slash-inp" rows="6" style="resize:vertical;font-size:0.82rem;line-height:1.5;font-family:monospace"></textarea>
|
|
600
|
+
</div>
|
|
601
|
+
<button id="va-record-btn"
|
|
602
|
+
style="background:#1f6feb;color:#fff;font-size:1.1rem;font-weight:700;padding:1.1rem;margin:0 0.2rem 0.3rem;border:none;border-radius:12px;cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none">
|
|
603
|
+
按住说话
|
|
604
|
+
</button>
|
|
605
|
+
<div id="va-status" style="text-align:center;font-size:0.78rem;color:#8b949e;min-height:1.3em;margin-bottom:0.2rem"></div>
|
|
606
|
+
<div style="display:flex;gap:0.4rem;padding:0 0.2rem">
|
|
607
|
+
<button id="va-organize-btn" class="action-sheet-cancel" onclick="vaOrganize()"
|
|
608
|
+
style="flex:1;background:#7c3aed;color:#fff;font-weight:600;margin-bottom:0">整理成需求</button>
|
|
609
|
+
<button id="va-send-btn" class="action-sheet-cancel" onclick="vaSend()"
|
|
610
|
+
style="flex:1;background:#1f6feb;color:#fff;font-weight:600;margin-bottom:0;display:none">发送到对话</button>
|
|
611
|
+
</div>
|
|
612
|
+
<button class="action-sheet-cancel" onclick="closeVaSheet()" style="margin-top:0.4rem">关闭</button>
|
|
613
|
+
</div>
|
|
614
|
+
</div>
|
|
615
|
+
|
|
572
616
|
<div id="status-overlay">连接中...</div>
|
|
573
617
|
|
|
574
618
|
<script type="module">
|
|
@@ -2044,11 +2088,49 @@
|
|
|
2044
2088
|
document.getElementById('set-parse').checked = settings.parseOutput;
|
|
2045
2089
|
document.getElementById('set-thinking').checked = settings.showThinking;
|
|
2046
2090
|
document.getElementById('set-collapse').checked = settings.collapseTools;
|
|
2091
|
+
// 从服务端加载 ASR URL
|
|
2092
|
+
fetch('/api/server-config').then(r => r.json()).then(d => {
|
|
2093
|
+
document.getElementById('set-asr-url').value = d.asrUrl || '';
|
|
2094
|
+
}).catch(() => {});
|
|
2047
2095
|
}
|
|
2048
2096
|
document.getElementById('set-parse').addEventListener('change', (e) => { settings.parseOutput = e.target.checked; saveSettings(); });
|
|
2049
2097
|
document.getElementById('set-thinking').addEventListener('change', (e) => { settings.showThinking = e.target.checked; saveSettings(); });
|
|
2050
2098
|
document.getElementById('set-collapse').addEventListener('change', (e) => { settings.collapseTools = e.target.checked; saveSettings(); });
|
|
2051
2099
|
|
|
2100
|
+
window.saveAsrUrl = async function() {
|
|
2101
|
+
const url = document.getElementById('set-asr-url').value.trim();
|
|
2102
|
+
const statusEl = document.getElementById('set-asr-status');
|
|
2103
|
+
statusEl.style.color = '#8b949e';
|
|
2104
|
+
statusEl.textContent = '保存中...';
|
|
2105
|
+
try {
|
|
2106
|
+
const resp = await fetch('/api/server-config', {
|
|
2107
|
+
method: 'POST',
|
|
2108
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2109
|
+
body: JSON.stringify({ asrUrl: url }),
|
|
2110
|
+
});
|
|
2111
|
+
const data = await resp.json();
|
|
2112
|
+
if (data.ok) {
|
|
2113
|
+
statusEl.style.color = '#3fb950';
|
|
2114
|
+
statusEl.textContent = url ? '已保存,重启后生效(如已设置 ASR_URL 环境变量则以环境变量为准)' : '已清空';
|
|
2115
|
+
} else {
|
|
2116
|
+
statusEl.style.color = '#f85149';
|
|
2117
|
+
statusEl.textContent = '保存失败: ' + (data.error || '未知错误');
|
|
2118
|
+
}
|
|
2119
|
+
} catch (e) {
|
|
2120
|
+
statusEl.style.color = '#f85149';
|
|
2121
|
+
statusEl.textContent = '请求失败: ' + e.message;
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
|
|
2125
|
+
window.restartMyhi = async function() {
|
|
2126
|
+
if (!confirm('确认重启 myhi 服务?\n重启期间连接会短暂中断,页面将自动重连。')) return;
|
|
2127
|
+
try {
|
|
2128
|
+
await fetch('/api/restart', { method: 'POST' });
|
|
2129
|
+
} catch {}
|
|
2130
|
+
addStatusMessage('服务重启中,请稍候...');
|
|
2131
|
+
closeSettings();
|
|
2132
|
+
};
|
|
2133
|
+
|
|
2052
2134
|
// ── 辅助 ──────────────────────────────────────
|
|
2053
2135
|
function updateViewers(count) { document.getElementById('viewer-count').textContent = count > 1 ? `${count} 人在线` : ''; }
|
|
2054
2136
|
window.goBack = function() { window.location.href = '/'; };
|
|
@@ -3060,6 +3142,161 @@
|
|
|
3060
3142
|
|
|
3061
3143
|
window.toggleVoice = _startVoice; // 保留 onclick 兼容
|
|
3062
3144
|
|
|
3145
|
+
// ── 语音需求助手 ──────────────────────────────────────────
|
|
3146
|
+
let _vaLines = [];
|
|
3147
|
+
let _vaMR = null;
|
|
3148
|
+
let _vaActive = false;
|
|
3149
|
+
let _vaStartTime2 = 0;
|
|
3150
|
+
|
|
3151
|
+
const _vaBtn = document.getElementById('va-record-btn');
|
|
3152
|
+
const _vaStatusEl = document.getElementById('va-status');
|
|
3153
|
+
|
|
3154
|
+
function _vaSetStatus(t) { _vaStatusEl.textContent = t || ''; }
|
|
3155
|
+
|
|
3156
|
+
function _vaRender() {
|
|
3157
|
+
const el = document.getElementById('va-transcript');
|
|
3158
|
+
if (_vaLines.length === 0) {
|
|
3159
|
+
el.innerHTML = '<span style="color:#8b949e">按住大按钮说话,松手自动识别,多轮累积后整理成需求...</span>';
|
|
3160
|
+
} else {
|
|
3161
|
+
el.textContent = _vaLines.map((t, i) => `[${i + 1}] ${t}`).join('\n\n');
|
|
3162
|
+
el.scrollTop = el.scrollHeight;
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
async function _vaStart() {
|
|
3167
|
+
if (_vaActive) return;
|
|
3168
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
3169
|
+
_vaSetStatus('浏览器不支持麦克风(需要 HTTPS)'); return;
|
|
3170
|
+
}
|
|
3171
|
+
let stream;
|
|
3172
|
+
try {
|
|
3173
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
3174
|
+
} catch (e) {
|
|
3175
|
+
_vaSetStatus(e.name === 'NotAllowedError' ? '麦克风权限被拒绝,请在地址栏允许' : '麦克风失败: ' + e.message);
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
const chunks = [];
|
|
3179
|
+
_vaMR = new MediaRecorder(stream);
|
|
3180
|
+
_vaMR.ondataavailable = (e) => { if (e.data.size > 0) chunks.push(e.data); };
|
|
3181
|
+
_vaMR.onstop = async () => {
|
|
3182
|
+
stream.getTracks().forEach(t => t.stop());
|
|
3183
|
+
await _vaTranscribe(new Blob(chunks, { type: _vaMR.mimeType || 'audio/webm' }));
|
|
3184
|
+
};
|
|
3185
|
+
_vaMR.start();
|
|
3186
|
+
_vaActive = true;
|
|
3187
|
+
_vaBtn.textContent = '松手停止';
|
|
3188
|
+
_vaBtn.style.background = '#da3633';
|
|
3189
|
+
_vaSetStatus('录音中...');
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
function _vaStop() {
|
|
3193
|
+
if (!_vaActive || !_vaMR) return;
|
|
3194
|
+
_vaActive = false;
|
|
3195
|
+
_vaBtn.textContent = '按住说话';
|
|
3196
|
+
_vaBtn.style.background = '#1f6feb';
|
|
3197
|
+
_vaSetStatus('识别中...');
|
|
3198
|
+
_vaMR.stop();
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
async function _vaTranscribe(blob) {
|
|
3202
|
+
try {
|
|
3203
|
+
const form = new FormData();
|
|
3204
|
+
form.append('audio', blob, 'audio.webm');
|
|
3205
|
+
const resp = await fetch(`/api/voice-transcribe?sessionId=${SESSION_ID}`, { method: 'POST', body: form });
|
|
3206
|
+
const data = await resp.json();
|
|
3207
|
+
if (resp.status === 503) {
|
|
3208
|
+
_vaSetStatus('ASR 服务未配置');
|
|
3209
|
+
} else if (data.text) {
|
|
3210
|
+
_vaLines.push(data.text);
|
|
3211
|
+
_vaRender();
|
|
3212
|
+
_vaSetStatus(`第 ${_vaLines.length} 段已录入,可继续录或点"整理成需求"`);
|
|
3213
|
+
} else {
|
|
3214
|
+
_vaSetStatus('未识别到内容,请重试');
|
|
3215
|
+
}
|
|
3216
|
+
} catch (e) {
|
|
3217
|
+
_vaSetStatus('上传失败: ' + e.message);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
window.openVaSheet = function() {
|
|
3222
|
+
document.getElementById('va-sheet').classList.add('active');
|
|
3223
|
+
_vaRender();
|
|
3224
|
+
};
|
|
3225
|
+
window.closeVaSheet = function() {
|
|
3226
|
+
document.getElementById('va-sheet').classList.remove('active');
|
|
3227
|
+
if (_vaActive) _vaStop();
|
|
3228
|
+
};
|
|
3229
|
+
window.vaClear = function() {
|
|
3230
|
+
_vaLines = [];
|
|
3231
|
+
document.getElementById('va-result-wrap').style.display = 'none';
|
|
3232
|
+
document.getElementById('va-send-btn').style.display = 'none';
|
|
3233
|
+
document.getElementById('va-organize-btn').style.display = '';
|
|
3234
|
+
_vaRender();
|
|
3235
|
+
_vaSetStatus('');
|
|
3236
|
+
};
|
|
3237
|
+
window.vaOrganize = async function() {
|
|
3238
|
+
if (_vaLines.length === 0) { _vaSetStatus('请先录入语音内容'); return; }
|
|
3239
|
+
const btn = document.getElementById('va-organize-btn');
|
|
3240
|
+
btn.textContent = '整理中...';
|
|
3241
|
+
btn.disabled = true;
|
|
3242
|
+
try {
|
|
3243
|
+
const resp = await fetch('/api/voice-assist/organize', {
|
|
3244
|
+
method: 'POST',
|
|
3245
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3246
|
+
body: JSON.stringify({ transcript: _vaLines.join('\n'), sessionId: SESSION_ID }),
|
|
3247
|
+
});
|
|
3248
|
+
const data = await resp.json();
|
|
3249
|
+
if (data.requirement) {
|
|
3250
|
+
document.getElementById('va-result').value = data.requirement;
|
|
3251
|
+
document.getElementById('va-result-wrap').style.display = '';
|
|
3252
|
+
document.getElementById('va-send-btn').style.display = '';
|
|
3253
|
+
_vaSetStatus('整理完成,可编辑后点"发送到对话"');
|
|
3254
|
+
} else {
|
|
3255
|
+
_vaSetStatus('整理失败: ' + (data.error || '未知错误'));
|
|
3256
|
+
}
|
|
3257
|
+
} catch (e) {
|
|
3258
|
+
_vaSetStatus('请求失败: ' + e.message);
|
|
3259
|
+
} finally {
|
|
3260
|
+
btn.textContent = '整理成需求';
|
|
3261
|
+
btn.disabled = false;
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
window.vaSend = function() {
|
|
3265
|
+
const text = document.getElementById('va-result').value.trim();
|
|
3266
|
+
if (!text) return;
|
|
3267
|
+
cmdInput.value = text;
|
|
3268
|
+
window.closeVaSheet();
|
|
3269
|
+
if (!_isTouchDevice()) cmdInput.focus();
|
|
3270
|
+
};
|
|
3271
|
+
|
|
3272
|
+
// 大录音按钮事件(长按逻辑与主录音按钮一致)
|
|
3273
|
+
_vaBtn.addEventListener('pointerdown', (e) => {
|
|
3274
|
+
e.preventDefault();
|
|
3275
|
+
_vaStartTime2 = Date.now();
|
|
3276
|
+
_vaStart();
|
|
3277
|
+
});
|
|
3278
|
+
_vaBtn.addEventListener('pointerup', (e) => {
|
|
3279
|
+
e.preventDefault();
|
|
3280
|
+
if (!_vaActive) return;
|
|
3281
|
+
const elapsed = Date.now() - _vaStartTime2;
|
|
3282
|
+
if (elapsed < MIN_RECORD_MS) {
|
|
3283
|
+
_vaActive = false;
|
|
3284
|
+
_vaBtn.textContent = '按住说话';
|
|
3285
|
+
_vaBtn.style.background = '#1f6feb';
|
|
3286
|
+
try { _vaMR?.stream?.getTracks().forEach(t => t.stop()); } catch {}
|
|
3287
|
+
try { if (_vaMR?.state !== 'inactive') _vaMR?.stop(); } catch {}
|
|
3288
|
+
_vaMR = null;
|
|
3289
|
+
_vaSetStatus('请按住按钮说话,松手识别');
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
_vaStop();
|
|
3293
|
+
});
|
|
3294
|
+
_vaBtn.addEventListener('pointercancel', () => { if (_vaActive) _vaStop(); });
|
|
3295
|
+
_vaBtn.addEventListener('click', (e) => {
|
|
3296
|
+
if (_isTouchDevice()) return; // 触屏由 pointer 处理
|
|
3297
|
+
if (_vaActive) { _vaStop(); } else { _vaStart(); }
|
|
3298
|
+
});
|
|
3299
|
+
|
|
3063
3300
|
cmdInput.focus();
|
|
3064
3301
|
|
|
3065
3302
|
// ── 版本更新检查 ──────────────────────────────
|