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
|
|
@ -1,11 +1,14 @@
|
|||
// src/main/bootstrap.ts — 초기화 시퀀스
|
||||
|
||||
import { app, dialog, globalShortcut, ipcMain as ipcMainRef } from 'electron'
|
||||
import { uIOhook, type UiohookKeyboardEvent } from 'uiohook-napi'
|
||||
import { VK } from '@d3ro/core/keybinding'
|
||||
import { initLoggerService, getLogger } from './services/LoggerService'
|
||||
import { initConfigService, configGet, configSet } from './services/ConfigService'
|
||||
import { getKeyBindingService } from './services/KeyBindingService'
|
||||
import { getKeyBindingService, uiohookCodeToVk } from './services/KeyBindingService'
|
||||
import { acquireGlobalInputHook } from './services/global-input-hook'
|
||||
import { getVoiceModeService } from './services/VoiceModeService'
|
||||
import { startLocalLLMAvailability } from './services/LocalLLMService'
|
||||
import { startLocalLLMAvailability, getLocalLLMService } from './services/LocalLLMService'
|
||||
import { getHistoryService } from './services/HistoryService'
|
||||
import { persistCompletedVoiceSessionSafe } from './voice-session-persist'
|
||||
import { getTextInsertService } from './services/TextInsertService'
|
||||
|
|
@ -33,9 +36,12 @@ import {
|
|||
hideRecordingTip,
|
||||
updateRecordingTipState,
|
||||
showSuggestionOverlay,
|
||||
updateSuggestionOverlay,
|
||||
hideSuggestionOverlay,
|
||||
isSuggestionOverlayVisible,
|
||||
} from './windows/WindowManager'
|
||||
import { createTray } from './windows/TrayManager'
|
||||
import { decideSuggestionOverlayAction, shouldDismissOnEscape } from './suggestion-overlay-policy'
|
||||
import { registerAllIpcHandlers } from './ipc'
|
||||
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
|
||||
import type { IPCChannel } from '@d3ro/core/ipc-channels'
|
||||
|
|
@ -156,16 +162,32 @@ async function initInputIntelligence(): Promise<void> {
|
|||
state.candidates.length > 0 || state.generating || state.warmingUp || !!state.partialText
|
||||
const shouldPresent = hasSomethingToShow && !!state.anchor
|
||||
telemetry.setSuggestionPresentationActive(shouldPresent)
|
||||
if (!shouldPresent) {
|
||||
|
||||
const action = decideSuggestionOverlayAction(isSuggestionOverlayVisible(), shouldPresent)
|
||||
if (action === 'hide') {
|
||||
hideSuggestionOverlay()
|
||||
} else if (action === 'update') {
|
||||
// 이미 떠 있으면 내용만 갱신한다 — setBounds/present 를 매 스트리밍 청크마다
|
||||
// 다시 부르면 X 클릭이 재present 에 가로채이고 패널이 튄다(실측).
|
||||
updateSuggestionOverlay({
|
||||
candidates: state.candidates,
|
||||
activeIndex: state.activeIndex,
|
||||
targetTotal: state.targetTotal,
|
||||
generating: state.generating,
|
||||
warmingUp: state.warmingUp,
|
||||
partialText: state.partialText,
|
||||
provenance: state.provenance
|
||||
})
|
||||
} else {
|
||||
showSuggestionOverlay({
|
||||
candidates: state.candidates,
|
||||
activeIndex: state.activeIndex,
|
||||
targetTotal: state.targetTotal,
|
||||
generating: state.generating,
|
||||
warmingUp: state.warmingUp,
|
||||
partialText: state.partialText,
|
||||
anchor: state.anchor,
|
||||
anchorKind: state.anchorKind,
|
||||
appName: state.appName,
|
||||
provenance: state.provenance
|
||||
})
|
||||
|
|
@ -188,15 +210,33 @@ async function initInputIntelligence(): Promise<void> {
|
|||
sendToMainWindow(IPC_CHANNELS.SUGGESTION.STATE_CHANGED, state)
|
||||
)
|
||||
|
||||
// 제안 수락/순환/닫기는 전역 키바인딩으로만 들어온다 — 오버레이는 포커스를 갖지 않는다.
|
||||
// 제안 수락/순환/페이지/닫기는 전역 키바인딩으로만 들어온다 — 오버레이는 포커스를 갖지 않는다.
|
||||
getKeyBindingService().on('triggered', (payload) => {
|
||||
if (payload.type !== 'pressed') return
|
||||
if (payload.actionId === 'suggestion-accept') void suggestion.accept()
|
||||
else if (payload.actionId === 'suggestion-next') suggestion.next()
|
||||
else if (payload.actionId === 'suggestion-prev') suggestion.previous()
|
||||
else if (payload.actionId === 'suggestion-page-next') suggestion.pageNext()
|
||||
else if (payload.actionId === 'suggestion-page-prev') suggestion.pagePrev()
|
||||
else if (payload.actionId === 'suggestion-dismiss') suggestion.dismiss('dismissed')
|
||||
})
|
||||
|
||||
// 평범한 Esc(수정자 없음)로 제안을 닫는다 — 등록된 바인딩이 아니라 오버레이가 떠
|
||||
// 있을 때만 반응하는 전용 키다. Escape 는 단독 바인딩이 불가하므로(requiresModifier)
|
||||
// KeyBindingService 의 등록 바인딩 경로로는 절대 들어오지 않는다 — 원본 keydown을
|
||||
// 직접 듣는다(InputTelemetryService/KeyBindingService 가 이미 쓰는 것과 같은 패턴).
|
||||
acquireGlobalInputHook()
|
||||
uIOhook.on('keydown', (event: UiohookKeyboardEvent) => {
|
||||
if (uiohookCodeToVk(event.keycode) !== VK.Escape) return
|
||||
const shouldDismiss = shouldDismissOnEscape(suggestion.isPresentationActive, {
|
||||
ctrl: event.ctrlKey === true,
|
||||
alt: event.altKey === true,
|
||||
shift: event.shiftKey === true,
|
||||
meta: event.metaKey === true
|
||||
})
|
||||
if (shouldDismiss) suggestion.dismiss('dismissed')
|
||||
})
|
||||
|
||||
// Chromium(Electron)은 접근성 지원이 감지될 때만 AX 트리를 만든다. 켜지 않으면
|
||||
// 우리 앱 자신의 입력창은 UIA 로 읽히지 않아 D3RO 안에서 타이핑할 때 제안이 죽는다.
|
||||
if (configGet('inputTelemetryEnabled') || configGet('suggestionEnabled')) {
|
||||
|
|
@ -204,6 +244,15 @@ async function initInputIntelligence(): Promise<void> {
|
|||
logger.info('[bootstrap] accessibility tree enabled for UIA context capture')
|
||||
}
|
||||
|
||||
// 제안이 켜져 있으면 켜진 시점부터 모델을 미리 올려 둔다 — 토글 시에만 워밍업하면
|
||||
// 앱을 새로 켤 때마다 첫 제안이 콜드 로딩으로 타임아웃된다.
|
||||
if (configGet('suggestionEnabled')) void suggestion.warmUp()
|
||||
|
||||
// Ollama 서버가 나중에 뜨는 경우(느린 부팅 등) 가용해지는 시점에도 워밍업한다.
|
||||
getLocalLLMService().on('availability-changed', ({ available }) => {
|
||||
if (available && configGet('suggestionEnabled')) void suggestion.warmUp()
|
||||
})
|
||||
|
||||
telemetry.start()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue