Initial Commit

This commit is contained in:
2026-09-16 17:22:14 +09:00
commit 858ee9e9da
335 changed files with 123898 additions and 0 deletions
@@ -0,0 +1,34 @@
# 명세 품질 체크리스트: 채팅 이미지 입력
**목적**: 계획 전에 명세의 완성도와 품질 확인
**작성일**: 2026-09-16
**기능**: [spec.md](../spec.md)
## 내용 품질
- [x] 구현 세부사항 없음
- [x] 사용자 가치와 필요에 집중함
- [x] 비개발자도 이해할 수 있음
- [x] 필수 섹션을 모두 작성함
## 요구사항 완성도
- [x] 미확정 표시가 없음
- [x] 요구사항이 테스트 가능하고 분명함
- [x] 성공 기준을 측정할 수 있음
- [x] 성공 기준이 구현 기술에 묶이지 않음
- [x] 인수 시나리오를 정의함
- [x] 예외 상황을 정의함
- [x] 범위가 분명함
- [x] 의존성과 가정을 정의함
## 기능 준비 상태
- [x] 모든 기능 요구사항에 확인 가능한 기준이 있음
- [x] 주요 흐름이 사용자 스토리에 포함됨
- [x] 성공 기준으로 기능을 확인할 수 있음
- [x] 명세에 구현 세부사항이 새지 않음
## 메모
- 전체 항목 통과. 계획 단계 진행 가능.
@@ -0,0 +1,20 @@
# 계약: POST /chat/stream
```ts
interface SnapImageInput {
mediaType: "image/png" | "image/jpeg" | "image/webp"
data: string
}
interface SnapStreamRequest {
sessionId: string
content: string
images?: SnapImageInput[]
forcedSkill?: string
explain?: boolean
}
```
- `content`가 비어 있으면 `images`가 한 장 이상이어야 함.
- `images` 생략 또는 빈 배열은 기존 텍스트 전용 요청과 같음.
- 이미지 원본은 응답과 과거 메시지 조회에 포함되지 않음.
+18
View File
@@ -0,0 +1,18 @@
# 데이터 모델: 채팅 이미지 입력
## SnapImageInput
| 필드 | 형식 | 규칙 |
|---|---|---|
| `mediaType` | 문자열 | `image/png`, `image/jpeg`, `image/webp` 중 하나 |
| `data` | 문자열 | mediaType과 일치하는 base64 data URL |
## 작성 중 질문
| 필드 | 형식 | 규칙 |
|---|---|---|
| 텍스트 | 문자열 | 비어 있어도 이미지가 있으면 전송 가능 |
| 텍스트 첨부 | 문자열 배열 | 기존 100자 이상 붙여넣기 흐름 유지 |
| 이미지 첨부 | `SnapImageInput[]` | 최대 4장, 개별 5 MiB, 전체 15 MiB |
이미지 첨부는 전송용 임시 상태이며 저장된 메시지와 관계를 만들지 않는다.
+49
View File
@@ -0,0 +1,49 @@
# 구현 계획: 채팅 이미지 입력
## 기술 배경
- React 19 + TypeScript + Zustand 기반 `features/snap`
- 기존 SSE 전송기 `streamLLM``/chat/stream` JSON 요청
- Tauri 화면 캡쳐가 만드는 PNG data URL
- 브라우저 Clipboard API가 제공하는 PNG/JPEG/WebP Blob
- Vitest + Testing Library
## 규칙 확인
- `CLAUDE.md`를 유일한 작업 규칙으로 적용함
- 기존 `Composer`, `useSnapChat`, `snapStream`을 재사용함
- 서버 DTO는 `features/snap/contract/types.ts`의 기존 계약 위치에 둠
- 테스트를 먼저 실패시킨 뒤 최소 구현함
- 새 라이브러리는 추가하지 않음
## 설계
1. `SnapImageInput``mediaType`과 data URL `data`로 정의하고 `SnapStreamRequest.images`에 연결한다.
2. `Composer`가 캡쳐와 클립보드 이미지를 같은 첨부 목록으로 관리한다.
3. 첨부 시 형식, 개수, 개별 크기, 전체 크기를 검사한다.
4. `onSend`는 텍스트와 이미지 목록을 받아 새 대화와 기존 대화에서 같은 전송 경로를 쓴다.
5. 새 대화는 세션 생성 뒤 route state로 텍스트와 이미지를 넘기고, 세션 화면이 한 번 소비한다.
6. `useSnapChat`은 로컬 user 메시지를 만든 뒤 백엔드 요청에 `images`를 포함한다.
7. 과거 메시지 DTO에는 이미지 정보를 추가하지 않는다. 원본이 저장되지 않는 백엔드 계약을 따른다.
## 변경 파일
- `2_frontend/src/features/snap/contract/types.ts`
- `2_frontend/src/features/snap/components/Composer.tsx`
- `2_frontend/src/features/snap/components/Composer.test.tsx`
- `2_frontend/src/features/snap/hooks/useSnapChat.ts`
- `2_frontend/src/features/snap/hooks/useSnapChat.test.tsx`
- `2_frontend/src/features/snap/pages/NewChatPage.tsx`
- `2_frontend/src/features/snap/pages/SessionChatPage.tsx`
- `2_frontend/src/features/snap/api/snap.stream.test.ts`
## 위험과 대응
- base64 크기를 문자열 길이로 잘못 계산할 수 있음 → data URL 본문을 디코딩한 byte 길이로 검사
- 새 대화 이동 중 이미지가 사라질 수 있음 → 텍스트와 이미지 모두 route state 한 묶음으로 전달
- 요청 실패 때 작성 내용이 사라질 수 있음 → 전송이 시작되기 전 검증 실패에는 입력과 첨부를 보존함
## 검증
- 단위 테스트: 이미지 계약, 캡쳐 수신, 클립보드 첨부, 이미지-only, 제한 차단, 요청 body
- 전체: `npm run format`, `npm run lint`, `npm run test`, `npm run build`
+19
View File
@@ -0,0 +1,19 @@
# 빠른 검증: 채팅 이미지 입력
## 자동 검사
```powershell
cd 2_frontend
npm run format
npm run lint
npm run test
npm run build
```
## 직접 확인
1. `Ctrl+Shift+9`로 화면을 캡쳐해 새 대화에 썸네일이 붙는지 확인함.
2. 텍스트 없이 전송하고 이미지 내용을 반영한 답변이 오는지 확인함.
3. 기존 대화에서 클립보드 PNG/JPEG/WebP를 붙여 전송함.
4. 첨부 X 버튼과 최대 4장 제한을 확인함.
5. 텍스트만 보내 기존 동작이 유지되는지 확인함.
+21
View File
@@ -0,0 +1,21 @@
# 완료 보고: 채팅 이미지 입력
## 계획 대비 결과
- 백엔드의 `sessionId/content/images/forcedSkill/explain` 계약에 맞춰 Snap 요청 타입을 확장함
- 화면 캡쳐 이미지는 새 대화 생성 뒤에도 유실되지 않고 첫 stream 요청으로 전달됨
- 클립보드 이미지는 새 대화와 기존 대화 Composer에서 질문 첨부로 들어감
- 텍스트 없이 이미지만 전송할 수 있음
- 최대 4장, 장당 5 MiB, 전체 15 MiB, PNG/JPEG/WebP 제한을 적용함
- 백엔드가 원본을 저장하지 않는 계약대로 과거 메시지 DTO와 화면은 바꾸지 않음
## 검증
- `npm run format` 통과
- `npm run lint` 오류 0개, 기존 경고 9개
- `npm run test` 50개 파일, 278개 테스트 통과
- `npm run build` 통과
## 남은 확인
- 배포된 backend에서 `OPENROUTER_API_KEY`, vision 모델, DB migration이 준비된 상태로 실제 이미지 답변을 확인해야 함
+19
View File
@@ -0,0 +1,19 @@
# 조사: 채팅 이미지 입력
## 결정 1: 이미지 표현
- **결정**: 캡쳐와 클립보드 이미지를 `{ mediaType, data }`로 통일함.
- **이유**: 백엔드 `ChatImageInput`의 camelCase 계약과 정확히 맞고 캡쳐 결과가 이미 data URL임.
- **검토한 대안**: multipart 업로드는 백엔드 계약 변경이 필요해 제외함.
## 결정 2: 이미지 보존 범위
- **결정**: 작성 중과 현재 전송에만 이미지를 보존함.
- **이유**: 백엔드가 원본/base64를 저장하거나 응답하지 않음.
- **검토한 대안**: 로컬 영구 저장은 별도 개인정보·수명 관리가 필요해 제외함.
## 결정 3: 클립보드 동작
- **결정**: 이미지가 있으면 첨부 목록에 추가하고, 함께 제공된 텍스트도 기존 규칙대로 반영함.
- **이유**: 사용자가 선택한 범위이며 기존 Clipboard API 코드와 썸네일 UI를 재사용할 수 있음.
- **검토한 대안**: 클립보드 이력에만 보내는 기존 동작은 질문 전송 의도와 어긋나 폐기함.
+57
View File
@@ -0,0 +1,57 @@
# 기능 명세: 채팅 이미지 입력
## 사용자 시나리오 및 테스트
### 사용자 스토리 1 - 캡쳐 이미지로 질문 (P1)
사용자는 화면 캡쳐 이미지를 새 대화에 첨부하고, 텍스트 없이도 이미지 내용을 바탕으로 답변을 받을 수 있다.
**독립 테스트**: 캡쳐 이미지 한 장만 첨부해 전송하면 새 세션이 만들어지고 이미지가 포함된 질문이 처리된다.
### 사용자 스토리 2 - 클립보드 이미지로 질문 (P1)
사용자는 새 대화나 기존 대화에서 클립보드 이미지를 붙여 질문할 수 있다.
**독립 테스트**: PNG, JPEG 또는 WebP 이미지를 클립보드에서 붙이면 썸네일이 보이고 전송 요청에 포함된다.
### 사용자 스토리 3 - 잘못된 첨부를 전송 전에 확인 (P2)
사용자는 허용되지 않는 형식이나 크기의 이미지를 선택했을 때 즉시 이유를 확인하고 질문 내용을 잃지 않는다.
**독립 테스트**: 제한을 넘는 첨부를 시도하면 전송되지 않고 안내가 표시되며 기존 입력과 첨부가 남는다.
## 요구사항
- **FR-001**: 화면 캡쳐 이미지를 새 대화의 첨부로 전송할 수 있어야 한다.
- **FR-002**: 클립보드의 PNG, JPEG, WebP 이미지를 새 대화와 기존 대화에 첨부할 수 있어야 한다.
- **FR-003**: 텍스트 또는 이미지 중 하나만 있어도 전송할 수 있어야 한다.
- **FR-004**: 이미지는 최대 4장, 한 장당 5 MiB, 전체 15 MiB까지만 허용해야 한다.
- **FR-005**: 전송 전 각 이미지의 썸네일을 보여주고 개별 삭제할 수 있어야 한다.
- **FR-006**: 이미지 형식이나 크기 제한을 넘으면 이유를 알려주고 현재 입력을 보존해야 한다.
- **FR-007**: 전송이 성공적으로 시작된 뒤에만 입력과 첨부를 비워야 한다.
- **FR-008**: 이미지가 없는 기존 텍스트 전용 전송 동작을 유지해야 한다.
- **FR-009**: 저장된 과거 대화에는 이미지 원본이나 복원 가능한 썸네일을 표시하지 않아야 한다.
## 주요 데이터
- **이미지 첨부**: 지원 형식, 인코딩된 본문, 원본 크기로 이루어진 일회성 입력이다.
- **작성 중 질문**: 입력 텍스트, 접힌 텍스트 첨부, 이미지 첨부의 묶음이다.
## 성공 기준
- **SC-001**: 사용자가 캡쳐 또는 클립보드 이미지로 3번 이내의 동작을 거쳐 질문을 보낼 수 있다.
- **SC-002**: 텍스트 없이 지원 이미지 한 장만 첨부한 질문이 정상 처리된다.
- **SC-003**: 제한을 넘는 이미지는 서버 요청 전에 모두 차단되고 입력 손실이 없다.
- **SC-004**: 기존 텍스트 전용 채팅 테스트가 모두 통과한다.
## 예외 상황
- 같은 이미지가 여러 번 들어오면 각각 별도 첨부로 취급한다.
- 전송 중에는 새 첨부와 중복 전송을 막는다.
- 클립보드에 텍스트와 이미지가 함께 있으면 둘 다 작성 중 질문에 반영한다.
## 가정
- 서버는 이미지 원본을 저장하지 않으므로 새로고침 후 과거 이미지 미리보기는 복구하지 않는다.
- 화면 캡쳐는 현재처럼 새 대화 화면으로 이동해 첨부된다.
- 네트워크 요청이 시작된 뒤 발생한 서버 오류는 기존 채팅 오류 처리 흐름을 따른다.
+19
View File
@@ -0,0 +1,19 @@
# 단계 1: 이미지 입력 계약 연결
## 한 일
- 캡쳐와 클립보드 이미지를 같은 `SnapImageInput` 계약으로 묶음
- 새 대화 route state와 기존 대화 전송에 이미지 목록을 연결함
- 텍스트 없는 이미지-only 전송과 최대 4장 제한을 지원함
- PNG, JPEG, WebP 및 개별 5 MiB·전체 15 MiB 제한을 프론트에서 먼저 검사함
## 검증 결과
- 이미지 stream body, hook 전달, Composer 이미지-only/4장 제한 테스트 통과
- 전체 테스트 278개 통과
- lint 오류 0개(기존 경고 9개)
- production build 통과
## 다음
- 실제 backend와 붙인 화면 캡쳐·클립보드 수동 확인
+50
View File
@@ -0,0 +1,50 @@
# 작업: 채팅 이미지 입력
## Phase 1: 계약 기반
- [x] T001 `2_frontend/src/features/snap/contract/types.ts`에 이미지 입력과 stream 요청 계약 추가
- [x] T002 [P] `2_frontend/src/features/snap/api/snap.stream.test.ts`에 images 요청 전달 테스트 추가
## Phase 2: 사용자 스토리 1 - 캡쳐 이미지 전송
**독립 테스트**: 캡쳐 한 장만으로 새 세션을 만들고 이미지 요청을 전송함.
- [x] T003 [US1] `2_frontend/src/features/snap/components/Composer.test.tsx`에 캡쳐 이미지-only 전송과 제한 테스트 추가
- [x] T004 [US1] `2_frontend/src/features/snap/components/Composer.tsx`에 이미지 계약 변환·검증·전송 구현
- [x] T005 [US1] `2_frontend/src/features/snap/pages/NewChatPage.tsx``2_frontend/src/features/snap/pages/SessionChatPage.tsx`에 첫 이미지 전달 구현
- [x] T006 [US1] `2_frontend/src/features/snap/hooks/useSnapChat.test.tsx`에 images 요청 body 테스트 추가
- [x] T007 [US1] `2_frontend/src/features/snap/hooks/useSnapChat.ts`에 이미지 전송 연결
## Phase 3: 사용자 스토리 2 - 클립보드 이미지 전송
**독립 테스트**: 기존 대화에서 지원 이미지를 클립보드로 가져오면 썸네일과 함께 요청에 포함됨.
- [x] T008 [US2] `2_frontend/src/features/snap/components/Composer.test.tsx`에 클립보드 이미지 첨부 테스트 추가
- [x] T009 [US2] `2_frontend/src/features/snap/components/Composer.tsx`에서 클립보드 이미지를 질문 첨부로 누적
- [x] T010 [US2] `2_frontend/src/features/snap/pages/SessionChatPage.tsx`에서 기존 대화 이미지 입력 허용
## Phase 4: 사용자 스토리 3 - 제한과 입력 보존
**독립 테스트**: 형식·개수·크기 제한을 넘으면 요청 없이 안내하고 작성 내용을 유지함.
- [x] T011 [US3] `2_frontend/src/features/snap/components/Composer.test.tsx`에 형식·개수·크기·입력 보존 회귀 테스트 추가
- [x] T012 [US3] `2_frontend/src/features/snap/components/Composer.tsx`의 오류 안내와 경계 처리를 완성
## Phase 5: 마무리
- [x] T013 `specs/005-chat-image-input/stage-1.md`에 구현 및 단계 검증 결과 기록
- [x] T014 `2_frontend`에서 format, lint, test, build 실행
- [x] T015 `specs/005-chat-image-input/report.md`에 계획 대비 결과와 검증 기록
## 의존 관계
- T001 뒤에 T003~T012를 진행함.
- US1이 새 대화 전달 경로를 완성하고, US2는 같은 Composer 계약을 기존 대화까지 넓힘.
- US3은 US1·US2의 공통 입력 경계를 잠금.
## 구현 전략
- MVP는 US1의 캡쳐 이미지-only 전송임.
- 다음으로 기존 대화 클립보드 입력을 연결하고 마지막에 제한 회귀를 잠금.
- 모든 구현 작업은 대응 테스트 실패를 먼저 확인한 뒤 통과시킴.