v7:UI 보완_20260130

This commit is contained in:
2026-02-20 11:45:16 +09:00
parent 5129ee69d4
commit db6532b33c
3 changed files with 623 additions and 265 deletions

BIN
7th.zip Normal file

Binary file not shown.

170
README.md
View File

@@ -1,14 +1,14 @@
# 글벗 (Geulbeot) v6.0 # 글벗 (Geulbeot) v7.0
**HWPX 템플릿 분석·저장·관리** **UI 고도화 — 템플릿 관리·작성 방식·문서 유형 선택 UI**
다양한 형식의 자료(PDF·HWP·이미지·Excel 등)를 입력하면, AI가 RAG 파이프라인으로 분석한 뒤 다양한 형식의 자료(PDF·HWP·이미지·Excel 등)를 입력하면, AI가 RAG 파이프라인으로 분석한 뒤
선택한 문서 유형(기획서·보고서·발표자료 등)에 맞는 표준 HTML 문서를 자동 생성합니다. 선택한 문서 유형(기획서·보고서·발표자료 등)에 맞는 표준 HTML 문서를 자동 생성합니다.
생성된 문서는 웹 편집기에서 수정하고, HTML / PDF / HWP로 출력합니다. 생성된 문서는 웹 편집기에서 수정하고, HTML / PDF / HWP로 출력합니다.
v6에서는 HWPX 템플릿 관리 기능을 추가했습니다. v7에서는 프론트엔드 UI를 고도화했습니다.
HWPX 파일을 업로드하면 XML을 파싱하여 폰트·색상·여백·표 구조·테두리 등을 자동 분석하고, v6에서 백엔드로만 존재하던 템플릿 관리를 화면에서 직접 조작할 수 있게 되었고,
재사용 가능한 템플릿으로 저장합니다. 자료 활용 방식(형식 변경·재구성·신규 작성)과 문서 유형을 시각적으로 선택하는 UI가 추가되었습니다.
--- ---
@@ -20,17 +20,20 @@ HWPX 파일을 업로드하면 XML을 파싱하여 폰트·색상·여백·표
자료 입력 (파일/폴더) 자료 입력 (파일/폴더)
작성 방식 선택 ─── 형식만 변경 / 내용 재구성 / 신규 작성 (v7 신규)
RAG 파이프라인 (9단계) ─── 공통 처리 RAG 파이프라인 (9단계) ─── 공통 처리
문서 유형 선택 문서 유형 선택 ─── UI 리스트 (v7 신규)
├─ 기획서 (기본) ├─ 기획서 (기본)
├─ 보고서 (기본) ├─ 보고서 (기본)
├─ 발표자료 (기본) ├─ 발표자료 (기본)
└─ 사용자 등록 (확장 가능) └─ 사용자 등록 (확장 가능)
글벗 표준 HTML 생성 ◀── 템플릿 스타일 참조 (v6 신규) 글벗 표준 HTML 생성 ◀── 템플릿 스타일 참조 + 요소 선택 (v7 신규)
웹 편집기 (수기 편집 / AI 편집) 웹 편집기 (수기 편집 / AI 편집)
@@ -48,11 +51,11 @@ RAG 파이프라인 (9단계) ─── 공통 처리
- OpenAI API — RAG 임베딩, 인덱싱, 텍스트 추출 - OpenAI API — RAG 임베딩, 인덱싱, 텍스트 추출
- Gemini API — 보고서 콘텐츠·HTML 생성 - Gemini API — 보고서 콘텐츠·HTML 생성
- **Features**: - **Features**:
- 자료 입력 → 9단계 RAG 파이프라인 (파일 변환 → 추출 → 도메인 분석 → 청킹 → 임베딩 → 코퍼스 → 인덱싱 → 콘텐츠 생성 → HTML 조립) - 자료 입력 → 9단계 RAG 파이프라인
- 문서 유형별 생성: 기획서 (Claude 3단계), 보고서 (Gemini 2단계) - 문서 유형별 생성: 기획서 (Claude 3단계), 보고서 (Gemini 2단계)
- AI 편집: 전체 수정 (`/refine`), 부분 수정 (`/refine-selection`) - AI 편집: 전체 수정 (`/refine`), 부분 수정 (`/refine-selection`)
- HWPX 템플릿 분석·저장·관리 (v6 신규) - HWPX 템플릿 분석·저장·관리
- HWP 변환: 하이브리드 방식 — pyhwpx 기본 생성 → HWPX 스타일 주입 → 표 열 너비 수정 - HWP 변환: 하이브리드 방식 — pyhwpx → HWPX 스타일 주입 → 표 열 너비 수정
- PDF 변환: WeasyPrint 기반 - PDF 변환: WeasyPrint 기반
### 2. Frontend (순수 JavaScript) ### 2. Frontend (순수 JavaScript)
@@ -61,6 +64,9 @@ RAG 파이프라인 (9단계) ─── 공통 처리
- 웹 WYSIWYG 편집기 — 브라우저에서 생성된 문서 직접 수정 - 웹 WYSIWYG 편집기 — 브라우저에서 생성된 문서 직접 수정
- 페이지 넘김·들여쓰기·정렬 등 서식 도구 - 페이지 넘김·들여쓰기·정렬 등 서식 도구
- HTML / PDF / HWP 다운로드 - HTML / PDF / HWP 다운로드
- **작성 방식 선택 탭 (v7 신규)**: 📄 형식만 변경 / 🔄 내용의 재구성 / ✨ 문서 참고 신규 작성
- **문서 유형 선택 UI (v7 신규)**: 기획서·보고서 라디오 리스트 + 배지 스타일
- **템플릿 관리 UI (v7 신규)**: 사이드바에서 템플릿 업로드·선택·삭제, 적용할 요소 체크박스
### 3. 변환 엔진 (Converters) ### 3. 변환 엔진 (Converters)
@@ -68,25 +74,23 @@ RAG 파이프라인 (9단계) ─── 공통 처리
- **분량 자동 판단**: 5,000자 기준 — 긴 문서는 전체 파이프라인, 짧은 문서는 축약 파이프라인 - **분량 자동 판단**: 5,000자 기준 — 긴 문서는 전체 파이프라인, 짧은 문서는 축약 파이프라인
- **HWP 변환 (하이브리드 방식)**: HTML 분석 → pyhwpx 변환 → HWPX 스타일 주입 → 표 열 너비 수정 - **HWP 변환 (하이브리드 방식)**: HTML 분석 → pyhwpx 변환 → HWPX 스타일 주입 → 표 열 너비 수정
### 4. 템플릿 관리 (v6 신규) ### 4. 템플릿 관리
- **HWPX 파싱**: 업로드된 HWPX를 압축 해제하여 header.xml + section*.xml 구조 분석 - **HWPX 파싱**: 업로드된 HWPX를 압축 해제하여 header.xml + section*.xml 구조 분석
- **자동 추출 항목**: - **자동 추출**: 폰트·문단·표·배경·테두리·페이지 설정
- 폰트 정보 (이름·크기·굵기·색상) - **CSS 자동 생성**: 분석된 스타일 → CSS 변환
- 문단 스타일 (정렬·줄간격·들여쓰기·번호 체계) - **저장소**: `templates_store/` — meta.json + 원본 + 분석 결과
- 표 구조 (열 너비·행 수·셀 병합·테두리 스타일·선 종류) - **UI 연동 (v7 신규)**: 사이드바에서 목록 조회·선택·삭제, 요소별 적용 체크박스
- 배경 (색상·이미지 채우기)
- 테두리 (ARGB 8자리 색상 정규화, NONE 제외)
- 페이지 설정 (여백·용지 크기)
- **CSS 자동 생성**: 분석된 스타일을 CSS로 변환하여 HTML 생성 시 참조 가능
- **저장소**: `templates_store/` 디렉토리에 메타데이터(meta.json) + 원본 파일 + 분석 결과 저장
### 5. 주요 시나리오 (Core Scenarios) ### 5. 주요 시나리오 (Core Scenarios)
1. **기획서 생성**: 텍스트 또는 파일을 입력하면, RAG 분석 후 Claude API가 구조 추출 → 페이지 배치 계획 → 글벗 표준 HTML 기획서를 생성. 1~N페이지 옵션 지원 1. **기획서 생성**: 텍스트 또는 파일을 입력하면, RAG 분석 후 Claude API가 구조 추출 → 페이지 배치 계획 → 글벗 표준 HTML 기획서를 생성. 1~N페이지 옵션 지원
2. **보고서 생성**: 폴더 경로의 자료들을 RAG 파이프라인으로 분석하고, Gemini API가 섹션별 콘텐츠 초안 → 표지·목차·간지·별첨이 포함된 다페이지 HTML 보고서를 생성 2. **보고서 생성**: 폴더 경로의 자료들을 RAG 파이프라인으로 분석하고, Gemini API가 섹션별 콘텐츠 초안 → 표지·목차·간지·별첨이 포함된 다페이지 HTML 보고서를 생성
3. **AI 편집**: 생성된 문서를 웹 편집기에서 확인 후, 피드백으로 전체 또는 선택 부분을 AI가 수정 3. **작성 방식 선택 (v7 신규)**: 업로드 자료를 어떻게 활용할지 3가지 모드 중 선택
4. **템플릿 등록 (v6 신규)**: HWPX 파일을 업로드하면 XML을 파싱하여 폰트·표·테두리·색상 등을 자동 분석하고, 재사용 가능한 템플릿으로 저장. 등록된 템플릿은 조회·삭제 가능 - 📄 **형식만 변경** — 원본 내용 유지, 글벗 양식으로만 변환
- 🔄 **내용의 재구성** — 원본 기반으로 구조와 내용을 재구성 (기본값)
-**문서 참고 신규 작성** — 원본을 참고 자료로만 활용, 새로 작성
4. **템플릿 적용**: 등록된 HWPX 템플릿을 선택하고, 적용할 요소(제목 스타일·표 스타일·색상 등)를 체크박스로 선택
5. **HWP 내보내기**: pyhwpx 변환 후 HWPX 스타일 주입 + 표 열 너비 정밀 수정 5. **HWP 내보내기**: pyhwpx 변환 후 HWPX 스타일 주입 + 표 열 너비 정밀 수정
### 프로세스 플로우 ### 프로세스 플로우
@@ -119,87 +123,18 @@ flowchart TD
I --> J I --> J
``` ```
#### 문서 유형별 생성 → 편집 → 출력
```mermaid
flowchart TD
classDef decision fill:#fffde7,stroke:#f9a825,stroke-width:2px,color:#333
classDef aiClaude fill:#fff3cd,stroke:#d97706,stroke-width:2px,color:#856404
classDef aiGemini fill:#d6eaf8,stroke:#4285f4,stroke-width:2px,color:#1a4d8f
classDef editStyle fill:#fff3e0,stroke:#ef6c00,stroke-width:1.5px,color:#e65100
classDef exportStyle fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1.5px,color:#4a148c
classDef startEnd fill:#1a365d,stroke:#1a365d,color:#fff,stroke-width:2px
classDef planned fill:#f5f5f5,stroke:#999,stroke-width:1px,stroke-dasharray: 5 5,color:#999
classDef newModule fill:#e0f2f1,stroke:#00695c,stroke-width:2px,color:#004d40
A(["📋 RAG 분석 결과"]):::startEnd
B{"문서 유형 선택"}:::decision
C["기획서 생성\n구조추출→배치→HTML\n⚡ Claude API"]:::aiClaude
D["보고서 생성\n콘텐츠→HTML 조립\n⚡ Gemini API"]:::aiGemini
E["발표자료 생성\n예정"]:::planned
F["사용자 등록 유형\n확장 가능"]:::planned
T["📋 템플릿 스타일 참조\ntemplates_store/\n(v6 신규)"]:::newModule
G["글벗 표준 HTML\nA4·Navy·Noto Sans KR"]:::startEnd
H{"편집 방식"}:::decision
I["웹 편집기\n수기 편집"]:::editStyle
J["AI 편집\n전체·부분 수정\n⚡ Claude API"]:::aiClaude
K{"출력 형식"}:::decision
L["HTML / PDF"]:::exportStyle
M["HWP 변환 (하이브리드)\npyhwpx→스타일주입→표주입"]:::exportStyle
N["PPT 변환\n예정"]:::planned
O(["✅ 최종 산출물"]):::startEnd
A --> B
B -->|"기획서"| C --> G
B -->|"보고서"| D --> G
B -->|"발표자료"| E -.-> G
B -->|"확장"| F -.-> G
T -.->|"스타일 참조"| G
G --> H
H -->|"수기"| I --> K
H -->|"AI"| J --> K
K -->|"웹/인쇄"| L --> O
K -->|"HWP"| M --> O
K -->|"PPT"| N -.-> O
```
#### 템플릿 분석 (v6 신규)
```mermaid
flowchart LR
classDef process fill:#e8f4fd,stroke:#1a365d,stroke-width:1.5px,color:#1a365d
classDef newModule fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,color:#e65100
classDef dataStore fill:#e0f2f1,stroke:#00695c,stroke-width:1.5px,color:#004d40
classDef startEnd fill:#1a365d,stroke:#1a365d,color:#fff,stroke-width:2px
A(["📄 HWPX 업로드"]):::startEnd
B["압축 해제\nheader.xml\nsection*.xml"]:::process
C["XML 파싱\n폰트·표·테두리·색상\n페이지 설정"]:::newModule
D["CSS 자동 생성\n스타일 요약"]:::newModule
E[("📋 templates_store/\nmeta.json\n+ 분석 결과")]:::dataStore
A --> B --> C --> D --> E
```
--- ---
## 🔄 v5 → v6 변경사항 ## 🔄 v6 → v7 변경사항
| 영역 | v5 | v6 | | 영역 | v6 | v7 |
|------|------|------| |------|------|------|
| 템플릿 관리 | 없음 | **handlers/template/ 패키지 신규** | | 작성 방식 | 없음 (무조건 재구성) | **3가지 모드 UI**: 형식 변경 / 재구성 / 신규 작성 |
| HWPX 분석 | 없음 | header.xml·section.xml 파싱 (폰트·표·테두리·배경·페이지) | | 문서 유형 선택 | 기획서·보고서 구분 없이 탭 | **문서 유형 라디오 리스트** + 배지 스타일 |
| CSS 자동 생성 | 없음 | 분석된 스타일 → CSS 변환 | | 템플릿 관리 UI | API만 존재 (화면 없음) | **사이드바 UI**: 목록·선택·삭제 + 요소별 체크박스 |
| 신규 API | — | `GET /templates` · `POST /analyze-template` · `DELETE /delete-template/<id>` | | 템플릿 업로드 | API 직접 호출 | **모달 UI**: 파일 선택 + 이름 입력 + 업로드 |
| 저장소 | — | `templates_store/` (meta.json + 원본 + 분석 결과) | | index.html | 2,974줄 | 3,400줄 (+426) |
| AI 프롬프트 | — | `analyze_template.txt` — 구조 분석 보조 | | Python | 변경 없음 | 변경 없음 |
--- ---
@@ -210,8 +145,8 @@ flowchart LR
- **Phase 3**: 출력 — HTML/PDF 다운로드, HWP 변환 (🔧 기본 구현) - **Phase 3**: 출력 — HTML/PDF 다운로드, HWP 변환 (🔧 기본 구현)
- **Phase 4**: HWP/HWPX/HTML 매핑 — 스타일 분석·HWPX 생성·스타일 주입·표 주입 (🔧 기본 구현) - **Phase 4**: HWP/HWPX/HTML 매핑 — 스타일 분석·HWPX 생성·스타일 주입·표 주입 (🔧 기본 구현)
- **Phase 5**: 문서 유형 분석·등록 — HWPX 업로드 → AI 구조 분석 → 유형 CRUD + 확장 (예정) - **Phase 5**: 문서 유형 분석·등록 — HWPX 업로드 → AI 구조 분석 → 유형 CRUD + 확장 (예정)
- **Phase 6**: HWPX 템플릿 관리 — 파싱·스타일 추출·CSS 생성·저장·조회·삭제 (🔧 기본 구현 · 현재 버전) - **Phase 6**: HWPX 템플릿 관리 — 파싱·스타일 추출·CSS 생성·저장·조회·삭제 (🔧 기본 구현)
- **Phase 7**: UI 고도화 — 프론트 모듈화, 데모 모드, AI 편집 개선, 도메인 선택기 (예정) - **Phase 7**: UI 고도화 — 작성 방식 선택, 문서 유형 UI, 템플릿 관리 UI (🔧 기본 구현 · 현재 버전)
- **Phase 8**: 백엔드 재구조화 + 배포 — 패키지 정리, API 키 공통화, 로깅, Docker (예정) - **Phase 8**: 백엔드 재구조화 + 배포 — 패키지 정리, API 키 공통화, 로깅, Docker (예정)
--- ---
@@ -230,8 +165,8 @@ flowchart LR
```bash ```bash
# 저장소 클론 및 설정 # 저장소 클론 및 설정
git clone http://[Gitea주소]/kei/geulbeot-v6.git git clone http://[Gitea주소]/kei/geulbeot-v7.git
cd geulbeot-v6 cd geulbeot-v7
# 가상환경 # 가상환경
python -m venv venv python -m venv venv
@@ -265,7 +200,7 @@ python app.py
## 📂 프로젝트 구조 ## 📂 프로젝트 구조
``` ```
geulbeot_6th/ geulbeot_7th/
├── app.py # Flask 웹 서버 — API 라우팅 ├── app.py # Flask 웹 서버 — API 라우팅
├── api_config.py # .env 환경변수 로더 ├── api_config.py # .env 환경변수 로더
@@ -273,36 +208,31 @@ geulbeot_6th/
│ ├── common.py # Claude API 호출, JSON/HTML 추출 │ ├── common.py # Claude API 호출, JSON/HTML 추출
│ ├── briefing/ # 기획서 처리 (구조추출 → 배치 → HTML) │ ├── briefing/ # 기획서 처리 (구조추출 → 배치 → HTML)
│ ├── report/ # 보고서 처리 (RAG 파이프라인 연동) │ ├── report/ # 보고서 처리 (RAG 파이프라인 연동)
│ └── template/ # ★ v6 신규 — 템플릿 관리 │ └── template/ # 템플릿 관리 (HWPX 파싱·분석·CRUD)
│ ├── processor.py # HWPX 파싱·분석·CSS 생성·CRUD
│ └── prompts/
│ └── analyze_template.txt # AI 구조 분석 프롬프트
├── converters/ # 변환 엔진 ├── converters/ # 변환 엔진
│ ├── pipeline/ # 9단계 RAG 파이프라인 │ ├── pipeline/ # 9단계 RAG 파이프라인
│ │ ├── router.py # 분량 판단 (5,000자 기준)
│ │ └── step1 ~ step9 # 변환→추출→분석→청킹→임베딩→코퍼스→인덱싱→콘텐츠→HTML
│ ├── style_analyzer.py # HTML 요소 역할 분류 │ ├── style_analyzer.py # HTML 요소 역할 분류
│ ├── hwpx_generator.py # HWPX 파일 직접 생성 │ ├── hwpx_generator.py # HWPX 파일 직접 생성
│ ├── hwp_style_mapping.py # 역할 → HWP 스타일 매핑 │ ├── hwp_style_mapping.py # 역할 → HWP 스타일 매핑
│ ├── hwpx_style_injector.py # HWPX 커스텀 스타일 주입 │ ├── hwpx_style_injector.py # HWPX 커스텀 스타일 주입
│ ├── hwpx_table_injector.py # HWPX 표 열 너비 정밀 수정 │ ├── hwpx_table_injector.py # HWPX 표 열 너비 정밀 수정
│ ├── html_to_hwp.py # 보고서 → HWP 변환 (하이브리드 워크플로우) │ ├── html_to_hwp.py # 보고서 → HWP 변환
│ └── html_to_hwp_briefing.py # 기획서 → HWP 변환 │ └── html_to_hwp_briefing.py # 기획서 → HWP 변환
├── templates_store/ # ★ v6 신규 — 등록된 템플릿 저장소 ├── templates_store/ # 등록된 템플릿 저장소
├── static/ ├── static/
│ ├── js/editor.js # 웹 WYSIWYG 편집기 │ ├── js/editor.js # 웹 WYSIWYG 편집기
│ └── css/editor.css # 편집기 스타일 │ └── css/editor.css # 편집기 스타일
├── templates/ ├── templates/
│ ├── index.html # 메인 UI │ ├── index.html # ★ v7 고도화 — 작성 방식·문서 유형·템플릿 UI
│ └── hwp_guide.html # HWP 변환 가이드 │ └── hwp_guide.html # HWP 변환 가이드
├── .env / .env.sample # API 키 관리 ├── .env / .env.sample # API 키 관리
├── .gitignore ├── .gitignore
├── requirements.txt ├── requirements.txt
├── Procfile # 배포 설정 (Gunicorn) ├── Procfile
└── README.md └── README.md
``` ```
@@ -326,7 +256,8 @@ geulbeot_6th/
- API 키 분산: 파이프라인 각 step에 개별 정의 (공통화 미완) - API 키 분산: 파이프라인 각 step에 개별 정의 (공통화 미완)
- HWP 변환: Windows + pyhwpx + 한글 프로그램 필수 - HWP 변환: Windows + pyhwpx + 한글 프로그램 필수
- 문서 유형: 기획서·보고서만 구현, 발표자료·사용자 등록 유형 미구현 - 문서 유형: 기획서·보고서만 구현, 발표자료·사용자 등록 유형 미구현
- 템플릿 → 문서 생성 연동: 아직 미연 (분석·저장만 가능, 생성 시 자동 적용은 예정) - 작성 방식: UI만 구현, 백엔드 로직 미연 (모드별 프롬프트 분기 예정)
- 템플릿 → 문서 생성 연동: 아직 미연결 (선택·체크는 가능, 생성 시 자동 적용은 예정)
- 레거시 잔존: prompts/ 디렉토리 - 레거시 잔존: prompts/ 디렉토리
--- ---
@@ -335,9 +266,9 @@ geulbeot_6th/
| 영역 | 줄 수 | | 영역 | 줄 수 |
|------|-------| |------|-------|
| Python 전체 | 11,406 (+624) | | Python 전체 | 11,500 |
| 프론트엔드 (JS + CSS + HTML) | 3,859 | | 프론트엔드 (JS + CSS + HTML) | 4,904 (+1,045) |
| **합계** | **~15,300** | | **합계** | **~16,400** |
--- ---
@@ -350,7 +281,8 @@ geulbeot_6th/
| v3 | 9단계 RAG 파이프라인 + HWP 변환 | | v3 | 9단계 RAG 파이프라인 + HWP 변환 |
| v4 | 코드 모듈화 (handlers 패키지) + 스타일 분석기·HWPX 생성기 | | v4 | 코드 모듈화 (handlers 패키지) + 스타일 분석기·HWPX 생성기 |
| v5 | HWPX 스타일 주입 + 표 열 너비 정밀 변환 | | v5 | HWPX 스타일 주입 + 표 열 너비 정밀 변환 |
| **v6** | **HWPX 템플릿 분석·저장·관리** | | v6 | HWPX 템플릿 분석·저장·관리 |
| **v7** | **UI 고도화 — 작성 방식·문서 유형·템플릿 관리 UI** |
--- ---

View File

@@ -303,6 +303,103 @@
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
} }
/* 템플릿 리스트 */
.template-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 10px;
}
.template-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: var(--ui-panel);
border: 1px solid var(--ui-border);
border-radius: 6px;
cursor: pointer;
transition: all 0.15s;
}
.template-item:hover {
background: var(--ui-hover);
border-color: var(--ui-dim);
}
.template-item.selected {
border-color: var(--ui-accent);
background: rgba(0, 200, 83, 0.1);
}
.template-item input[type="radio"] {
accent-color: var(--ui-accent);
}
.template-item .label {
flex: 1;
font-size: 13px;
font-weight: 500;
}
.template-item .delete-btn {
opacity: 0;
background: transparent;
border: none;
color: var(--ui-dim);
cursor: pointer;
font-size: 14px;
}
.template-item:hover .delete-btn {
opacity: 1;
}
.template-item .delete-btn:hover {
color: var(--ui-error);
}
/* 템플릿 요소 체크박스 */
.template-elements {
margin-top: 12px;
padding: 12px;
background: var(--ui-bg);
border: 1px solid var(--ui-border);
border-radius: 6px;
}
.elements-title {
font-size: 10px;
font-weight: 600;
color: var(--ui-dim);
margin-bottom: 8px;
text-transform: uppercase;
}
.elements-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.element-checkbox {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--ui-text);
cursor: pointer;
}
.element-checkbox input[type="checkbox"] {
accent-color: var(--ui-accent);
}
.element-checkbox .element-icon {
font-size: 14px;
}
/* 사용자 템플릿 프리뷰 */ /* 사용자 템플릿 프리뷰 */
.user-template-preview { .user-template-preview {
display: none; display: none;
@@ -573,6 +670,71 @@
color: var(--ui-accent); color: var(--ui-accent);
} }
/* 작성 방식 선택 (가로 탭) */
.write-mode-box {
background: var(--ui-panel);
border: 1px solid var(--ui-border);
border-radius: 8px;
padding: 10px;
}
.write-mode-tabs {
display: flex;
gap: 4px;
}
.write-mode-tab {
flex: 1;
padding: 10px 8px;
border: 1px solid var(--ui-border);
border-radius: 6px;
background: transparent;
cursor: pointer;
transition: all 0.15s;
text-align: center;
}
.write-mode-tab:hover {
background: var(--ui-hover);
border-color: var(--ui-dim);
}
.write-mode-tab.selected {
background: rgba(0, 200, 83, 0.15);
border-color: var(--ui-accent);
}
.write-mode-tab input[type="radio"] {
display: none;
}
.write-mode-icon {
font-size: 16px;
margin-bottom: 4px;
}
.write-mode-label {
font-size: 11px;
font-weight: 600;
color: var(--ui-text);
}
.write-mode-tab.selected .write-mode-label {
color: var(--ui-accent);
}
.write-mode-notice {
margin-top: 8px;
padding: 6px 8px;
background: rgba(255, 152, 0, 0.1);
border-radius: 4px;
font-size: 10px;
color: var(--ui-warning);
display: flex;
align-items: center;
gap: 5px;
}
/* 진행 상태 */ /* 진행 상태 */
.step-list { .step-list {
display: flex; display: flex;
@@ -756,6 +918,45 @@
align-items: center; align-items: center;
gap: 12px; gap: 12px;
} }
/* 목차 확인 액션바 */
.toc-action-bar {
background: var(--ui-panel);
border-top: 1px solid var(--ui-border);
padding: 12px 20px;
display: none;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
.toc-action-bar.show { display: flex; }
.toc-action-btn {
padding: 10px 24px;
border-radius: 6px;
border: none;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
}
.toc-action-btn.primary {
background: var(--ui-accent);
color: #003300;
}
.toc-action-btn.primary:hover {
background: #00e676;
}
.toc-action-btn.secondary {
background: var(--ui-hover);
color: var(--ui-text);
border: 1px solid var(--ui-border);
}
.toc-action-btn.secondary:hover {
border-color: var(--ui-accent);
color: var(--ui-accent);
}
.feedback-bar.show { display: flex; } .feedback-bar.show { display: flex; }
.feedback-input { .feedback-input {
@@ -1437,14 +1638,14 @@
<!-- 좌측 사이드바: 입력 --> <!-- 좌측 사이드바: 입력 -->
<div class="sidebar"> <div class="sidebar">
<div class="sidebar-header"> <div class="sidebar-header">
<div class="sidebar-title">입력</div> <div class="sidebar-title">문서 업로드</div>
<button class="sidebar-btn" onclick="openFolderModal()"> <button class="sidebar-btn" onclick="openFolderModal()">
<span class="icon">📁</span> <span class="icon">📁</span>
<span>폴더 위치</span> <span>폴더 위치</span>
</button> </button>
<button class="sidebar-btn" onclick="openLinkModal()"> <button class="sidebar-btn" onclick="openLinkModal()">
<span class="icon">🔗</span> <span class="icon">🔗</span>
<span>참고 링크</span> <span>외부 링크</span>
</button> </button>
<button class="sidebar-btn" onclick="openHtmlModal()"> <button class="sidebar-btn" onclick="openHtmlModal()">
<span class="icon">📋</span> <span class="icon">📋</span>
@@ -1456,7 +1657,7 @@
<div class="sidebar-content"> <div class="sidebar-content">
<!-- 참고 파일 확인 --> <!-- 참고 파일 확인 -->
<div> <div>
<div class="section-title">참고 파일 확인</div> <div class="section-title">업로드 파일 검토</div>
<div class="file-check-box"> <div class="file-check-box">
<div class="file-path empty" id="folderPathDisplay">폴더 경로가 설정되지 않음</div> <div class="file-path empty" id="folderPathDisplay">폴더 경로가 설정되지 않음</div>
<div class="file-check-row"> <div class="file-check-row">
@@ -1489,10 +1690,33 @@
</div> </div>
</div> </div>
<!-- 초안 생성 버튼 --> <!-- 작성 방식 -->
<button class="sidebar-generate-btn" id="generateBtnSide" onclick="generate()" disabled> <div>
🚀 초안 생성하기 <div class="section-title">업로드 자료 활용 범위</div>
</button> <div class="write-mode-box">
<div class="write-mode-tabs">
<label class="write-mode-tab" onclick="selectWriteMode('format')">
<input type="radio" name="writeMode" value="format">
<div class="write-mode-icon">📄</div>
<div class="write-mode-label">형식만 <br>변경</div>
</label>
<label class="write-mode-tab selected" onclick="selectWriteMode('restructure')">
<input type="radio" name="writeMode" value="restructure" checked>
<div class="write-mode-icon">🔄</div>
<div class="write-mode-label">내용의<br>재구성</div>
</label>
<label class="write-mode-tab" onclick="selectWriteMode('new')">
<input type="radio" name="writeMode" value="new">
<div class="write-mode-icon"></div>
<div class="write-mode-label">문서 참고 <br> 신규 작성</div>
</label>
</div>
<div class="write-mode-notice">
<span>⚠️</span>
<span>문서 유형에 적합하지 않은 콘텐츠 업로드시,<br> 자동 재구성 및 신규 콘텐츠 작성 </span>
</div>
</div>
</div>
<!-- 진행 상태 --> <!-- 진행 상태 -->
<div> <div>
@@ -1561,6 +1785,12 @@
</div> </div>
</div> </div>
<!-- 목차 확인 액션바 -->
<div class="toc-action-bar" id="tocActionBar">
<button class="toc-action-btn secondary" onclick="editToc()">✏️ 편집</button>
<button class="toc-action-btn primary" id="approveBtn" onclick="approveToc()">✅ 승인 & 생성하기</button>
</div>
<!-- 하단 피드백 바 --> <!-- 하단 피드백 바 -->
<div class="feedback-bar" id="feedbackBar"> <div class="feedback-bar" id="feedbackBar">
<input type="text" class="feedback-input" id="feedbackInput" placeholder="수정 요청사항을 입력하세요... (예: 5 Step을 첨부로 이동해줘)"> <input type="text" class="feedback-input" id="feedbackInput" placeholder="수정 요청사항을 입력하세요... (예: 5 Step을 첨부로 이동해줘)">
@@ -1577,9 +1807,8 @@
<div class="panel-header"> <div class="panel-header">
<span class="panel-title">문서 설정</span> <span class="panel-title">문서 설정</span>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<!-- 문서 유형 선택 --> <!-- ===== 문서 유형 ===== -->
<div class="option-section"> <div class="option-section">
<div class="option-title">문서 유형</div> <div class="option-title">문서 유형</div>
<div class="doc-type-list"> <div class="doc-type-list">
@@ -1587,65 +1816,13 @@
<div class="doc-type-item selected" data-type="briefing" onclick="selectDocType('briefing')"> <div class="doc-type-item selected" data-type="briefing" onclick="selectDocType('briefing')">
<input type="radio" name="docType" checked> <input type="radio" name="docType" checked>
<span class="label">📋 기획서</span> <span class="label">📋 기획서</span>
<!-- 프리뷰 생략 -->
<!-- 플로팅 프리뷰 -->
<div class="doc-type-preview">
<div class="preview-thumbnail briefing">
<div class="page">
<div class="page-header"></div>
<div class="page-title"></div>
<div class="page-divider"></div>
<div class="page-lead"></div>
<div class="page-body"></div>
<div class="page-body" style="width:80%"></div>
<div class="page-body" style="width:90%"></div>
<div class="page-bottom"></div>
</div>
<div class="page">
<div class="page-header"></div>
<div class="page-attach">[첨부]</div>
<div class="page-body"></div>
<div class="page-body" style="width:85%"></div>
<div class="page-body"></div>
<div class="page-body" style="width:75%"></div>
<div class="page-bottom"></div>
</div>
</div>
<div class="preview-title">기획서 (보고자료)</div>
<div class="preview-desc">임원보고용 정형화된 1~2페이지 문서</div>
<div class="preview-features">
<div class="preview-feature"><span class="icon">📄</span> 1p 본문만 / 1p+1p첨부 / 1p+np첨부</div>
<div class="preview-feature"><span class="icon">🎨</span> Navy 양식 (A4 인쇄 최적화)</div>
<div class="preview-feature"><span class="icon">✍️</span> 개조식 자동 변환</div>
</div>
</div>
</div> </div>
<!-- 보고서 --> <!-- 보고서 -->
<div class="doc-type-item" data-type="report" onclick="selectDocType('report')"> <div class="doc-type-item" data-type="report" onclick="selectDocType('report')">
<input type="radio" name="docType"> <input type="radio" name="docType">
<span class="label">📄 보고서</span> <span class="label">📄 보고서</span>
<div class="doc-type-preview">
<div class="preview-thumbnail report">
<div class="line h1"></div>
<div class="line body"></div>
<div class="line body" style="width:90%"></div>
<div class="line body" style="width:85%"></div>
<div class="line h2"></div>
<div class="line body"></div>
<div class="line body" style="width:92%"></div>
<div class="line h2"></div>
<div class="line body" style="width:88%"></div>
</div>
<div class="preview-title">보고서 (HWP)</div>
<div class="preview-desc">RAG 기반 장문 보고서 → HWPX 출력</div>
<div class="preview-features">
<div class="preview-feature"><span class="icon">🏷️</span> AI 스타일 자동 태깅</div>
<div class="preview-feature"><span class="icon">📝</span> 대제목/중제목/소제목/본문</div>
<div class="preview-feature"><span class="icon"></span> 한글에서 스타일 일괄 변경</div>
</div>
</div>
</div> </div>
<!-- 발표자료 --> <!-- 발표자료 -->
@@ -1653,73 +1830,35 @@
<input type="radio" name="docType" disabled> <input type="radio" name="docType" disabled>
<span class="label">📊 발표자료</span> <span class="label">📊 발표자료</span>
<span class="badge">준비중</span> <span class="badge">준비중</span>
<div class="doc-type-preview">
<div class="preview-thumbnail ppt">
<div class="slide">
<div class="slide-title">제목</div>
<div class="slide-body"></div>
<div class="slide-body"></div>
</div>
<div class="slide">
<div class="slide-title">본문</div>
<div class="slide-body"></div>
<div class="slide-body"></div>
<div class="slide-body"></div>
</div>
<div class="slide">
<div class="slide-title">결론</div>
<div class="slide-body"></div>
</div>
</div>
<div class="preview-title">발표자료 (PPT)</div>
<div class="preview-desc">프레젠테이션 형식 슬라이드</div>
<div class="preview-features">
<div class="preview-feature"><span class="icon">📊</span> 슬라이드 자동 구성</div>
<div class="preview-feature"><span class="icon">🎯</span> 핵심 내용 추출</div>
<div class="preview-feature"><span class="icon">🖼️</span> 도식화 자동 생성</div>
</div>
</div>
</div> </div>
<!-- 사용자 추가 문서 유형 (동적) -->
<div id="customDocTypesArea"></div>
</div> </div>
<div id="userTemplatesArea" style="display: none;"> <!-- 문서 유형 추가 버튼 -->
<div class="template-divider"></div> <button class="add-template-btn" onclick="openDocTypeModal()">+ 문서 유형 추가</button>
<div class="user-templates-section" id="userTemplatesList">
<!-- 동적으로 추가됨 -->
</div>
</div>
<!-- 템플릿 추가 버튼 -->
<button class="add-template-btn" onclick="openTemplateModal()">+ 템플릿 추가</button>
</div> </div>
<!-- 기획서 옵션 --> <!-- ===== 문서 유형별 옵션 (기존 유지) ===== -->
<div id="briefingOptions"> <div id="briefingOptions">
<!-- 페이지 구성 -->
<div class="option-section"> <div class="option-section">
<div class="option-title">페이지 구성</div> <div class="option-title">페이지 구성</div>
<div class="option-group"> <div class="option-group">
<div class="option-item" onclick="selectPageOption('1')"> <div class="option-item" onclick="selectPageOption('1')">
<input type="radio" name="pages" value="1" id="page1"> <input type="radio" name="pages" value="1" id="page1">
<label for="page1"> (본문) 1p</label> <label for="page1">(본문) 1p</label>
</div> </div>
<div class="option-item selected" onclick="selectPageOption('2')"> <div class="option-item selected" onclick="selectPageOption('2')">
<input type="radio" name="pages" value="2" id="page2" checked> <input type="radio" name="pages" value="2" id="page2" checked>
<label for="page2"> (본문) 1p + (첨부) 1p</label> <label for="page2">(본문) 1p + (첨부) 1p</label>
</div> </div>
<div class="option-item" onclick="selectPageOption('n')"> <div class="option-item" onclick="selectPageOption('n')">
<input type="radio" name="pages" value="n" id="pageN"> <input type="radio" name="pages" value="n" id="pageN">
<label for="pageN"> (본문) 1p + (첨부) np</label> <label for="pageN">(본문) 1p + (첨부) np</label>
</div> </div>
</div> </div>
</div> </div>
<!-- 요청사항 -->
<div class="option-section">
<div class="option-title">요청사항</div>
<textarea class="request-textarea" id="instructionInput" placeholder="예: 세무 리스크 중심으로 작성해줘&#10;예: 표를 더 상세하게 만들어줘"></textarea>
</div>
</div> </div>
<!-- 보고서 옵션 --> <!-- 보고서 옵션 -->
@@ -1746,28 +1885,50 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- 템플릿 옵션 (요청사항만) --> <div id="templateOptions" style="display:none;">
<div id="templateOptions" style="display:none;">
<div class="option-section">
<div class="option-title">요청사항</div>
<textarea class="request-textarea" id="templateInstructionInput" placeholder="예: 요약을 상세하게 작성해줘&#10;예: 특정 섹션 강조"></textarea>
</div>
</div>
<!-- 요청사항 -->
<div class="option-section"> <div class="option-section">
<div class="option-title">요청사항</div> <div class="option-title">요청사항</div>
<textarea class="request-textarea" id="reportInstructionInput" placeholder="예: 요약을 상세하게 작성해줘&#10;예: 표지에 로고 추가"></textarea> <textarea class="request-textarea" id="templateInstructionInput"
placeholder="예: 요약을 상세하게 작성해줘&#10;예: 특정 섹션 강조"></textarea>
</div> </div>
</div> </div>
<!-- ===== 템플릿 선택 (새로 추가) ===== -->
<div class="option-section">
<div class="option-title">템플릿</div>
<div class="template-list">
<!-- 기본 템플릿 -->
<div class="template-item selected" data-template="default" onclick="selectTemplate('default')">
<input type="radio" name="template" checked>
<span class="label">📄 기본 템플릿</span>
</div>
<!-- 사용자 템플릿 (동적) -->
<div id="userTemplatesListNew"></div>
</div>
<!-- 템플릿 추가 버튼 -->
<button class="add-template-btn" onclick="openTemplateModal()">+ 템플릿 추가</button>
<!-- 선택된 템플릿 옵션 (AI 분석 결과) -->
<div id="templateElementOptions" class="template-elements" style="display:none;">
<div class="elements-title">적용 요소</div>
<div class="elements-list">
<!-- 동적으로 채워짐 -->
</div>
</div>
</div>
<!-- ===== 요청사항 ===== -->
<div class="option-section">
<div class="option-title">요청사항</div>
<textarea class="request-textarea" id="globalInstructionInput"
placeholder="예: 표를 더 상세하게&#10;예: 전문 용어 풀어서 설명"></textarea>
</div>
<!-- 생성 버튼 --> <!-- ===== 생성 버튼 ===== -->
<button class="generate-btn" id="generateBtn" onclick="generate()" disabled> <button class="generate-btn" id="generateBtn" onclick="generate()" disabled>
<span id="generateBtnText">🚀 생성하기</span> <span id="generateBtnText">🚀 생성하기</span>
<div class="loading-spinner" id="generateSpinner" style="display:none;"></div> <div class="loading-spinner" id="generateSpinner" style="display:none;"></div>
@@ -1837,9 +1998,14 @@
let generatedHTML = ''; let generatedHTML = '';
let currentDocType = 'briefing'; let currentDocType = 'briefing';
let currentPageOption = '2'; let currentPageOption = '2';
let currentWriteMode = 'restructure'; // format, restructure, new
let currentZoom = 100; let currentZoom = 100;
let folderPath = ''; let folderPath = '';
let referenceLinks = []; let referenceLinks = [];
let customDocTypes = []; // [{id, name, structure, created_at}, ...]
let currentTemplate = 'default';
let templateElements = {}; // {templateId: {cover: true, divider: false, ...}}
// ===== AI 부분 수정 관련 ===== // ===== AI 부분 수정 관련 =====
let selectedText = ''; let selectedText = '';
@@ -2069,6 +2235,121 @@
document.getElementById('briefingOptions').style.display = isBriefing ? 'block' : 'none'; document.getElementById('briefingOptions').style.display = isBriefing ? 'block' : 'none';
document.getElementById('reportOptions').style.display = isReport ? 'block' : 'none'; document.getElementById('reportOptions').style.display = isReport ? 'block' : 'none';
document.getElementById('templateOptions').style.display = isTemplate ? 'block' : 'none'; document.getElementById('templateOptions').style.display = isTemplate ? 'block' : 'none';
const generateBtnText = document.getElementById('generateBtnText');
if (type === 'report' || type === 'presentation') {
generateBtnText.textContent = '📋 목차 확인하기';
} else {
generateBtnText.textContent = '🚀 생성하기';
}
}
// ===== 문서 유형 추가 모달 =====
function openDocTypeModal() {
document.getElementById('docTypeModal').classList.add('active');
}
function closeDocTypeModal() {
document.getElementById('docTypeModal').classList.remove('active');
}
// ===== 템플릿 선택 =====
function selectTemplate(templateId) {
currentTemplate = templateId;
// 모든 템플릿 선택 해제
document.querySelectorAll('.template-item').forEach(item => {
item.classList.remove('selected');
const radio = item.querySelector('input[type="radio"]');
if (radio) radio.checked = false;
});
// 선택한 템플릿 활성화
const selectedItem = document.querySelector(`.template-item[data-template="${templateId}"]`);
if (selectedItem) {
selectedItem.classList.add('selected');
const radio = selectedItem.querySelector('input[type="radio"]');
if (radio) radio.checked = true;
}
// 템플릿 요소 옵션 표시/숨김
const elementsPanel = document.getElementById('templateElementOptions');
if (templateId === 'default') {
elementsPanel.style.display = 'none';
} else {
showTemplateElements(templateId);
elementsPanel.style.display = 'block';
}
console.log('템플릿 선택:', templateId);
}
// ===== 템플릿 요소 표시 (AI 분석 결과) =====
function showTemplateElements(templateId) {
const template = userTemplates.find(t => t.id === templateId);
if (!template || !template.elements) return;
const container = document.querySelector('#templateElementOptions .elements-list');
container.innerHTML = template.elements.map(el => `
<label class="element-checkbox">
<input type="checkbox"
data-element="${el.type}"
${el.default ? 'checked' : ''}
onchange="toggleTemplateElement('${templateId}', '${el.type}', this.checked)">
<span class="element-icon">${el.icon}</span>
<span>${el.name}</span>
</label>
`).join('');
}
// ===== 템플릿 요소 토글 =====
function toggleTemplateElement(templateId, elementType, checked) {
if (!templateElements[templateId]) {
templateElements[templateId] = {};
}
templateElements[templateId][elementType] = checked;
console.log('템플릿 요소 변경:', templateId, elementType, checked);
}
// ===== 사용자 템플릿 렌더링 (수정) =====
function renderUserTemplates() {
const container = document.getElementById('userTemplatesListNew');
if (userTemplates.length === 0) {
container.innerHTML = '';
return;
}
container.innerHTML = userTemplates.map(tpl => `
<div class="template-item" data-template="${tpl.id}" onclick="selectTemplate('${tpl.id}')">
<input type="radio" name="template">
<span class="label">📑 ${tpl.name}</span>
<button class="delete-btn" onclick="event.stopPropagation(); deleteTemplate('${tpl.id}')" title="삭제">✕</button>
</div>
`).join('');
}
// ===== 작성 방식 선택 =====
function selectWriteMode(mode) {
currentWriteMode = mode;
// 모든 탭 선택 해제
document.querySelectorAll('.write-mode-tab').forEach(tab => {
tab.classList.remove('selected');
tab.querySelector('input[type="radio"]').checked = false;
});
// 선택한 탭 활성화
const selectedTab = document.querySelector(`.write-mode-tab input[value="${mode}"]`);
if (selectedTab) {
selectedTab.checked = true;
selectedTab.closest('.write-mode-tab').classList.add('selected');
}
console.log('작성 방식 선택:', mode);
} }
// ===== HWP 추출 ===== // ===== HWP 추출 =====
@@ -2351,7 +2632,6 @@
// 생성 버튼 활성화 (HTML 입력이 있거나, 폴더/링크가 있으면) // 생성 버튼 활성화 (HTML 입력이 있거나, 폴더/링크가 있으면)
const canGenerate = hasHtml || hasFolder || hasLinks; const canGenerate = hasHtml || hasFolder || hasLinks;
document.getElementById('generateBtnSide').disabled = !canGenerate;
document.getElementById('generateBtn').disabled = !canGenerate; document.getElementById('generateBtn').disabled = !canGenerate;
// Step 0 상태 업데이트 // Step 0 상태 업데이트
@@ -2503,11 +2783,118 @@
async function generate() { async function generate() {
if (currentDocType === 'briefing') { if (currentDocType === 'briefing') {
await generateBriefing(); await generateBriefing();
} else if (currentDocType === 'report') { } else if (currentDocType === 'report' || currentDocType === 'presentation') {
await generateReport(); await generateDraft(); // ← 목차 확인 플로우
} else if (currentDocType.startsWith('template_')) { } else if (currentDocType.startsWith('template_')) {
// ⭐ 사용자 템플릿: 보고서와 동일하게 처리 await generateBriefing(); // 템플릿은 바로 생성
}
}
// ===== 초안/목차 생성 (보고서/발표자료용) =====
async function generateDraft() {
if (!folderPath && !inputContent && referenceLinks.length === 0) {
alert('먼저 폴더 위치, 참고 링크, 또는 HTML을 입력해주세요.');
return;
}
const btn = document.getElementById('generateBtn');
const btnText = document.getElementById('generateBtnText');
const spinner = document.getElementById('generateSpinner');
btn.disabled = true;
btnText.textContent = '분석 중...';
spinner.style.display = 'block';
resetSteps();
updateStep(0, 'done');
setStatus('목차 생성 중...', true);
try {
// Step 1~7 실행
for (let i = 1; i <= 7; i++) {
updateStep(i, 'running');
await new Promise(r => setTimeout(r, 500));
updateStep(i, 'done');
}
// 목차 테이블 표시
document.getElementById('placeholder').style.display = 'none';
const frame = document.getElementById('previewFrame');
frame.classList.remove('active');
let tocContainer = document.getElementById('tocContainer');
if (!tocContainer) {
tocContainer = document.createElement('div');
tocContainer.id = 'tocContainer';
document.getElementById('a4Preview').appendChild(tocContainer);
}
const tableContent = document.getElementById('embedded-table-context');
if (tableContent) {
tocContainer.innerHTML = tableContent.innerHTML;
tocContainer.style.display = 'block';
}
// 액션바 표시
document.getElementById('tocActionBar').classList.add('show');
document.getElementById('feedbackBar').classList.remove('show');
setStatus('목차 생성 완료 - 확인 후 승인해주세요', true);
} catch (error) {
alert('목차 생성 오류: ' + error.message);
setStatus('오류 발생', false);
} finally {
btn.disabled = false;
btnText.textContent = '📋 목차 확인하기';
spinner.style.display = 'none';
}
}
// ===== 목차 편집 =====
function editToc() {
const tocContainer = document.getElementById('tocContainer');
if (tocContainer) {
tocContainer.contentEditable = true;
tocContainer.style.outline = '2px solid var(--ui-accent)';
}
setStatus('목차 편집 모드 - 직접 수정 가능합니다', true);
}
// ===== 목차 승인 & 최종 생성 =====
async function approveToc() {
const btn = document.getElementById('approveBtn');
btn.disabled = true;
btn.textContent = '⏳ 생성 중...';
document.getElementById('tocActionBar').classList.remove('show');
setStatus('최종 문서 생성 중...', true);
// 목차 컨테이너 숨기기
const tocContainer = document.getElementById('tocContainer');
if (tocContainer) {
tocContainer.style.display = 'none';
tocContainer.contentEditable = false;
}
try {
// Step 8~9 실행
for (let i = 8; i <= 9; i++) {
updateStep(i, 'running');
await new Promise(r => setTimeout(r, 800));
updateStep(i, 'done');
}
// TODO: 실제 API 호출 (generateReport 로직 활용)
// 지금은 generateReport() 호출
await generateReport(); await generateReport();
} catch (error) {
alert('문서 생성 오류: ' + error.message);
setStatus('오류 발생', false);
} finally {
btn.disabled = false;
btn.textContent = '✅ 승인 & 생성하기';
} }
} }
@@ -2522,10 +2909,8 @@
const btn = document.getElementById('generateBtn'); const btn = document.getElementById('generateBtn');
const btnText = document.getElementById('generateBtnText'); const btnText = document.getElementById('generateBtnText');
const spinner = document.getElementById('generateSpinner'); const spinner = document.getElementById('generateSpinner');
const sideBtn = document.getElementById('generateBtnSide');
btn.disabled = true; btn.disabled = true;
sideBtn.disabled = true;
btnText.textContent = '생성 중...'; btnText.textContent = '생성 중...';
spinner.style.display = 'block'; spinner.style.display = 'block';
resetSteps(); resetSteps();
@@ -2588,7 +2973,6 @@
} }
} finally { } finally {
btn.disabled = false; btn.disabled = false;
sideBtn.disabled = false;
btnText.textContent = '🚀 생성하기'; btnText.textContent = '🚀 생성하기';
spinner.style.display = 'none'; spinner.style.display = 'none';
} }
@@ -2971,5 +3355,47 @@
</div> </div>
</div> </div>
<div id="embedded-table-context" style="display:none;">
<!-- 목차 테이블 HTML (나중에 첨부파일 내용으로 대체) -->
<div style="padding: 15mm; font-family: 'Noto Sans KR', sans-serif;">
<div style="text-align:center; margin-bottom:20px;">
<div style="font-size:18pt; font-weight:800; color:#006400;">목차 확인</div>
<div style="font-size:10pt; color:#666; margin-top:5px;">체크박스로 포함/제외를 선택하세요</div>
</div>
<!-- 실제 목차 테이블은 API 응답으로 동적 생성 -->
<p style="color:#888; text-align:center;">목차 데이터 로딩 중...</p>
</div>
</div>
<!-- 문서 유형 추가 모달 -->
<div class="template-modal" id="docTypeModal">
<div class="template-modal-content">
<div class="template-modal-header">
<div class="template-modal-title">📁 문서 유형 추가</div>
<button class="template-modal-close" onclick="closeDocTypeModal()"></button>
</div>
<div class="template-input-group">
<label class="template-input-label">유형 이름</label>
<input type="text" class="template-name-input" id="docTypeNameInput"
placeholder="예: 제안서, 기술보고서, 회의록">
</div>
<div class="template-input-group">
<label class="template-input-label">샘플 문서 (선택)</label>
<div class="template-dropzone" id="docTypeDropzone">
<div class="template-dropzone-icon">📄</div>
<div class="template-dropzone-text">샘플 문서를 업로드하면 AI가 구조를 분석합니다</div>
<div class="template-dropzone-hint">HWPX, HWP, PDF, DOCX 지원</div>
</div>
</div>
<button class="template-submit-btn" id="docTypeSubmitBtn" onclick="submitDocType()">
<span id="docTypeSpinner" class="spinner"></span>
<span id="docTypeSubmitText">✨ 분석 및 추가</span>
</button>
</div>
</div>
</body> </body>
</html> </html>