From e2e8b9b9219c1d077e3d7ab6d5cc521964e2f12c Mon Sep 17 00:00:00 2001 From: altair823 Date: Sun, 10 May 2026 04:43:03 +0900 Subject: [PATCH] feat(v031): buildVisionPrompt + GenerateInput.images + GenerateOptions.visionModel Co-Authored-By: Claude Sonnet 4.6 --- src/main/ai/InferenceProvider.ts | 9 ++++++++- src/main/ai/visionPrompt.ts | 17 +++++++++++++++++ tests/unit/visionPrompt.test.ts | 23 +++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/main/ai/visionPrompt.ts create mode 100644 tests/unit/visionPrompt.test.ts diff --git a/src/main/ai/InferenceProvider.ts b/src/main/ai/InferenceProvider.ts index 29b4fe8..5b6ffb9 100644 --- a/src/main/ai/InferenceProvider.ts +++ b/src/main/ai/InferenceProvider.ts @@ -6,13 +6,20 @@ export interface GenerateInput { todayKst: string; // ISO YYYY-MM-DD in KST dueDateCandidates: ParseResult[]; vocab?: string[]; // v0.2.3 #3 — top-N kebab-case 태그. 미전달 시 빈 배열로 처리. + // v0.3.1 Cut F — 첨부 이미지. 미전달 시 텍스트 전용 처리. + images?: Array<{ base64: string; mime: string }>; +} + +export interface GenerateOptions { + /** v0.3.1 Cut F — vision 전용 model 지정. null/미전달 시 기본 model 사용. */ + visionModel?: string | null; } export interface HealthResult { ok: boolean; model?: string; reason?: string; } export interface InferenceProvider { readonly name: string; - generate(input: GenerateInput): Promise; + generate(input: GenerateInput, opts?: GenerateOptions): Promise; healthCheck(): Promise; /** v0.2.3.1 — 외부에서 in-flight generate 강제 중단. ProviderHolder.replace 시 사용. */ abort?: () => void; diff --git a/src/main/ai/visionPrompt.ts b/src/main/ai/visionPrompt.ts new file mode 100644 index 0000000..0858ab9 --- /dev/null +++ b/src/main/ai/visionPrompt.ts @@ -0,0 +1,17 @@ +export function buildVisionPrompt( + text: string, + todayKst: string, + dueCandidates: string[], + vocab: string[] +): string { + return `다음 메모와 첨부 이미지를 종합 분석해 한국어로 요약하세요. + +메모 본문 (비어 있을 수 있음): +${text || '(이미지만 있음)'} + +이미지 분석 시 주요 시각적 정보 (텍스트, 사람, 장면) 도 포함해 요약하세요. +출력 JSON: { "title": "...", "summary": "...", "tags": [...], "due_date": "..." } +오늘: ${todayKst} +가능한 due 후보: ${dueCandidates.join(', ')} +빈출 태그: ${vocab.slice(0, 20).join(', ')}`; +} diff --git a/tests/unit/visionPrompt.test.ts b/tests/unit/visionPrompt.test.ts new file mode 100644 index 0000000..5b42577 --- /dev/null +++ b/tests/unit/visionPrompt.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest'; +import { buildVisionPrompt } from '@main/ai/visionPrompt.js'; + +describe('buildVisionPrompt', () => { + it('includes text, todayKst, dueCandidates, and vocab slice when present', () => { + const result = buildVisionPrompt( + '회의 메모', + '2026-05-09', + ['2026-05-10', '2026-05-15'], + ['work', 'meeting', 'project', 'todo'] + ); + expect(result).toContain('회의 메모'); + expect(result).toContain('2026-05-09'); + expect(result).toContain('2026-05-10, 2026-05-15'); + expect(result).toContain('work, meeting, project, todo'); + }); + + it('uses (이미지만 있음) placeholder when text is empty', () => { + const result = buildVisionPrompt('', '2026-05-09', [], []); + expect(result).toContain('(이미지만 있음)'); + expect(result).not.toContain('\n\n\n'); // no double-blank from empty text + }); +});