fix(vision): graceful fallback 가시화 + 진단 로그

이전 fix 후에도 사용자가 "(첨부 메모)" placeholder 만 보이면 왜 fail 한지 모름.
가능성 큰 원인: vision_model 미설정 → text-only path → 본문 빈 응답.

- AiWorker: ai.vision.decide 로그 추가 — visionActive / visionModelConfigured /
  mediaCount / mediaStorePresent. logs/main.log 에서 진단 가능.
- NoteCard: ai_status='done' + title='(첨부 메모)' + media 있을 때 노란 banner.
  "vision 모델 설정 확인 + 직접 편집" 안내.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
th-kim0823
2026-05-12 14:44:41 +09:00
parent c616555d7d
commit e34f036f20
2 changed files with 23 additions and 1 deletions

View File

@@ -144,7 +144,17 @@ export class AiWorker {
// 5MB cap 초과 시 throw → AiWorker 의 'other' 분기 → markAiFailed 도달. // 5MB cap 초과 시 throw → AiWorker 의 'other' 분기 → markAiFailed 도달.
const visionModel = this.settings ? await this.settings.getVisionModel() : null; const visionModel = this.settings ? await this.settings.getVisionModel() : null;
let images: Array<{ base64: string; mime: string }> | undefined; let images: Array<{ base64: string; mime: string }> | undefined;
if (visionModel && note.media.length > 0 && this.mediaStore) { const visionActive = !!(visionModel && note.media.length > 0 && this.mediaStore);
// v0.3.14 — vision 활성 여부 진단 로그. 사용자가 vision_model 미설정으로 text-only
// path 가 도는지 / 이미지가 모델에 전달되는지 확인 가능 (logs/main.log).
this.logger.info('ai.vision.decide', {
noteId: job.noteId,
visionActive,
visionModelConfigured: !!visionModel,
mediaCount: note.media.length,
mediaStorePresent: !!this.mediaStore
});
if (visionActive) {
const oversize = note.media.find((m) => m.bytes > 5 * 1024 * 1024); const oversize = note.media.find((m) => m.bytes > 5 * 1024 * 1024);
if (oversize) { if (oversize) {
throw new Error(`image ${oversize.relPath} exceeds 5MB cap (${oversize.bytes} bytes)`); throw new Error(`image ${oversize.relPath} exceeds 5MB cap (${oversize.bytes} bytes)`);

View File

@@ -279,6 +279,18 @@ export function NoteCard({ note, onDeleted, onUpdated, mode = 'inbox', onRestore
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>{fallbackTitle}</h3> <h3 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>{fallbackTitle}</h3>
</div> </div>
)} )}
{/* v0.3.14 — graceful fallback 가시화. title 이 placeholder 면 vision 처리 실패
(모델 미선택 / 응답 unparseable / null 반환). 사용자가 설정 점검 + 수동 편집 유도. */}
{!isTrash && local.aiStatus === 'done' && local.aiTitle === '(첨부 메모)' && local.media.length > 0 && (
<div style={{
marginTop: 4, padding: '6px 10px', background: '#fff8e1',
borderRadius: 4, fontSize: 12, color: '#7a5a00'
}}>
💡 AI . AI Vision
vision-capable (: gemma4:26b, gemma3:27b) .
/ .
</div>
)}
{local.aiStatus === 'done' && ( {local.aiStatus === 'done' && (
<> <>
{isTrash ? ( {isTrash ? (