release: ship v1.5.0 with on-device writing suggestions
Adds next-sentence suggestions while typing, weekly input insights and a personal phrase memory to the desktop app, and fixes custom instructions so they process the text instead of inserting the instruction's own wording. Local model requests are now bounded and individually cancellable. Bumps the product version to 1.5.0 (Android/iOS build 1050000), refreshes the landing and web download links, and records the new INPUT feature rows and the open verification gaps in the infrastructure map.
This commit is contained in:
parent
99f06c253c
commit
5c11ee2fde
104 changed files with 14410 additions and 174 deletions
|
|
@ -11,6 +11,8 @@ import { persistCompletedVoiceSessionSafe } from './voice-session-persist'
|
|||
import { getTextInsertService } from './services/TextInsertService'
|
||||
import { getCustomInstructionService } from './services/CustomInstructionService'
|
||||
import { getVoiceCommandService } from './services/VoiceCommandService'
|
||||
import { getInputTelemetryService } from './services/InputTelemetryService'
|
||||
import { getSuggestionService } from './services/SuggestionService'
|
||||
import { getSoundEffectService } from './services/SoundEffectService'
|
||||
import { getAutoLaunchService } from './services/AutoLaunchService'
|
||||
import { getAudioCaptureService } from './services/AudioCaptureService'
|
||||
|
|
@ -30,6 +32,8 @@ import {
|
|||
isCommandPopupVisible,
|
||||
hideRecordingTip,
|
||||
updateRecordingTipState,
|
||||
showSuggestionOverlay,
|
||||
hideSuggestionOverlay,
|
||||
} from './windows/WindowManager'
|
||||
import { createTray } from './windows/TrayManager'
|
||||
import { registerAllIpcHandlers } from './ipc'
|
||||
|
|
@ -62,6 +66,7 @@ export async function bootstrap(): Promise<void> {
|
|||
{ name: 'auto-launch', critical: false, fn: initAutoLaunch },
|
||||
{ name: 'popup-preload', critical: false, fn: initPopupWindows },
|
||||
{ name: 'key-bindings', critical: false, fn: initKeyBindings },
|
||||
{ name: 'input-intelligence', critical: false, fn: initInputIntelligence },
|
||||
{ name: 'voice-mode', critical: false, fn: initVoiceMode },
|
||||
{ name: 'stt-warmup', critical: false, fn: initSTTWarmup },
|
||||
{ name: 'llm-polling', critical: false, fn: initLLMPolling },
|
||||
|
|
@ -125,6 +130,91 @@ async function initKeyBindings(): Promise<void> {
|
|||
keyBindings.start()
|
||||
}
|
||||
|
||||
/**
|
||||
* 입력 인텔리전스 배선 — 텔레메트리 → 제안 → 오버레이.
|
||||
*
|
||||
* 두 서비스를 직접 서로 import 하지 않고 여기서 이벤트로 는다
|
||||
* (서비스 간 결합을 늘리지 않으면서 교체 가능성을 남긴다).
|
||||
*/
|
||||
async function initInputIntelligence(): Promise<void> {
|
||||
const telemetry = getInputTelemetryService()
|
||||
const suggestion = getSuggestionService()
|
||||
|
||||
telemetry.on('typed-context', (context) => suggestion.handleTypingContext(context))
|
||||
telemetry.on('state-changed', (state) =>
|
||||
sendToMainWindow(IPC_CHANNELS.INPUT_TELEMETRY.STATE_CHANGED, state)
|
||||
)
|
||||
telemetry.on('activity', (payload) =>
|
||||
sendToMainWindow(IPC_CHANNELS.INPUT_TELEMETRY.ACTIVITY, payload)
|
||||
)
|
||||
|
||||
suggestion.on('updated', (state) => {
|
||||
// 오버레이/윈도우 호출이 예외를 던지면 서비스의 진행 플래그가 굳을 수 있다.
|
||||
// 이벤트 경로는 어떤 경우에도 조용히 삼킨다 (UI 문제로 기능 전체가 멈추면 안 된다).
|
||||
try {
|
||||
const hasSomethingToShow =
|
||||
state.candidates.length > 0 || state.generating || state.warmingUp || !!state.partialText
|
||||
const shouldPresent = hasSomethingToShow && !!state.anchor
|
||||
telemetry.setSuggestionPresentationActive(shouldPresent)
|
||||
if (!shouldPresent) {
|
||||
hideSuggestionOverlay()
|
||||
} else {
|
||||
showSuggestionOverlay({
|
||||
candidates: state.candidates,
|
||||
activeIndex: state.activeIndex,
|
||||
generating: state.generating,
|
||||
warmingUp: state.warmingUp,
|
||||
partialText: state.partialText,
|
||||
anchor: state.anchor,
|
||||
appName: state.appName,
|
||||
provenance: state.provenance
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`제안 오버레이 표시 실패: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
sendToMainWindow(IPC_CHANNELS.SUGGESTION.UPDATED, state)
|
||||
})
|
||||
suggestion.on('cleared', (payload) => {
|
||||
telemetry.setSuggestionPresentationActive(false)
|
||||
try {
|
||||
hideSuggestionOverlay()
|
||||
} catch (error) {
|
||||
logger.warn(`제안 오버레이 숨기기 실패: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
sendToMainWindow(IPC_CHANNELS.SUGGESTION.CLEARED, payload)
|
||||
})
|
||||
suggestion.on('state-changed', (state) =>
|
||||
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-dismiss') suggestion.dismiss('dismissed')
|
||||
})
|
||||
|
||||
// Chromium(Electron)은 접근성 지원이 감지될 때만 AX 트리를 만든다. 켜지 않으면
|
||||
// 우리 앱 자신의 입력창은 UIA 로 읽히지 않아 D3RO 안에서 타이핑할 때 제안이 죽는다.
|
||||
if (configGet('inputTelemetryEnabled') || configGet('suggestionEnabled')) {
|
||||
app.setAccessibilitySupportEnabled(true)
|
||||
logger.info('[bootstrap] accessibility tree enabled for UIA context capture')
|
||||
}
|
||||
|
||||
telemetry.start()
|
||||
}
|
||||
|
||||
/** 메인 도우로만 이벤트 전송 (팝업은 자체 내부 채널을 다) */
|
||||
function sendToMainWindow(channel: IPCChannel, payload: unknown): void {
|
||||
const win = getMainWindow()
|
||||
if (win && !win.isDestroyed() && !win.webContents.isDestroyed()) {
|
||||
win.webContents.send(channel, payload)
|
||||
}
|
||||
}
|
||||
|
||||
async function initCustomInstructions(): Promise<void> {
|
||||
getCustomInstructionService().initialize()
|
||||
}
|
||||
|
|
@ -228,6 +318,7 @@ function unregisterPopupNavKeys(): void {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
async function initVoiceMode(): Promise<void> {
|
||||
const voiceMode = getVoiceModeService()
|
||||
voiceMode.connectKeyBindings()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue