@wendongfly/myhi 1.3.58 → 1.3.60
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 +207 -3
- package/dist/index.js +1 -1
- package/dist/index.min.js +117 -104
- package/package.json +1 -1
package/dist/chat.html
CHANGED
|
@@ -137,8 +137,9 @@
|
|
|
137
137
|
#img-preview .img-remove:hover { background: #f85149; color: #fff; }
|
|
138
138
|
#input-box.focused { border-color: #58a6ff; }
|
|
139
139
|
#input-box.disabled { opacity: 0.5; pointer-events: none; }
|
|
140
|
-
#cmd-input { display: block; width: 100%; background: transparent; color: #e6edf3; border: none; padding: 0.6rem 0.75rem 0.3rem; font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; font-size: 0.85rem; outline: none; resize: none; line-height: 1.
|
|
140
|
+
#cmd-input { display: block; width: 100%; background: transparent; color: #e6edf3; border: none; padding: 0.6rem 0.75rem 0.3rem; font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; font-size: 0.85rem; outline: none; resize: none; line-height: 1.5; max-height: 140px; overflow-y: auto; }
|
|
141
141
|
#cmd-input::placeholder { color: #484f58; }
|
|
142
|
+
@media (pointer: coarse) { #cmd-input { font-size: 1rem; padding: 0.75rem 0.75rem 0.35rem; line-height: 1.55; } }
|
|
142
143
|
#input-toolbar { display: flex; align-items: center; padding: 0.25rem 0.4rem 0.4rem; gap: 0.15rem; }
|
|
143
144
|
#input-toolbar .spacer { flex: 1; }
|
|
144
145
|
.tb-btn { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 8px; border: none; background: transparent; color: #8b949e; cursor: pointer; transition: all 0.15s; flex-shrink: 0; }
|
|
@@ -349,6 +350,7 @@
|
|
|
349
350
|
<button class="sk sk-claude" onclick="doSlashCmd('/rename')">命名</button>
|
|
350
351
|
<button class="sk sk-claude" onclick="openGitSheet()" style="color:#3fb950">提交</button>
|
|
351
352
|
<button class="sk sk-claude" onclick="showMemory()">记忆</button>
|
|
353
|
+
<button class="sk sk-claude" onclick="openVaSheet()" style="color:#1f6feb">语音</button>
|
|
352
354
|
<button class="sk sk-claude" onclick="openSkillSheet()" style="color:#9d5cf5">技能</button>
|
|
353
355
|
<span id="sk-builtin-skills"></span>
|
|
354
356
|
<span id="sk-custom-skills"></span>
|
|
@@ -568,6 +570,36 @@
|
|
|
568
570
|
</div>
|
|
569
571
|
</div>
|
|
570
572
|
|
|
573
|
+
<!-- 语音需求助手面板 -->
|
|
574
|
+
<div id="va-sheet" class="action-sheet">
|
|
575
|
+
<div class="action-sheet-backdrop" onclick="closeVaSheet()"></div>
|
|
576
|
+
<div class="action-sheet-box" style="max-height:90vh;display:flex;flex-direction:column">
|
|
577
|
+
<div class="action-sheet-title" style="display:flex;align-items:center;justify-content:space-between">
|
|
578
|
+
<span>语音需求助手</span>
|
|
579
|
+
<button onclick="vaClear()" style="background:none;border:none;color:#8b949e;font-size:0.78rem;cursor:pointer;padding:0.2rem 0.4rem">清除</button>
|
|
580
|
+
</div>
|
|
581
|
+
<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">
|
|
582
|
+
<span style="color:#8b949e">按住大按钮说话,松手自动识别,多轮累积后整理成需求...</span>
|
|
583
|
+
</div>
|
|
584
|
+
<div id="va-result-wrap" style="display:none;padding:0 0.2rem 0.3rem">
|
|
585
|
+
<div style="font-size:0.72rem;color:#8b949e;margin-bottom:0.2rem">整理后的需求</div>
|
|
586
|
+
<textarea id="va-result" class="slash-inp" rows="6" style="resize:vertical;font-size:0.82rem;line-height:1.5;font-family:monospace"></textarea>
|
|
587
|
+
</div>
|
|
588
|
+
<button id="va-record-btn"
|
|
589
|
+
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">
|
|
590
|
+
按住说话
|
|
591
|
+
</button>
|
|
592
|
+
<div id="va-status" style="text-align:center;font-size:0.78rem;color:#8b949e;min-height:1.3em;margin-bottom:0.2rem"></div>
|
|
593
|
+
<div style="display:flex;gap:0.4rem;padding:0 0.2rem">
|
|
594
|
+
<button id="va-organize-btn" class="action-sheet-cancel" onclick="vaOrganize()"
|
|
595
|
+
style="flex:1;background:#7c3aed;color:#fff;font-weight:600;margin-bottom:0">整理成需求</button>
|
|
596
|
+
<button id="va-send-btn" class="action-sheet-cancel" onclick="vaSend()"
|
|
597
|
+
style="flex:1;background:#1f6feb;color:#fff;font-weight:600;margin-bottom:0;display:none">发送到对话</button>
|
|
598
|
+
</div>
|
|
599
|
+
<button class="action-sheet-cancel" onclick="closeVaSheet()" style="margin-top:0.4rem">关闭</button>
|
|
600
|
+
</div>
|
|
601
|
+
</div>
|
|
602
|
+
|
|
571
603
|
<div id="status-overlay">连接中...</div>
|
|
572
604
|
|
|
573
605
|
<script type="module">
|
|
@@ -3019,11 +3051,14 @@
|
|
|
3019
3051
|
addStatusMessage('语音上传失败: ' + e.message);
|
|
3020
3052
|
} finally {
|
|
3021
3053
|
_voicePlaceholder();
|
|
3022
|
-
cmdInput.focus();
|
|
3054
|
+
if (!_isTouchDevice()) cmdInput.focus(); // 触屏不抢焦点,避免弹键盘
|
|
3023
3055
|
}
|
|
3024
3056
|
}
|
|
3025
3057
|
|
|
3026
|
-
//
|
|
3058
|
+
// 桌面端:点击切换;触屏:长按录音松手发送,短按提示
|
|
3059
|
+
const MIN_RECORD_MS = 400;
|
|
3060
|
+
let _voiceStartTime = 0;
|
|
3061
|
+
|
|
3027
3062
|
_voiceBtn.addEventListener('click', (e) => {
|
|
3028
3063
|
if (_isTouchDevice()) return; // 触屏由 pointer 事件处理
|
|
3029
3064
|
_startVoice();
|
|
@@ -3031,17 +3066,186 @@
|
|
|
3031
3066
|
_voiceBtn.addEventListener('pointerdown', (e) => {
|
|
3032
3067
|
if (!_isTouchDevice()) return;
|
|
3033
3068
|
e.preventDefault();
|
|
3069
|
+
cmdInput.blur(); // 防止键盘弹出
|
|
3070
|
+
_voiceStartTime = Date.now();
|
|
3034
3071
|
_startVoice();
|
|
3035
3072
|
});
|
|
3036
3073
|
_voiceBtn.addEventListener('pointerup', (e) => {
|
|
3037
3074
|
if (!_isTouchDevice() || !_voiceActive) return;
|
|
3038
3075
|
e.preventDefault();
|
|
3076
|
+
const elapsed = Date.now() - _voiceStartTime;
|
|
3077
|
+
if (elapsed < MIN_RECORD_MS) {
|
|
3078
|
+
// 短按:取消录音,不上传,给提示
|
|
3079
|
+
_voiceActive = false;
|
|
3080
|
+
_voiceBtn.classList.remove('recording');
|
|
3081
|
+
_voicePlaceholder();
|
|
3082
|
+
try { _mediaRecorder?.stream?.getTracks().forEach(t => t.stop()); } catch {}
|
|
3083
|
+
try { if (_mediaRecorder?.state !== 'inactive') _mediaRecorder?.stop(); } catch {}
|
|
3084
|
+
_mediaRecorder = null;
|
|
3085
|
+
addStatusMessage('请长按 🎤 录音(按住说话,松手识别)');
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3039
3088
|
_stopVoice();
|
|
3040
3089
|
});
|
|
3041
3090
|
_voiceBtn.addEventListener('pointercancel', () => { if (_voiceActive) _stopVoice(); });
|
|
3042
3091
|
|
|
3043
3092
|
window.toggleVoice = _startVoice; // 保留 onclick 兼容
|
|
3044
3093
|
|
|
3094
|
+
// ── 语音需求助手 ──────────────────────────────────────────
|
|
3095
|
+
let _vaLines = [];
|
|
3096
|
+
let _vaMR = null;
|
|
3097
|
+
let _vaActive = false;
|
|
3098
|
+
let _vaStartTime2 = 0;
|
|
3099
|
+
|
|
3100
|
+
const _vaBtn = document.getElementById('va-record-btn');
|
|
3101
|
+
const _vaStatusEl = document.getElementById('va-status');
|
|
3102
|
+
|
|
3103
|
+
function _vaSetStatus(t) { _vaStatusEl.textContent = t || ''; }
|
|
3104
|
+
|
|
3105
|
+
function _vaRender() {
|
|
3106
|
+
const el = document.getElementById('va-transcript');
|
|
3107
|
+
if (_vaLines.length === 0) {
|
|
3108
|
+
el.innerHTML = '<span style="color:#8b949e">按住大按钮说话,松手自动识别,多轮累积后整理成需求...</span>';
|
|
3109
|
+
} else {
|
|
3110
|
+
el.textContent = _vaLines.map((t, i) => `[${i + 1}] ${t}`).join('\n\n');
|
|
3111
|
+
el.scrollTop = el.scrollHeight;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
async function _vaStart() {
|
|
3116
|
+
if (_vaActive) return;
|
|
3117
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
3118
|
+
_vaSetStatus('浏览器不支持麦克风(需要 HTTPS)'); return;
|
|
3119
|
+
}
|
|
3120
|
+
let stream;
|
|
3121
|
+
try {
|
|
3122
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
3123
|
+
} catch (e) {
|
|
3124
|
+
_vaSetStatus(e.name === 'NotAllowedError' ? '麦克风权限被拒绝,请在地址栏允许' : '麦克风失败: ' + e.message);
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
const chunks = [];
|
|
3128
|
+
_vaMR = new MediaRecorder(stream);
|
|
3129
|
+
_vaMR.ondataavailable = (e) => { if (e.data.size > 0) chunks.push(e.data); };
|
|
3130
|
+
_vaMR.onstop = async () => {
|
|
3131
|
+
stream.getTracks().forEach(t => t.stop());
|
|
3132
|
+
await _vaTranscribe(new Blob(chunks, { type: _vaMR.mimeType || 'audio/webm' }));
|
|
3133
|
+
};
|
|
3134
|
+
_vaMR.start();
|
|
3135
|
+
_vaActive = true;
|
|
3136
|
+
_vaBtn.textContent = '松手停止';
|
|
3137
|
+
_vaBtn.style.background = '#da3633';
|
|
3138
|
+
_vaSetStatus('录音中...');
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
function _vaStop() {
|
|
3142
|
+
if (!_vaActive || !_vaMR) return;
|
|
3143
|
+
_vaActive = false;
|
|
3144
|
+
_vaBtn.textContent = '按住说话';
|
|
3145
|
+
_vaBtn.style.background = '#1f6feb';
|
|
3146
|
+
_vaSetStatus('识别中...');
|
|
3147
|
+
_vaMR.stop();
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
async function _vaTranscribe(blob) {
|
|
3151
|
+
try {
|
|
3152
|
+
const form = new FormData();
|
|
3153
|
+
form.append('audio', blob, 'audio.webm');
|
|
3154
|
+
const resp = await fetch(`/api/voice-transcribe?sessionId=${SESSION_ID}`, { method: 'POST', body: form });
|
|
3155
|
+
const data = await resp.json();
|
|
3156
|
+
if (resp.status === 503) {
|
|
3157
|
+
_vaSetStatus('ASR 服务未配置');
|
|
3158
|
+
} else if (data.text) {
|
|
3159
|
+
_vaLines.push(data.text);
|
|
3160
|
+
_vaRender();
|
|
3161
|
+
_vaSetStatus(`第 ${_vaLines.length} 段已录入,可继续录或点"整理成需求"`);
|
|
3162
|
+
} else {
|
|
3163
|
+
_vaSetStatus('未识别到内容,请重试');
|
|
3164
|
+
}
|
|
3165
|
+
} catch (e) {
|
|
3166
|
+
_vaSetStatus('上传失败: ' + e.message);
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
window.openVaSheet = function() {
|
|
3171
|
+
document.getElementById('va-sheet').classList.add('active');
|
|
3172
|
+
_vaRender();
|
|
3173
|
+
};
|
|
3174
|
+
window.closeVaSheet = function() {
|
|
3175
|
+
document.getElementById('va-sheet').classList.remove('active');
|
|
3176
|
+
if (_vaActive) _vaStop();
|
|
3177
|
+
};
|
|
3178
|
+
window.vaClear = function() {
|
|
3179
|
+
_vaLines = [];
|
|
3180
|
+
document.getElementById('va-result-wrap').style.display = 'none';
|
|
3181
|
+
document.getElementById('va-send-btn').style.display = 'none';
|
|
3182
|
+
document.getElementById('va-organize-btn').style.display = '';
|
|
3183
|
+
_vaRender();
|
|
3184
|
+
_vaSetStatus('');
|
|
3185
|
+
};
|
|
3186
|
+
window.vaOrganize = async function() {
|
|
3187
|
+
if (_vaLines.length === 0) { _vaSetStatus('请先录入语音内容'); return; }
|
|
3188
|
+
const btn = document.getElementById('va-organize-btn');
|
|
3189
|
+
btn.textContent = '整理中...';
|
|
3190
|
+
btn.disabled = true;
|
|
3191
|
+
try {
|
|
3192
|
+
const resp = await fetch('/api/voice-assist/organize', {
|
|
3193
|
+
method: 'POST',
|
|
3194
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3195
|
+
body: JSON.stringify({ transcript: _vaLines.join('\n'), sessionId: SESSION_ID }),
|
|
3196
|
+
});
|
|
3197
|
+
const data = await resp.json();
|
|
3198
|
+
if (data.requirement) {
|
|
3199
|
+
document.getElementById('va-result').value = data.requirement;
|
|
3200
|
+
document.getElementById('va-result-wrap').style.display = '';
|
|
3201
|
+
document.getElementById('va-send-btn').style.display = '';
|
|
3202
|
+
_vaSetStatus('整理完成,可编辑后点"发送到对话"');
|
|
3203
|
+
} else {
|
|
3204
|
+
_vaSetStatus('整理失败: ' + (data.error || '未知错误'));
|
|
3205
|
+
}
|
|
3206
|
+
} catch (e) {
|
|
3207
|
+
_vaSetStatus('请求失败: ' + e.message);
|
|
3208
|
+
} finally {
|
|
3209
|
+
btn.textContent = '整理成需求';
|
|
3210
|
+
btn.disabled = false;
|
|
3211
|
+
}
|
|
3212
|
+
};
|
|
3213
|
+
window.vaSend = function() {
|
|
3214
|
+
const text = document.getElementById('va-result').value.trim();
|
|
3215
|
+
if (!text) return;
|
|
3216
|
+
cmdInput.value = text;
|
|
3217
|
+
window.closeVaSheet();
|
|
3218
|
+
if (!_isTouchDevice()) cmdInput.focus();
|
|
3219
|
+
};
|
|
3220
|
+
|
|
3221
|
+
// 大录音按钮事件(长按逻辑与主录音按钮一致)
|
|
3222
|
+
_vaBtn.addEventListener('pointerdown', (e) => {
|
|
3223
|
+
e.preventDefault();
|
|
3224
|
+
_vaStartTime2 = Date.now();
|
|
3225
|
+
_vaStart();
|
|
3226
|
+
});
|
|
3227
|
+
_vaBtn.addEventListener('pointerup', (e) => {
|
|
3228
|
+
e.preventDefault();
|
|
3229
|
+
if (!_vaActive) return;
|
|
3230
|
+
const elapsed = Date.now() - _vaStartTime2;
|
|
3231
|
+
if (elapsed < MIN_RECORD_MS) {
|
|
3232
|
+
_vaActive = false;
|
|
3233
|
+
_vaBtn.textContent = '按住说话';
|
|
3234
|
+
_vaBtn.style.background = '#1f6feb';
|
|
3235
|
+
try { _vaMR?.stream?.getTracks().forEach(t => t.stop()); } catch {}
|
|
3236
|
+
try { if (_vaMR?.state !== 'inactive') _vaMR?.stop(); } catch {}
|
|
3237
|
+
_vaMR = null;
|
|
3238
|
+
_vaSetStatus('请按住按钮说话,松手识别');
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
_vaStop();
|
|
3242
|
+
});
|
|
3243
|
+
_vaBtn.addEventListener('pointercancel', () => { if (_vaActive) _vaStop(); });
|
|
3244
|
+
_vaBtn.addEventListener('click', (e) => {
|
|
3245
|
+
if (_isTouchDevice()) return; // 触屏由 pointer 处理
|
|
3246
|
+
if (_vaActive) { _vaStop(); } else { _vaStart(); }
|
|
3247
|
+
});
|
|
3248
|
+
|
|
3045
3249
|
cmdInput.focus();
|
|
3046
3250
|
|
|
3047
3251
|
// ── 版本更新检查 ──────────────────────────────
|