fix(desktop): harden session, meeting, caption and LLM lifecycles; route LLM calls through the gateway
This commit is contained in:
parent
3a46437f28
commit
ddc78546f0
62 changed files with 4786 additions and 648 deletions
126
apps/desktop/src/main/windows/suggestion-overlay-presenter.ts
Normal file
126
apps/desktop/src/main/windows/suggestion-overlay-presenter.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// src/main/windows/suggestion-overlay-presenter.ts
|
||||
// 입력 제안 오버레이 표시 정책의 단일 정본 (RecordingTipPresenter 와 같은 형태).
|
||||
//
|
||||
// 예전엔 bootstrap 의 이벤트 클로저 하나에 표시 여부 판단, 텔레메트리 표시 플래그 동기화, show/update/hide
|
||||
// 선택, payload 매핑(show 와 update 가 같은 7개 필드를 따로 나열), 예외 삼키기가 섞여 있었다. bootstrap 은
|
||||
// electron·서비스를 최상위에서 import 해 이 정책을 테스트할 수 없었고, 필드를 추가할 때마다 두 매핑을 따로
|
||||
// 고쳐야 했다(누락돼도 컴파일러가 못 잡았다). 이제 매핑은 함수 하나이고, 창은 뷰 포트로 주입한다.
|
||||
|
||||
import type { AnchorKind, PhysicalRect, SuggestionState } from '@d3ro/core/input-intelligence'
|
||||
import { decideSuggestionOverlayAction } from '../suggestion-overlay-policy'
|
||||
import {
|
||||
hideSuggestionOverlay,
|
||||
isSuggestionOverlayVisible,
|
||||
showSuggestionOverlay,
|
||||
updateSuggestionOverlay,
|
||||
} from './WindowManager'
|
||||
|
||||
/** 오버레이 내용 — show 와 update 가 같이 쓴다 */
|
||||
export type SuggestionOverlayContentPayload = Pick<
|
||||
SuggestionState,
|
||||
'candidates' | 'activeIndex' | 'targetTotal' | 'generating' | 'warmingUp' | 'partialText' | 'provenance'
|
||||
>
|
||||
|
||||
/** 새로 띄울 때만 필요한 배치 정보를 더한 payload */
|
||||
export interface SuggestionOverlayShowPayload extends SuggestionOverlayContentPayload {
|
||||
anchor: PhysicalRect | null
|
||||
anchorKind: AnchorKind
|
||||
appName: string | null
|
||||
}
|
||||
|
||||
/** 상태 → 오버레이 내용 (단일 매핑) */
|
||||
export function toSuggestionOverlayContent(state: SuggestionState): SuggestionOverlayContentPayload {
|
||||
return {
|
||||
candidates: state.candidates,
|
||||
activeIndex: state.activeIndex,
|
||||
targetTotal: state.targetTotal,
|
||||
generating: state.generating,
|
||||
warmingUp: state.warmingUp,
|
||||
partialText: state.partialText,
|
||||
provenance: state.provenance,
|
||||
}
|
||||
}
|
||||
|
||||
/** 상태 → 새로 띄울 때의 payload (내용 + 앵커·앱 이름) */
|
||||
export function toSuggestionOverlayShowPayload(state: SuggestionState): SuggestionOverlayShowPayload {
|
||||
return {
|
||||
...toSuggestionOverlayContent(state),
|
||||
anchor: state.anchor,
|
||||
anchorKind: state.anchorKind,
|
||||
appName: state.appName,
|
||||
}
|
||||
}
|
||||
|
||||
/** 보여 줄 것(후보·생성 중·준비 중·부분 텍스트)이 있고 붙을 앵커가 있으면 표시한다. */
|
||||
export function shouldPresentSuggestion(state: SuggestionState): boolean {
|
||||
const hasSomethingToShow =
|
||||
state.candidates.length > 0 || state.generating || state.warmingUp || !!state.partialText
|
||||
return hasSomethingToShow && !!state.anchor
|
||||
}
|
||||
|
||||
/** 오버레이 창 포트 — 테스트에서는 가짜로 바꾼다 */
|
||||
export interface SuggestionOverlayView {
|
||||
show(payload: SuggestionOverlayShowPayload): void
|
||||
update(payload: SuggestionOverlayContentPayload): void
|
||||
hide(): void
|
||||
isVisible(): boolean
|
||||
}
|
||||
|
||||
export interface SuggestionOverlayPresenterDeps {
|
||||
view: SuggestionOverlayView
|
||||
/** 텔레메트리에 "제안을 보여 주는 중" 을 알린다(입력 가로채기 판단용) */
|
||||
setPresentationActive(active: boolean): void
|
||||
warn(message: string): void
|
||||
}
|
||||
|
||||
function describe(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
|
||||
export class SuggestionOverlayPresenter {
|
||||
constructor(private readonly _deps: SuggestionOverlayPresenterDeps) {}
|
||||
|
||||
/**
|
||||
* 제안 상태가 바뀌었다. 오버레이·창 호출이 던져도 삼킨다 — UI 문제로 서비스의 진행 플래그가
|
||||
* 굳어 기능 전체가 멈추면 안 된다. (텔레메트리 동기화는 오버레이 호출보다 먼저, 같은 보호 안에서)
|
||||
*/
|
||||
onUpdated(state: SuggestionState): void {
|
||||
try {
|
||||
const shouldPresent = shouldPresentSuggestion(state)
|
||||
this._deps.setPresentationActive(shouldPresent)
|
||||
const view = this._deps.view
|
||||
const action = decideSuggestionOverlayAction(view.isVisible(), shouldPresent)
|
||||
if (action === 'hide') {
|
||||
view.hide()
|
||||
} else if (action === 'update') {
|
||||
// 이미 떠 있으면 내용만 갱신한다 — setBounds/present 를 매 스트리밍 청크마다
|
||||
// 다시 부르면 X 클릭이 재present 에 가로채이고 패널이 튄다(실측).
|
||||
view.update(toSuggestionOverlayContent(state))
|
||||
} else {
|
||||
view.show(toSuggestionOverlayShowPayload(state))
|
||||
}
|
||||
} catch (error) {
|
||||
this._deps.warn(`제안 오버레이 표시 실패: ${describe(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/** 제안이 닫혔다. 텔레메트리 플래그는 항상 내리고, 숨기기 실패만 삼킨다. */
|
||||
onCleared(): void {
|
||||
this._deps.setPresentationActive(false)
|
||||
try {
|
||||
this._deps.view.hide()
|
||||
} catch (error) {
|
||||
this._deps.warn(`제안 오버레이 숨기기 실패: ${describe(error)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** WindowManager 의 제안 오버레이 창을 뷰 포트로 감싼다 */
|
||||
export function createWindowSuggestionOverlayView(): SuggestionOverlayView {
|
||||
return {
|
||||
show: (payload) => showSuggestionOverlay(payload),
|
||||
update: (payload) => updateSuggestionOverlay(payload),
|
||||
hide: () => hideSuggestionOverlay(),
|
||||
isVisible: () => isSuggestionOverlayVisible(),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue