feat(notes): 원문 편집/이력 복원 시 AI 재처리
dogfood: 사용자가 노트 본문 수정해도 기존 AI 제목/요약이 그대로 남는 문제. NoteRepository.markAiPendingForReprocess 추가 — done/failed/pending 노트를 pending 으로 reset + pending_jobs 재투입. disabled 는 사용자가 명시적으로 비활성화한 상태라 존중하여 no-op. inboxApi 의 update-raw-text / restore-revision 핸들러가 raw 갱신 후 위 헬퍼 + worker.enqueue + pushNoteUpdated 호출. NoteCard.saveRaw 는 optimistic 으로 aiStatus='pending' 즉시 반영 → UI 가 "Inkling이 정리하는 중…" 즉시 표시, 백엔드 push 로 자동 sync. updateAiResult 의 user-edit 가드가 사용자가 직접 편집한 title/summary 는 새 AI 결과로 덮어쓰지 않으므로 안전. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -303,6 +303,7 @@ export function registerInboxApi(deps: InboxIpcDeps): void {
|
||||
return { ok: false as const, reason: 'empty' as const };
|
||||
}
|
||||
deps.repo.updateRawText(id, newText);
|
||||
await reprocessAi(deps, id);
|
||||
return { ok: true as const };
|
||||
});
|
||||
|
||||
@@ -311,6 +312,7 @@ export function registerInboxApi(deps: InboxIpcDeps): void {
|
||||
ipcMain.handle('inbox:restore-revision', async (_e, id: string, revId: number) => {
|
||||
try {
|
||||
deps.repo.restoreRevision(id, revId);
|
||||
await reprocessAi(deps, id);
|
||||
return { ok: true as const };
|
||||
} catch (e) {
|
||||
return { ok: false as const, reason: (e as Error).message };
|
||||
@@ -344,6 +346,19 @@ export function pushNoteUpdated(getWin: () => BrowserWindow | null, note: Note):
|
||||
w.webContents.send('note:updated', note);
|
||||
}
|
||||
|
||||
/**
|
||||
* 원문 변경 후 AI 재처리 트리거. ai_status='pending' 으로 reset + pending_jobs 재투입 +
|
||||
* worker enqueue + renderer push. disabled 노트는 사용자 명시 비활성화 의도 존중하여 skip.
|
||||
*/
|
||||
async function reprocessAi(deps: InboxIpcDeps, id: string): Promise<void> {
|
||||
const now = new Date().toISOString();
|
||||
const r = deps.repo.markAiPendingForReprocess(id, now);
|
||||
if (!r.ok) return;
|
||||
if (deps.enqueue) await deps.enqueue(id);
|
||||
const updated = deps.repo.findById(id);
|
||||
if (updated !== null) pushNoteUpdated(deps.getInboxWindow, updated);
|
||||
}
|
||||
|
||||
export function pushOllamaStatus(getWin: () => BrowserWindow | null, status: HealthResult): void {
|
||||
const w = getWin();
|
||||
if (!w || w.isDestroyed()) return;
|
||||
|
||||
@@ -269,6 +269,29 @@ export class NoteRepository {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* raw_text 편집/복원 직후 AI 재처리 트리거. done/failed/pending 노트를 pending 으로
|
||||
* reset + pending_jobs row 보장 (attempts=0). disabled 는 사용자의 명시적 비활성화
|
||||
* 의도 존중 — no-op. updateAiResult 의 user-edit 가드 (title_edited_by_user 등) 가
|
||||
* 사용자가 직접 편집한 필드는 새 AI 결과로 덮어쓰지 않음.
|
||||
*/
|
||||
markAiPendingForReprocess(id: string, now: string): { ok: boolean } {
|
||||
const row = this.db
|
||||
.prepare(`SELECT ai_status FROM notes WHERE id=? AND deleted_at IS NULL`)
|
||||
.get(id) as { ai_status: string } | undefined;
|
||||
if (!row || row.ai_status === 'disabled') return { ok: false };
|
||||
const tx = this.db.transaction(() => {
|
||||
this.db
|
||||
.prepare(`UPDATE notes SET ai_status='pending', ai_error=NULL, updated_at=? WHERE id=?`)
|
||||
.run(now, id);
|
||||
this.db
|
||||
.prepare(`INSERT OR REPLACE INTO pending_jobs (note_id, attempts, next_run_at) VALUES (?, 0, ?)`)
|
||||
.run(id, now);
|
||||
});
|
||||
tx();
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.3.9 — pending 노트의 AI 처리 cancel. ai_status='disabled' 로 전환 + pending_jobs 삭제.
|
||||
* raw_text 는 보존. 사용자가 무한 pending (Ollama 끊김 등) 에서 빠져나오는 path.
|
||||
|
||||
@@ -154,7 +154,15 @@ export function NoteCard({ note, onDeleted, onUpdated, mode = 'inbox', onRestore
|
||||
if (next.trim().length === 0) return;
|
||||
const r = await inboxApi.updateRawText(note.id, next);
|
||||
if (!r.ok) return;
|
||||
const updated = { ...local, rawText: next, updatedAt: new Date().toISOString() };
|
||||
// disabled 노트는 AI 재처리 안 됨 (서버에서 skip) — aiStatus 유지.
|
||||
// 그 외는 optimistic 으로 pending 표시 → AiWorker 완료 시 push 로 자동 sync.
|
||||
const willReprocess = local.aiStatus !== 'disabled';
|
||||
const updated = {
|
||||
...local,
|
||||
rawText: next,
|
||||
updatedAt: new Date().toISOString(),
|
||||
...(willReprocess ? { aiStatus: 'pending' as const, aiError: null } : {})
|
||||
};
|
||||
setLocal(updated);
|
||||
onUpdated(updated);
|
||||
setEditingRaw(false);
|
||||
|
||||
@@ -22,6 +22,7 @@ function makeDeps(overrides: Partial<InboxIpcDeps> = {}): InboxIpcDeps {
|
||||
updateRawText: vi.fn(),
|
||||
listRevisions: vi.fn(() => []),
|
||||
restoreRevision: vi.fn(),
|
||||
markAiPendingForReprocess: vi.fn(() => ({ ok: false })),
|
||||
findById: vi.fn(),
|
||||
list: vi.fn(),
|
||||
listByStatus: vi.fn(),
|
||||
|
||||
Reference in New Issue
Block a user