diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d2aea2..6dc4cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,33 @@ 본 파일은 Inkling 의 버전별 사용자 영향 변경 사항을 기록한다. 형식은 [Keep a Changelog](https://keepachangelog.com/) 를 느슨하게 따른다. +## [0.3.14] — 2026-05-12 + +AI 처리 fail 원인 가시화. 이전엔 ai_error 가 NoteCard tooltip (title attribute) 에만 있어 사용자가 마우스 오버해야 보이는 데다 raw 메시지만 노출 → 무엇이 fail 했는지 불명. + +### 수정 + +- **NoteCard failed 노트에 "원인 보기" 접힘 섹션 (P1).** `
` summary 클릭하면 `
` 로 `ai_error` 전체 노출. wrap + word-break 적용. 사용자가 직접 메시지를 보고 모델/네트워크/JSON 등 fail 카테고리 진단 가능.
+- **`ai_error` 에 reason + provider name prefix 추가.** AiWorker 의 markAiFailed 시 `[schema|other] local-ollama/gemma4:26b\n<원본 message>` 형식. 사용자가 어느 카테고리에서, 어느 모델로 실패했는지 즉시 식별. log 의 ai.failed 에도 reason/provider 필드 함께 출력.
+
+### 게이트
+
+- 단위 752 PASS (ai_error 포맷 변경은 test 영향 없음 — 기존 test 가 정확한 prefix 매칭 안 함)
+- typecheck 0 errors
+- 신규 npm dependency 0
+
+### 사용자 안내
+
+이미지 AI 처리가 fail 한다면 NoteCard 의 "정리 보류" 옆 "원인 보기" 클릭 → 표시되는 메시지로:
+- `[timeout] ...` → vision 모델 cold-start 가 5분 초과. `ollama run gemma4:26b` 으로 한번 warm-up 후 재시도
+- `[schema] title must contain Korean characters` → vision 모델이 영어 title 반환. prompt 가 한국어 강조했지만 일부 모델은 여전히 영어. `gemma3:27b` 등 다른 vision 모델로 대체 고려
+- `[schema] unparseable response: ...` → vision 모델 JSON 출력 안 따름. v0.3.12 의 loose parse 가 실패한 경우
+- `[other] missing response field` → Ollama 가 빈 응답 반환. 모델 자체 문제
+
+### 업그레이드
+
+v0.3.13 인스톨러 위에 v0.3.14 인스톨러를 같은 위치에 실행하면 in-place 업그레이드.
+
 ## [0.3.13] — 2026-05-12
 
 대형 vision 모델 (gemma4:26b 등) 의 cold-start timeout 으로 인한 AI 처리 실패 fix.
diff --git a/package-lock.json b/package-lock.json
index b52e644..56e73b9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "inkling",
-  "version": "0.3.13",
+  "version": "0.3.14",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "inkling",
-      "version": "0.3.13",
+      "version": "0.3.14",
       "dependencies": {
         "better-sqlite3": "12.9.0",
         "electron-log": "5.2.0",
diff --git a/package.json b/package.json
index ea76f9d..21d0f0a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "inkling",
-  "version": "0.3.13",
+  "version": "0.3.14",
   "private": true,
   "description": "Inkling — local-first 한 줄 보관 도구",
   "author": "altair823 ",
diff --git a/src/main/ai/AiWorker.ts b/src/main/ai/AiWorker.ts
index 809530a..9cb314f 100644
--- a/src/main/ai/AiWorker.ts
+++ b/src/main/ai/AiWorker.ts
@@ -233,8 +233,13 @@ export class AiWorker {
         const nextRunAt = new Date(Date.now() + (this.backoffsMs[attempt + 1] ?? 0)).toISOString();
         this.repo.incrementJobAttempt(job.noteId, nextRunAt, msg);
         if (isLast) {
-          this.repo.markAiFailed(job.noteId, msg);
-          this.logger.error('ai.failed', { noteId: job.noteId, err: msg });
+          // v0.3.14 — ai_error 에 reason + provider name prefix 추가. NoteCard 의 "원인 보기"
+          // 가 사용자에게 보여주는 raw 메시지에 context (timeout/unreachable/schema/other +
+          // 어느 모델이 fail 했는지) 가 포함되어 진단성 향상.
+          const provider = this.holder.get().name;
+          const annotated = `[${reason}] ${provider}\n${msg}`;
+          this.repo.markAiFailed(job.noteId, annotated);
+          this.logger.error('ai.failed', { noteId: job.noteId, err: msg, reason, provider });
           if (this.telemetry) {
             await this.telemetry.emit({
               kind: 'ai_failed',
diff --git a/src/renderer/inbox/components/NoteCard.tsx b/src/renderer/inbox/components/NoteCard.tsx
index d02e4fc..474dd59 100644
--- a/src/renderer/inbox/components/NoteCard.tsx
+++ b/src/renderer/inbox/components/NoteCard.tsx
@@ -236,23 +236,40 @@ export function NoteCard({ note, onDeleted, onUpdated, mode = 'inbox', onRestore
         
       )}
       {local.aiStatus === 'failed' && (
-        
-
- 정리 보류 — 원문은 안전합니다 +
+
+
+ 정리 보류 — 원문은 안전합니다 +
+ {/* v0.3.9 — per-note 재시도 UI. FailedBanner 의 일괄 재시도와 별개. */} +
- {/* v0.3.9 — per-note 재시도 UI. FailedBanner 의 일괄 재시도와 별개. */} - + {/* v0.3.14 — fail 원인 inline 표시. ai_error 의 raw message 가 그대로 사용자에게 + 보여서 디버깅 + 모델/네트워크 이슈 진단 가능. 너무 길면
로 접힘. */} + {local.aiError !== null && local.aiError.length > 0 && ( +
+ + 원인 보기 + +
+                {local.aiError}
+              
+
+ )}
)} {/* v0.2.9 Cut B Task 13 — ai_status='disabled': raw_text 첫 줄 fallback title.