release: ship v1.6.0 with paged suggestions and a cleaner phrase memory
Next-sentence suggestions now arrive one at a time up to twelve, shown three per page with Ctrl+Alt+Up/Down to move, Left/Right to page, Enter to accept and Esc to close; old default bindings migrate and the panel guide follows the live bindings. The overlay is redesigned, stays put while candidates stream and sits outside the input box when no caret is reported. The personal phrase memory stops learning from terminals, code editors and the coding-agent hub, ignores symbol-heavy lines and empty-field placeholders, and prunes existing entries that break those rules. Fixes suggestion keys starting dictation, installs stuck on a pre-1.5.0 speech engine without the focus endpoint, Ollama runner windows flashing while typing, the speech engine starting twice, and cold-model timeouts. Live captions can be dragged to a remembered position and show a waiting notice until the first line arrives. Bumps the product version to 1.6.0 (Android/iOS build 1060000).
This commit is contained in:
parent
856e375f3e
commit
2fe20fa7b5
71 changed files with 2469 additions and 366 deletions
|
|
@ -33,6 +33,8 @@
|
|||
* @param {{id: string, text: string, timestamp: number, isFinal: boolean}} segment
|
||||
*/
|
||||
function addSegment(segment) {
|
||||
// 첫 자막이 오면 준비 안내를 걷는다 (그 전까지는 계속 보여 빈 화면을 만들지 않는다).
|
||||
removeStatusLine()
|
||||
// delta 줄이 있으면 제거 (확정 줄로 교체)
|
||||
removeDeltaLine()
|
||||
|
||||
|
|
@ -172,26 +174,92 @@
|
|||
})
|
||||
|
||||
// 상태 변경
|
||||
// starting: 음성 모델을 준비하는 중
|
||||
// active : 듣고 있지만 첫 자막은 아직 — 소리를 모아 첫 인식을 마칠 때까지 몇 초 걸린다.
|
||||
// active 가 되자마자 안내를 지우면 그 몇 초가 빈 화면이라 "고장" 처럼 보였다.
|
||||
window.popupAPI.on('caption:stateChanged', function (data) {
|
||||
if (data._i18n) i18nStrings = data._i18n
|
||||
if (data._i18n) {
|
||||
i18nStrings = data._i18n
|
||||
if (handleHint) handleHint.textContent = i18nStrings.captionDragHint || ''
|
||||
}
|
||||
if (data.state === 'starting') {
|
||||
// 로딩 표시
|
||||
clearAllLines()
|
||||
var loadingEl = document.createElement('div')
|
||||
loadingEl.className = 'caption-line loading'
|
||||
loadingEl.id = 'caption-loading'
|
||||
loadingEl.textContent = i18nStrings.captionLoading || '⏳ Loading STT model...'
|
||||
loadingEl.style.fontSize = config.fontSize + 'px'
|
||||
linesContainer.appendChild(loadingEl)
|
||||
showStatusLine(i18nStrings.captionLoading)
|
||||
} else if (data.state === 'active') {
|
||||
// 로딩 표시 제거
|
||||
var existing = document.getElementById('caption-loading')
|
||||
if (existing && existing.parentNode) {
|
||||
existing.parentNode.removeChild(existing)
|
||||
}
|
||||
if (lines.length === 0) showStatusLine(i18nStrings.captionWaiting)
|
||||
} else if (data.state === 'inactive' || data.state === 'stopping') {
|
||||
clearAllLines()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ── 준비 안내 줄 ──────────────────────────────────────
|
||||
|
||||
function showStatusLine(text) {
|
||||
if (!text) return
|
||||
var el = document.getElementById('caption-loading')
|
||||
if (!el) {
|
||||
el = document.createElement('div')
|
||||
el.className = 'caption-line loading'
|
||||
el.id = 'caption-loading'
|
||||
linesContainer.appendChild(el)
|
||||
}
|
||||
el.style.fontSize = config.fontSize + 'px'
|
||||
el.textContent = text
|
||||
}
|
||||
|
||||
function removeStatusLine() {
|
||||
var el = document.getElementById('caption-loading')
|
||||
if (el && el.parentNode) el.parentNode.removeChild(el)
|
||||
}
|
||||
|
||||
// ── 끌어서 옮기기 ──────────────────────────────────────
|
||||
// 창은 클릭 통과라 평소엔 아래 앱을 가리지 않는다. 마우스 이동은 전달받으므로
|
||||
// 올라오면 손잡이를 보여 주고, 손잡이 위에서만 마우스를 받는다.
|
||||
|
||||
var root = document.getElementById('root')
|
||||
var handle = document.getElementById('handle')
|
||||
var handleHint = document.getElementById('handleHint')
|
||||
var hoverTimer = null
|
||||
var dragging = false
|
||||
|
||||
function send(channel, value) {
|
||||
if (window.popupAPI) window.popupAPI.send(channel, value)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', function () {
|
||||
if (!root) return
|
||||
root.classList.add('hovering')
|
||||
if (hoverTimer) clearTimeout(hoverTimer)
|
||||
hoverTimer = setTimeout(function () {
|
||||
if (!dragging) root.classList.remove('hovering')
|
||||
}, 1500)
|
||||
})
|
||||
|
||||
if (handle) {
|
||||
handle.addEventListener('mouseenter', function () {
|
||||
send('captionPopup:setInteractive', true)
|
||||
})
|
||||
handle.addEventListener('mouseleave', function () {
|
||||
if (!dragging) send('captionPopup:setInteractive', false)
|
||||
})
|
||||
handle.addEventListener('pointerdown', function (event) {
|
||||
if (event.button !== 0) return
|
||||
dragging = true
|
||||
handle.setPointerCapture(event.pointerId)
|
||||
if (root) root.classList.add('dragging')
|
||||
send('captionPopup:dragStart')
|
||||
})
|
||||
var finishDrag = function () {
|
||||
if (!dragging) return
|
||||
dragging = false
|
||||
if (root) root.classList.remove('dragging')
|
||||
send('captionPopup:dragEnd')
|
||||
}
|
||||
handle.addEventListener('pointerup', finishDrag)
|
||||
handle.addEventListener('lostpointercapture', finishDrag)
|
||||
handle.addEventListener('dblclick', function () {
|
||||
send('captionPopup:resetPosition')
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue