- docs/design/311-llm-warmup/README.md — 기능 설계서. ChatWarmupController (5-state) + GemmaLlmService _loadingFuture concurrent guard + ModelLifecycle.quickCheck (lightweight ready). - docs/design/311-llm-warmup/UX-REVIEW.md — UX-Reviewer parallel pass. Strong 4 + Suggest 2 권고. 입력창 enabled 유지 (타이핑 가능) + hintText 만 교체 + 상태-행동 분리. - docs/design/311-llm-warmup/fn-chat_warmup_controller.md — start/retry 상세 + 빠른 경로 (isLoaded 시 Loading skip). - docs/design/311-llm-warmup/fn-concurrent_load_guard.md — _loadingFuture 패턴 + whenComplete cleanup. - .claude/agents/ux-reviewer.md — 신규 페르소나 (02-Architect 단계 내 parallel reviewer, 카테고리 부여 X). AC 8 → 12 (UX 신규 4건 통합). OQ 3건 모두 해소. ADR 없음 (backward-compatible 추가). Refs #311 #260
4.7 KiB
4.7 KiB
함수 설계서: ChatWarmupController.start (#311)
부모 설계서: ./README.md · 상태: Draft 작성: [AI] Architect · 구현:
app/lib/state/chat_warmup_provider.dart:start· 테스트:app/test/state/chat_warmup_test.dart
1. 시그니처
class ChatWarmupController extends StateNotifier<ChatWarmupState> {
ChatWarmupController({
required this.llm,
required this.lifecycle,
}) : super(const ChatWarmupIdle());
final LlmService llm;
final ModelLifecycle lifecycle;
bool _disposed = false;
Future<void> start();
Future<void> retry();
@override
void dispose() { _disposed = true; super.dispose(); }
}
2. 책임 (단일 책임)
모델 ready 추정 → background load() → state 전이까지를 한 번의 트랜잭션으로 묶고, 모든 실패/취소 분기에서 안전하게 state 만 갱신한다.
3. 입력
| 파라미터 | 타입 | 제약/검증 | 설명 |
|---|---|---|---|
(ctor) llm |
LlmService |
non-null | load/isLoaded 만 사용. |
(ctor) lifecycle |
ModelLifecycle |
non-null | quickCheck 만 사용. |
4. 출력
- 반환:
Future<void>— 완료 시점에 state 가 ready/failed/unavailable 중 하나로 확정. - 부수효과:
state =설정. 다른 I/O 없음.
5. 동작 / 알고리즘
1. 현재 state 가 Loading 이면 즉시 return (재진입 가드, retry 외에는 발생 X).
2. state = ChatWarmupLoading() 임시 설정 (단, 아래 빠른 경로 확인 전이라 안전).
→ ChatScreen 재진입 시 깜빡임 방지 위해 isLoaded 빠른 경로를 먼저 확인:
if (llm.isLoaded) {
_safeSet(const ChatWarmupReady());
return;
}
3. quickCheck = await lifecycle.quickCheck();
4. quickCheck != ready:
_safeSet(const ChatWarmupUnavailable());
return;
5. _safeSet(const ChatWarmupLoading()); // 본격 로드 시작
6. try { await llm.load(); }
catch (e) {
_safeSet(ChatWarmupFailed(_messageFor(e)));
return;
}
7. _safeSet(const ChatWarmupReady());
_safeSet(s) = if (_disposed) return; state = s;
retry() = state = ChatWarmupIdle(); 후 await start();.
6. 에러 & 실패 모드
| 조건 | 처리 | 반환/예외 |
|---|---|---|
quickCheck 가 DB lock 등으로 throw |
lifecycle.quickCheck 내부 catch → corrupt 반환 |
state = Unavailable (보수적) |
llm.load() 가 FileSystemException('model file missing') |
_messageFor 가 매핑 → "AI 모델 파일을 찾을 수 없어요. 설정에서 다시 다운로드해주세요." | state = Failed |
llm.load() 가 기타 throw (native init 실패, OOM) |
_messageFor → "AI 시작 중 오류가 발생했어요. 잠시 후 다시 시도해주세요." | state = Failed |
| start() 진행 중 dispose() | _disposed = true → _safeSet 가 no-op |
state 변경 안 함 (마지막 set 유지) |
| concurrent start() 호출 | step 1 의 Loading 가드 — 외부에서는 retry() 만 사용하므로 정상 흐름에서 미발생 | early return |
7. 엣지케이스
- ChatScreen 재진입 (이미 loaded): step 2 의 빠른 경로로 Loading 단계 skip → 라벨 깜빡임 없음.
- start() 진행 중 ChatScreen pop → push (빠른 재진입): 첫 인스턴스 dispose, 두 번째 인스턴스의 start() 가 새로 호출.
_disposed가 인스턴스별이라 race 없음.llm._loadingFuture가 native init 중복 차단. - opt-in 토글 race: 사용자가 ChatScreen 진입과 동시에 SettingsScreen 에서 opt-out → ChatScreen 의 🤖 entry 가 hidden 되며 즉시 pop. dispose 가드로 안전.
8. 복잡도 / 성능
- 시간: O(1) +
lifecycle.quickCheckO(1) (meta_kv 4 쿼리 + 1 stat) +llm.load()(수 초). - 공간: state object 1개.
- 호출 빈도: ChatScreen mount 당 1회 (+retry 횟수).
9. 의존성
LlmService(load,isLoaded) — 인터페이스 안정.ModelLifecycle.quickCheck(신규).flutter_riverpodStateNotifier.
10. 테스트 케이스
- 정상 happy: quickCheck=ready, load delay 100ms → 시퀀스 [Idle → Loading → Ready].
- 빠른 경로: isLoaded=true → 시퀀스 [Idle → Ready] (Loading 없음).
- unavailable: quickCheck=missing → 시퀀스 [Idle → Unavailable], load 호출 안 됨.
- failure: load throws FileSystemException → state = Failed + 매핑된 한국어 메시지.
- failure: load throws StateError → state = Failed + generic 메시지.
- retry: Failed → retry() → Loading → Ready.
- unmount race: start() 진행 중 dispose() → state 변경 시도 무시 (마지막 state = Loading 유지).
- DB 예외: quickCheck 가 throw → Unavailable.
11. 추적성
- 인수조건: AC1, AC2, AC3, AC4, AC5, AC6.
- 관련 ADR: 없음.