From 4266376b2309491efaab321e54c48c541660148f Mon Sep 17 00:00:00 2001 From: th-kim0823
` 로 `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.