📦 Initialize Geulbeot structure and merge Prompts & test projects
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "briefing",
|
||||
"name": "기획서",
|
||||
"icon": "📋",
|
||||
"description": "1~2페이지 분량의 임원 보고용 문서",
|
||||
"features": [
|
||||
{"icon": "📌", "text": "헤더 + 제목 블록"},
|
||||
{"icon": "💡", "text": "핵심 요약 (Lead Box)"},
|
||||
{"icon": "📊", "text": "본문 섹션 + 첨부"}
|
||||
],
|
||||
"thumbnailType": "briefing",
|
||||
"enabled": true,
|
||||
"isDefault": true,
|
||||
"order": 1,
|
||||
"options": {
|
||||
"pageConfig": {
|
||||
"type": "radio-with-input",
|
||||
"choices": [
|
||||
{"value": "body-only", "label": "(본문) 1p"},
|
||||
{"value": "body-attach", "label": "(본문) 1p + (첨부)", "hasInput": true, "inputSuffix": "p", "inputDefault": 1, "inputMin": 1, "inputMax": 10, "default": true}
|
||||
]
|
||||
}
|
||||
},
|
||||
"createdAt": "2024-01-01T00:00:00Z",
|
||||
"updatedAt": "2025-01-30T00:00:00Z"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"id": "presentation",
|
||||
"name": "발표자료",
|
||||
"icon": "📊",
|
||||
"description": "슬라이드 형식의 프레젠테이션",
|
||||
"features": [
|
||||
{"icon": "🎯", "text": "슬라이드 레이아웃"},
|
||||
{"icon": "📈", "text": "차트/다이어그램"},
|
||||
{"icon": "🎨", "text": "비주얼 중심 구성"}
|
||||
],
|
||||
"thumbnailType": "ppt",
|
||||
"enabled": false,
|
||||
"isDefault": true,
|
||||
"order": 3,
|
||||
"badge": "준비중",
|
||||
"options": {
|
||||
"slideCount": [
|
||||
{"value": "auto", "label": "자동 (내용 기반)", "default": true},
|
||||
{"value": "5", "label": "5장 이내"},
|
||||
{"value": "10", "label": "10장 이내"},
|
||||
{"value": "20", "label": "20장 이내"}
|
||||
]
|
||||
},
|
||||
"generateFlow": "draft-first",
|
||||
"createdAt": "2024-01-01T00:00:00Z",
|
||||
"updatedAt": "2025-01-30T00:00:00Z"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "report",
|
||||
"name": "보고서",
|
||||
"icon": "📄",
|
||||
"description": "다페이지 분량의 상세 보고서",
|
||||
"features": [
|
||||
{"icon": "📘", "text": "표지 (선택)"},
|
||||
{"icon": "📑", "text": "목차 자동 생성"},
|
||||
{"icon": "📝", "text": "챕터별 내지"}
|
||||
],
|
||||
"thumbnailType": "report",
|
||||
"enabled": true,
|
||||
"isDefault": true,
|
||||
"order": 2,
|
||||
"options": {
|
||||
"components": [
|
||||
{"id": "cover", "label": "표지", "icon": "📘", "default": true},
|
||||
{"id": "toc", "label": "목차", "icon": "📑", "default": true},
|
||||
{"id": "divider", "label": "간지", "icon": "📄", "default": false},
|
||||
{"id": "content", "label": "내지 (필수)", "icon": "📝", "default": true, "required": true}
|
||||
]
|
||||
},
|
||||
"generateFlow": "draft-first",
|
||||
"createdAt": "2024-01-01T00:00:00Z",
|
||||
"updatedAt": "2025-01-30T00:00:00Z"
|
||||
}
|
||||
302
03. Code/geulbeot_10th/templates/hwp_guide.md
Normal file
302
03. Code/geulbeot_10th/templates/hwp_guide.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# A4 HTML 문서 레이아웃 가이드
|
||||
> 이 가이드는 글벗 doc_template_analyzer가 HWPX에서 추출한 구조를
|
||||
> A4 규격 HTML template.html로 변환할 때 참조하는 레이아웃 규격입니다.
|
||||
>
|
||||
> ★ 이 파일의 값은 코드에 하드코딩하지 않습니다.
|
||||
> ★ doc_template_analyzer._build_css(), _build_full_html() 등에서 이 파일을 읽어 적용합니다.
|
||||
> ★ 색상, 폰트 등 스타일은 HWPX에서 추출한 값을 우선 사용하고, 없으면 이 가이드의 기본값을 사용합니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 페이지 규격 (Page Dimensions)
|
||||
|
||||
```yaml
|
||||
page:
|
||||
width: 210mm # A4 가로
|
||||
height: 297mm # A4 세로
|
||||
background: white
|
||||
boxSizing: border-box
|
||||
|
||||
margins:
|
||||
top: 20mm # 상단 여백 (머릿말 + 본문 시작)
|
||||
bottom: 20mm # 하단 여백 (꼬릿말 + 본문 끝)
|
||||
left: 20mm # 좌측 여백
|
||||
right: 20mm # 우측 여백
|
||||
|
||||
# 본문 가용 높이 = 297mm - 20mm(상) - 20mm(하) = 257mm ≈ 970px
|
||||
bodyMaxHeight: 970px
|
||||
```
|
||||
|
||||
## 2. HTML 골격 구조 (Page Structure)
|
||||
|
||||
각 페이지는 `.sheet` 클래스로 감싸며, 내부에 header/body/footer를 absolute로 배치합니다.
|
||||
|
||||
```html
|
||||
<!-- 페이지 단위 -->
|
||||
<div class="sheet">
|
||||
<!-- 머릿말: 상단 여백(20mm) 안, 위에서 10mm 지점 -->
|
||||
<div class="page-header">
|
||||
<!-- HWPX 머릿말 테이블 내용 -->
|
||||
</div>
|
||||
|
||||
<!-- 본문: 상하좌우 20mm 안쪽 -->
|
||||
<div class="body-content">
|
||||
<!-- 섹션 제목, 표, 개조식 등 -->
|
||||
</div>
|
||||
|
||||
<!-- 꼬릿말: 하단 여백(20mm) 안, 아래에서 10mm 지점 -->
|
||||
<div class="page-footer">
|
||||
<!-- HWPX 꼬릿말 테이블 내용 -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 3. 핵심 CSS 레이아웃 (Layout CSS)
|
||||
|
||||
### 3.1 용지 (.sheet)
|
||||
```css
|
||||
.sheet {
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
background: white;
|
||||
margin: 20px auto; /* 화면 미리보기용 */
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 인쇄 대응
|
||||
```css
|
||||
@media print {
|
||||
.sheet { margin: 0; break-after: page; box-shadow: none; }
|
||||
body { background: white; }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 머릿말 (.page-header)
|
||||
```css
|
||||
.page-header {
|
||||
position: absolute;
|
||||
top: 10mm; /* 상단 여백(20mm)의 중간 */
|
||||
left: 20mm;
|
||||
right: 20mm;
|
||||
font-size: 9pt;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
```
|
||||
- 머릿말이 **테이블 형태**인 경우: `<table>` 사용, 테두리 없음
|
||||
- HWPX에서 추출한 열 수와 셀 내용을 placeholder로 배치
|
||||
- 다중행 셀은 `<br>`로 줄바꿈
|
||||
|
||||
### 3.4 꼬릿말 (.page-footer)
|
||||
```css
|
||||
.page-footer {
|
||||
position: absolute;
|
||||
bottom: 10mm; /* 하단 여백(20mm)의 중간 */
|
||||
left: 20mm;
|
||||
right: 20mm;
|
||||
font-size: 9pt;
|
||||
color: #555;
|
||||
border-top: 1px solid #eee;
|
||||
padding-top: 5px;
|
||||
}
|
||||
```
|
||||
- 꼬릿말이 **테이블 형태**인 경우: `<table>` 사용, 테두리 없음
|
||||
- 2열 이상일 때 `display: flex; justify-content: space-between` 패턴도 가능
|
||||
- 페이지 번호는 별도 `<span class="pg-num">` 으로
|
||||
|
||||
### 3.5 본문 영역 (.body-content)
|
||||
```css
|
||||
.body-content {
|
||||
position: absolute;
|
||||
top: 20mm;
|
||||
left: 20mm;
|
||||
right: 20mm;
|
||||
bottom: 20mm; /* 또는 auto + JS 제어 */
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 타이포그래피 기본값 (Typography Defaults)
|
||||
|
||||
> HWPX에서 폰트/크기를 추출했으면 그 값을 사용합니다.
|
||||
> 추출 실패 시 아래 기본값을 적용합니다.
|
||||
|
||||
```yaml
|
||||
typography:
|
||||
fontFamily: "'Noto Sans KR', sans-serif"
|
||||
fontImport: "https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&display=swap"
|
||||
|
||||
body:
|
||||
fontSize: 12pt
|
||||
lineHeight: 1.6
|
||||
textAlign: justify
|
||||
wordBreak: keep-all # 한글 단어 중간 끊김 방지
|
||||
|
||||
heading:
|
||||
h1: { fontSize: 20pt, fontWeight: 900 }
|
||||
h2: { fontSize: 18pt, fontWeight: 700 }
|
||||
h3: { fontSize: 14pt, fontWeight: 700 }
|
||||
|
||||
headerFooter:
|
||||
fontSize: 9pt
|
||||
|
||||
table:
|
||||
fontSize: 9.5pt
|
||||
thFontSize: 9pt
|
||||
```
|
||||
|
||||
## 5. 표 스타일 기본값 (Table Defaults)
|
||||
|
||||
```yaml
|
||||
table:
|
||||
width: "100%"
|
||||
borderCollapse: collapse
|
||||
tableLayout: fixed # colgroup 비율 적용 시 fixed 필수
|
||||
borderTop: "2px solid" # 상단 굵은 선 (색상은 HWPX 추출)
|
||||
|
||||
th:
|
||||
fontWeight: 900
|
||||
textAlign: center
|
||||
verticalAlign: middle
|
||||
whiteSpace: nowrap # 헤더 셀은 한 줄 유지
|
||||
wordBreak: keep-all
|
||||
padding: "6px 5px"
|
||||
|
||||
td:
|
||||
textAlign: center
|
||||
verticalAlign: middle
|
||||
wordBreak: keep-all
|
||||
wordWrap: break-word
|
||||
padding: "6px 5px"
|
||||
border: "1px solid #ddd"
|
||||
```
|
||||
|
||||
## 6. 머릿말/꼬릿말 테이블 (Header/Footer Table)
|
||||
|
||||
머릿말/꼬릿말이 HWPX에서 테이블로 구성된 경우:
|
||||
|
||||
```yaml
|
||||
headerFooterTable:
|
||||
border: none # 테두리 없음
|
||||
width: "100%"
|
||||
fontSize: 9pt
|
||||
|
||||
# 열 역할 패턴 (HWPX에서 추출)
|
||||
# 보통 3열: [소속정보 | 빈칸/로고 | 작성자/날짜]
|
||||
# 또는 2열: [제목 | 페이지번호]
|
||||
|
||||
cellStyle:
|
||||
padding: "2px 5px"
|
||||
verticalAlign: middle
|
||||
border: none
|
||||
```
|
||||
|
||||
## 7. 개조식 (Bullet Style)
|
||||
|
||||
```yaml
|
||||
bulletList:
|
||||
marker: "·" # 한국 문서 기본 불릿
|
||||
className: "bullet-list"
|
||||
|
||||
css: |
|
||||
.bullet-list {
|
||||
list-style: none;
|
||||
padding-left: 15px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.bullet-list li::before {
|
||||
content: "· ";
|
||||
font-weight: bold;
|
||||
}
|
||||
.bullet-list li {
|
||||
margin-bottom: 3px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 색상 (Color Scheme)
|
||||
|
||||
> HWPX에서 추출한 색상을 CSS 변수로 주입합니다.
|
||||
> 추출 실패 시 아래 기본값을 사용합니다.
|
||||
|
||||
```yaml
|
||||
colors:
|
||||
# Navy 계열 (기본)
|
||||
primary: "#1a365d"
|
||||
accent: "#2c5282"
|
||||
lightBg: "#EBF4FF"
|
||||
|
||||
# 문서별 오버라이드 (HWPX 추출값)
|
||||
# doc_template_analyzer가 HWPX의 글자색/배경색을 분석하여
|
||||
# 이 값을 덮어씁니다.
|
||||
|
||||
css: |
|
||||
:root {
|
||||
--primary: #1a365d;
|
||||
--accent: #2c5282;
|
||||
--light-bg: #EBF4FF;
|
||||
--bg: #f5f5f5;
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 페이지 분할 규칙 (Page Break Rules)
|
||||
|
||||
```yaml
|
||||
pageBreak:
|
||||
# H1(대제목)에서만 강제 페이지 분할
|
||||
h1Break: true
|
||||
|
||||
# H2/H3이 페이지 하단에 홀로 남지 않도록
|
||||
orphanControl: true
|
||||
orphanMinSpace: 90px # 이 공간 미만이면 다음 페이지로
|
||||
|
||||
# 표/그림은 분할하지 않음
|
||||
atomicBlocks:
|
||||
- table
|
||||
- figure
|
||||
- ".highlight-box"
|
||||
|
||||
# break-inside: avoid 적용 대상
|
||||
avoidBreakInside:
|
||||
- table
|
||||
- figure
|
||||
- ".atomic-block"
|
||||
```
|
||||
|
||||
## 10. 배경 (Preview Background)
|
||||
|
||||
```yaml
|
||||
preview:
|
||||
bodyBackground: "#525659" # 회색 배경 위에 흰색 용지
|
||||
# 인쇄 시 배경 제거 (@media print)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ★ 사용 방법 (How doc_template_analyzer uses this guide)
|
||||
|
||||
1. `doc_template_analyzer._build_full_html()` 호출 시:
|
||||
- 이 가이드 파일을 읽음
|
||||
- HWPX에서 추출한 스타일(색상, 폰트, 크기)이 있으면 오버라이드
|
||||
- 없으면 가이드 기본값 사용
|
||||
|
||||
2. CSS 생성 순서:
|
||||
```
|
||||
가이드 기본값 → HWPX 추출 스타일 오버라이드 → CSS 변수로 통합
|
||||
```
|
||||
|
||||
3. HTML 구조:
|
||||
```
|
||||
가이드의 골격(.sheet > .page-header + .body-content + .page-footer)
|
||||
+ HWPX에서 추출한 placeholder 배치
|
||||
= template.html
|
||||
```
|
||||
|
||||
4. 색상 결정:
|
||||
```
|
||||
HWPX headerTextColor → --primary
|
||||
HWPX headerBgColor → --light-bg
|
||||
없으면 → 가이드 기본값(Navy 계열)
|
||||
```
|
||||
116
03. Code/geulbeot_10th/templates/hwp_html_defaults.json
Normal file
116
03. Code/geulbeot_10th/templates/hwp_html_defaults.json
Normal file
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"_comment": "A4 HTML 문서 레이아웃 기본값 - hwp_html_guide.md 참조. HWPX 추출값이 있으면 오버라이드됨",
|
||||
|
||||
"page": {
|
||||
"width": "210mm",
|
||||
"height": "297mm",
|
||||
"background": "white"
|
||||
},
|
||||
|
||||
"margins": {
|
||||
"top": "20mm",
|
||||
"bottom": "20mm",
|
||||
"left": "20mm",
|
||||
"right": "20mm"
|
||||
},
|
||||
|
||||
"headerPosition": {
|
||||
"top": "10mm",
|
||||
"left": "20mm",
|
||||
"right": "20mm"
|
||||
},
|
||||
|
||||
"footerPosition": {
|
||||
"bottom": "10mm",
|
||||
"left": "20mm",
|
||||
"right": "20mm"
|
||||
},
|
||||
|
||||
"bodyContent": {
|
||||
"top": "20mm",
|
||||
"left": "20mm",
|
||||
"right": "20mm",
|
||||
"bottom": "20mm"
|
||||
},
|
||||
|
||||
"bodyMaxHeight": "970px",
|
||||
|
||||
"preview": {
|
||||
"bodyBackground": "#f5f5f5",
|
||||
"sheetMargin": "20px auto",
|
||||
"sheetShadow": "0 0 15px rgba(0,0,0,0.1)"
|
||||
},
|
||||
|
||||
"typography": {
|
||||
"fontFamily": "'Noto Sans KR', sans-serif",
|
||||
"fontImport": "https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&display=swap",
|
||||
"body": {
|
||||
"fontSize": "10pt",
|
||||
"lineHeight": "1.6",
|
||||
"textAlign": "justify",
|
||||
"wordBreak": "keep-all"
|
||||
},
|
||||
"heading": {
|
||||
"h1": { "fontSize": "20pt", "fontWeight": "900" },
|
||||
"h2": { "fontSize": "16pt", "fontWeight": "700" },
|
||||
"h3": { "fontSize": "13pt", "fontWeight": "700" }
|
||||
},
|
||||
"headerFooter": {
|
||||
"fontSize": "9pt"
|
||||
}
|
||||
},
|
||||
|
||||
"colors": {
|
||||
"primary": "#1a365d",
|
||||
"accent": "#2c5282",
|
||||
"lightBg": "#EBF4FF",
|
||||
"text": "#000",
|
||||
"headerText": "#000",
|
||||
"footerText": "#555",
|
||||
"footerBorder": "#eee",
|
||||
"tableBorderTop": "#1a365d",
|
||||
"tableBorder": "#ddd",
|
||||
"tableHeaderBg": "#EBF4FF"
|
||||
},
|
||||
|
||||
"table": {
|
||||
"width": "100%",
|
||||
"borderCollapse": "collapse",
|
||||
"tableLayout": "fixed",
|
||||
"fontSize": "9.5pt",
|
||||
"th": {
|
||||
"fontSize": "9pt",
|
||||
"fontWeight": "900",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"whiteSpace": "nowrap",
|
||||
"padding": "6px 5px"
|
||||
},
|
||||
"td": {
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"wordBreak": "keep-all",
|
||||
"padding": "6px 5px"
|
||||
}
|
||||
},
|
||||
|
||||
"headerFooterTable": {
|
||||
"border": "none",
|
||||
"width": "100%",
|
||||
"fontSize": "9pt",
|
||||
"cellPadding": "2px 5px"
|
||||
},
|
||||
|
||||
"bulletList": {
|
||||
"marker": "·",
|
||||
"className": "bullet-list",
|
||||
"paddingLeft": "15px",
|
||||
"itemMargin": "3px 0"
|
||||
},
|
||||
|
||||
"pageBreak": {
|
||||
"h1Break": true,
|
||||
"orphanControl": true,
|
||||
"orphanMinSpace": "90px"
|
||||
}
|
||||
}
|
||||
782
03. Code/geulbeot_10th/templates/index.html
Normal file
782
03. Code/geulbeot_10th/templates/index.html
Normal file
@@ -0,0 +1,782 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>글벗 - AI 문서 자동화 시스템</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/editor.css">
|
||||
<link rel="stylesheet" href="/static/css/main.css">
|
||||
|
||||
<style>
|
||||
/* 목차 애니메이션 */
|
||||
.toc-display-area {
|
||||
padding: 20px 32px;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 200px);
|
||||
}
|
||||
.toc-anim-item {
|
||||
padding: 14px 18px;
|
||||
background: var(--ui-surface, #2a2f38);
|
||||
border: 1px solid var(--ui-border, #363a43);
|
||||
border-left: 3px solid var(--ui-accent, #4fc3f7);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.toc-anim-num {
|
||||
font-size: 12px;
|
||||
color: var(--ui-accent, #4fc3f7);
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.toc-anim-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--ui-text, #e8eaed);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.toc-anim-guide {
|
||||
font-size: 12px;
|
||||
color: var(--ui-dim, #9aa0a6);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.toc-anim-keywords {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.toc-anim-keyword {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
background: rgba(79, 195, 247, 0.1);
|
||||
color: var(--ui-accent, #4fc3f7);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.toc-anim-contents {
|
||||
margin-top: 8px;
|
||||
padding: 8px 10px;
|
||||
background: rgba(255,255,255,0.03);
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(79, 195, 247, 0.08);
|
||||
}
|
||||
.toc-anim-content-item {
|
||||
font-size: 11px;
|
||||
color: var(--ui-dim, #9aa0a6);
|
||||
line-height: 1.7;
|
||||
padding-left: 4px;
|
||||
}
|
||||
.toc-loading {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
}
|
||||
.toc-loading-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--ui-accent, #4fc3f7);
|
||||
border-radius: 50%;
|
||||
animation: tocDotPulse 1.2s infinite;
|
||||
}
|
||||
.toc-loading-dot:nth-child(2) { animation-delay: 0.2s; }
|
||||
.toc-loading-dot:nth-child(3) { animation-delay: 0.4s; }
|
||||
@keyframes tocDotPulse {
|
||||
0%, 60%, 100% { opacity: 0.3; transform: scale(0.8); }
|
||||
30% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
/* 생성 진행률 */
|
||||
.gen-progress-area {
|
||||
padding: 16px 32px;
|
||||
}
|
||||
.gen-progress-header {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--ui-text, #e8eaed);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.gen-progress-bar-wrap {
|
||||
height: 4px;
|
||||
background: var(--ui-border, #363a43);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.gen-progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--ui-accent, #4fc3f7), #66bb6a);
|
||||
border-radius: 2px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
.gen-progress-text {
|
||||
font-size: 12px;
|
||||
color: var(--ui-dim, #9aa0a6);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* AI 수정 버튼 활성 상태 */
|
||||
#aiEditToolbarBtn.has-selection {
|
||||
background: rgba(79, 195, 247, 0.15);
|
||||
color: var(--ui-accent, #4fc3f7);
|
||||
border-color: var(--ui-accent, #4fc3f7);
|
||||
}
|
||||
|
||||
/* ===== 도메인 선택 ===== */
|
||||
.domain-select-box {
|
||||
background: var(--ui-surface, #2a2f38);
|
||||
border: 1px solid var(--ui-border, #363a43);
|
||||
border-radius: 8px; padding: 10px 12px; cursor: pointer;
|
||||
}
|
||||
.domain-select-box:hover { border-color: var(--ui-accent, #4fc3f7); }
|
||||
.domain-display-text { font-size: 12px; color: var(--ui-text); margin-top: 6px; }
|
||||
.domain-display-text.auto { color: var(--ui-dim, #9aa0a6); font-style: italic; }
|
||||
.domain-display-text.selected { color: var(--ui-accent, #4fc3f7); }
|
||||
#domainModal {
|
||||
position: fixed; top:0; left:0; right:0; bottom:0;
|
||||
background: rgba(0,0,0,0.65); display: none;
|
||||
align-items: center; justify-content: center; z-index: 10000;
|
||||
}
|
||||
.domain-modal-content {
|
||||
background: var(--ui-bg, #1e2228); border: 1px solid var(--ui-border);
|
||||
border-radius: 14px; width: 560px; max-height: 80vh;
|
||||
display: flex; flex-direction: column; box-shadow: 0 20px 60px rgba(0,0,0,0.4);
|
||||
}
|
||||
.domain-modal-header { display: flex; align-items: center; justify-content: space-between; padding: 18px 24px; border-bottom: 1px solid var(--ui-border); }
|
||||
.domain-modal-title { font-size: 16px; font-weight: 700; color: var(--ui-text); }
|
||||
.domain-modal-close { background: none; border: none; color: var(--ui-dim); font-size: 20px; cursor: pointer; }
|
||||
.domain-modal-body { padding: 16px 24px; overflow-y: auto; flex: 1; }
|
||||
.domain-modal-desc { font-size: 13px; color: var(--ui-dim); margin-bottom: 16px; line-height: 1.6; }
|
||||
.domain-category { margin-bottom: 10px; }
|
||||
.domain-cat-label {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 12px 14px; background: var(--ui-surface, #2a2f38);
|
||||
border: 1px solid var(--ui-border); border-radius: 8px; cursor: pointer;
|
||||
}
|
||||
.domain-cat-label:hover { border-color: var(--ui-accent); }
|
||||
.domain-cat-label.checked { background: rgba(79,195,247,0.08); border-color: var(--ui-accent); }
|
||||
.domain-cat-label input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--ui-accent); }
|
||||
.domain-cat-icon { font-size: 20px; }
|
||||
.domain-cat-text { flex: 1; }
|
||||
.domain-cat-name { display: block; font-size: 14px; font-weight: 600; color: var(--ui-text); }
|
||||
.domain-cat-desc { display: block; font-size: 11px; color: var(--ui-dim); margin-top: 2px; }
|
||||
.domain-cat-expand { font-size: 10px; color: var(--ui-dim); transition: transform 0.2s; }
|
||||
.domain-cat-expand.open { transform: rotate(180deg); }
|
||||
.domain-sub-panel { padding: 10px 14px 10px 40px; border-left: 2px solid var(--ui-accent); margin-left: 20px; margin-top: 4px; }
|
||||
.domain-sub-group { margin-bottom: 10px; }
|
||||
.domain-sub-group-label { font-size: 11px; font-weight: 600; color: var(--ui-dim); margin-bottom: 6px; }
|
||||
.domain-sub-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.domain-chip {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 5px 12px; background: var(--ui-surface);
|
||||
border: 1px solid var(--ui-border); border-radius: 20px;
|
||||
font-size: 12px; color: var(--ui-text); cursor: pointer;
|
||||
}
|
||||
.domain-chip:hover { border-color: var(--ui-accent); }
|
||||
.domain-chip.selected { background: rgba(79,195,247,0.15); border-color: var(--ui-accent); color: var(--ui-accent); }
|
||||
.domain-chip input[type="checkbox"] { display: none; }
|
||||
.domain-modal-footer { padding: 14px 24px; border-top: 1px solid var(--ui-border); display: flex; align-items: center; justify-content: space-between; }
|
||||
.domain-summary-text { font-size: 12px; color: var(--ui-accent); flex: 1; }
|
||||
.domain-summary-text.empty { color: var(--ui-dim); font-style: italic; }
|
||||
.domain-modal-actions { display: flex; gap: 8px; }
|
||||
.domain-btn { padding: 8px 18px; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid var(--ui-border); background: var(--ui-surface); color: var(--ui-text); }
|
||||
.domain-btn.primary { background: var(--ui-accent); color: #000; border-color: var(--ui-accent); }
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- 상단 툴바 -->
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-logo">📝 글벗</div>
|
||||
|
||||
<div class="toolbar-spacer"></div>
|
||||
|
||||
<button class="toolbar-btn" id="editModeBtn" onclick="toggleEditMode()">✏️ 편집하기</button>
|
||||
<button class="toolbar-btn" id="aiEditToolbarBtn" onclick="triggerAiEdit()" title="AI 수정 (텍스트를 먼저 선택하세요)">🤖 AI 수정</button>
|
||||
|
||||
<div class="toolbar-divider"></div>
|
||||
|
||||
<select class="zoom-select" id="zoomSelect" onchange="setZoom(this.value)">
|
||||
<option value="50">50%</option>
|
||||
<option value="75">75%</option>
|
||||
<option value="100" selected>100%</option>
|
||||
<option value="125">125%</option>
|
||||
<option value="150">150%</option>
|
||||
</select>
|
||||
|
||||
<div class="toolbar-divider"></div>
|
||||
|
||||
<button class="toolbar-btn" onclick="exportHwp()">📄 HWP 추출</button>
|
||||
<button class="toolbar-btn" onclick="saveHtml()">💾 HTML 저장</button>
|
||||
<button class="toolbar-btn" onclick="saveHtml()">📊 PPT 저장</button>
|
||||
<button class="toolbar-btn" onclick="printDoc()">🖨️ PDF/인쇄</button>
|
||||
</div>
|
||||
|
||||
<!-- 메인 앱 -->
|
||||
<div class="app">
|
||||
<!-- 좌측 사이드바: 입력 -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="sidebar-title">문서 업로드</div>
|
||||
<button class="sidebar-btn" onclick="openFolderModal()">
|
||||
<span class="icon">📁</span>
|
||||
<span>폴더 위치</span>
|
||||
</button>
|
||||
<button class="sidebar-btn" onclick="openLinkModal()">
|
||||
<span class="icon">🔗</span>
|
||||
<span>외부 링크</span>
|
||||
</button>
|
||||
<button class="sidebar-btn" onclick="openHtmlModal()">
|
||||
<span class="icon">📋</span>
|
||||
<span>HTML 붙여넣기</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-content">
|
||||
<!-- 참고 파일 확인 -->
|
||||
<div>
|
||||
<div class="section-title">업로드 파일 검토</div>
|
||||
<div class="file-check-box">
|
||||
<div class="file-path empty" id="folderPathDisplay">폴더 경로가 설정되지 않음</div>
|
||||
<div class="file-check-row">
|
||||
<span class="file-check-label">전체 파일</span>
|
||||
<span class="file-check-value" id="totalCount">0개</span>
|
||||
</div>
|
||||
<div class="file-check-row">
|
||||
<span class="file-check-label">확인 (변환 가능)</span>
|
||||
<span class="file-check-value ok" id="okCount">0개 ✓</span>
|
||||
</div>
|
||||
<div class="file-check-row">
|
||||
<span class="file-check-label">미확인</span>
|
||||
<span class="file-check-value warn" id="unknownCount" onclick="toggleUnknownFiles()">0개</span>
|
||||
</div>
|
||||
|
||||
<div class="unknown-files" id="unknownFilesBox">
|
||||
<div id="unknownFilesList"></div>
|
||||
<button class="open-folder-btn" onclick="openFolder()">📂 폴더 열기</button>
|
||||
</div>
|
||||
|
||||
<div class="file-check-row" style="border-top: 1px solid var(--ui-border); margin-top: 8px; padding-top: 8px;">
|
||||
<span class="file-check-label">참고 링크</span>
|
||||
<span class="file-check-value" id="linkCount">0개</span>
|
||||
</div>
|
||||
|
||||
<div class="file-check-row">
|
||||
<span class="file-check-label">HTML 입력</span>
|
||||
<span class="file-check-value" id="htmlInputStatus">없음</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 도메인 지식 선택 -->
|
||||
<div id="domainSection" style="display:none;">
|
||||
<div class="section-title">도메인 지식 선택</div>
|
||||
<div class="domain-select-box" onclick="openDomainModal()">
|
||||
<div style="display:flex; justify-content:space-between; font-size:12px; color:var(--ui-dim);">
|
||||
<span>📚 업로드 자료의 성격을 선택해주세요</span>
|
||||
<span>▶</span>
|
||||
</div>
|
||||
<div class="domain-display-text auto" id="domainDisplayText">자동 분석 (AI 판단)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 작성 방식 -->
|
||||
<div>
|
||||
<div class="section-title">업로드 자료 활용 범위</div>
|
||||
<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 class="section-title">진행 상태</div>
|
||||
<div class="step-list" id="stepList">
|
||||
<div class="step-item pending" data-step="0">
|
||||
<span class="status">○</span>
|
||||
<span>참고 파일 확인</span>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<div class="step-item pending" data-step="1">
|
||||
<span class="status">○</span>
|
||||
<span>Step 1: 파일 변환</span>
|
||||
</div>
|
||||
<div class="step-item pending" data-step="2">
|
||||
<span class="status">○</span>
|
||||
<span>Step 2: 텍스트 추출</span>
|
||||
</div>
|
||||
<div class="step-item pending" data-step="3">
|
||||
<span class="status">○</span>
|
||||
<span>Step 3: 도메인 분석</span>
|
||||
</div>
|
||||
<div class="step-item pending" data-step="4">
|
||||
<span class="status">○</span>
|
||||
<span>Step 4: 청킹/요약</span>
|
||||
</div>
|
||||
<div class="step-item pending" data-step="5">
|
||||
<span class="status">○</span>
|
||||
<span>Step 5: RAG 구축</span>
|
||||
</div>
|
||||
<div class="step-item pending" data-step="6">
|
||||
<span class="status">○</span>
|
||||
<span>Step 6: Corpus 생성</span>
|
||||
</div>
|
||||
<div class="step-item pending" data-step="7">
|
||||
<span class="status">○</span>
|
||||
<span>Step 7: 목차 구성</span>
|
||||
</div>
|
||||
<div class="step-divider"></div>
|
||||
<div class="step-item pending" data-step="8">
|
||||
<span class="status">○</span>
|
||||
<span>Step 8: 콘텐츠 생성</span>
|
||||
</div>
|
||||
<div class="step-item pending" data-step="9">
|
||||
<span class="status">○</span>
|
||||
<span>Step 9: HTML 변환</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 가운데 뷰어 -->
|
||||
<div class="main">
|
||||
<div class="viewer" id="viewer">
|
||||
<div class="a4-wrapper" id="a4Wrapper">
|
||||
<div class="a4-preview" id="a4Preview">
|
||||
<iframe id="previewFrame" class="preview-iframe"></iframe>
|
||||
<div class="placeholder" id="placeholder">
|
||||
<div class="icon">📄</div>
|
||||
<div class="text">HTML을 입력하고 생성하세요</div>
|
||||
<div class="sub-text">좌측에서 HTML 붙여넣기 또는 파일 업로드</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 목차 애니메이션 표시 영역 -->
|
||||
<div class="toc-display-area" id="tocDisplayArea" style="display:none;"></div>
|
||||
|
||||
<!-- 생성 진행률 -->
|
||||
<div class="gen-progress-area" id="genProgressArea" style="display:none;">
|
||||
<div class="gen-progress-header">📝 문서 생성 중...</div>
|
||||
<div class="gen-progress-bar-wrap">
|
||||
<div class="gen-progress-bar" id="genProgressBar" style="width:0%"></div>
|
||||
</div>
|
||||
<div class="gen-progress-text" id="genProgressText">준비 중...</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">
|
||||
<input type="text" class="feedback-input" id="feedbackInput" placeholder="수정 요청사항을 입력하세요... (예: 5 Step을 첨부로 이동해줘)">
|
||||
<button class="feedback-btn primary" id="feedbackBtn" onclick="submitFeedback()">
|
||||
<span id="feedbackBtnText">🔄 수정 반영</span>
|
||||
<span id="feedbackSpinner" class="spinner" style="display:none;"></span>
|
||||
</button>
|
||||
<button class="feedback-btn secondary" onclick="regenerate()">🗑️ 다시 생성</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 우측 패널: 옵션 -->
|
||||
<div class="right-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">문서 설정</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<!-- ===== 문서 유형 (동적 로드) ===== -->
|
||||
<div class="option-section">
|
||||
<div class="option-title">문서 유형</div>
|
||||
<div class="doc-type-list loading" id="docTypeList">
|
||||
<!-- API에서 동적으로 로드 -->
|
||||
</div>
|
||||
|
||||
<!-- 문서 유형 추가 버튼 -->
|
||||
<button class="add-template-btn" onclick="openDocTypeModal()">+ 문서 유형 추가</button>
|
||||
</div>
|
||||
|
||||
<!-- ===== 문서 유형별 옵션 (동적) ===== -->
|
||||
<div id="docTypeOptionsContainer">
|
||||
<!-- selectDocType() 호출 시 동적으로 렌더링 -->
|
||||
</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>
|
||||
|
||||
<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="예: 표를 더 상세하게 예: 전문 용어 풀어서 설명"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- ===== 생성 버튼 ===== -->
|
||||
<button class="generate-btn" id="generateBtn" onclick="generate()" disabled>
|
||||
<span id="generateBtnText">🚀 생성하기</span>
|
||||
<div class="loading-spinner" id="generateSpinner" style="display:none;"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 상태바 -->
|
||||
<div class="status-bar">
|
||||
<div class="status-left">
|
||||
<span class="status-dot" id="statusDot"></span>
|
||||
<span id="statusMessage">준비됨</span>
|
||||
</div>
|
||||
<div id="statusRight">글벗 Light v2.1</div>
|
||||
</div>
|
||||
|
||||
<!-- HTML 입력 모달 -->
|
||||
<div class="modal-overlay" id="htmlModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">📋 HTML 붙여넣기</div>
|
||||
<div class="modal-body">
|
||||
<textarea class="modal-textarea" id="htmlContent" placeholder="HTML 코드를 붙여넣으세요..."></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="modal-btn" onclick="closeHtmlModal()">취소</button>
|
||||
<button class="modal-btn primary" onclick="submitHtml()">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 폴더 모달 -->
|
||||
<div class="modal-overlay" id="folderModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">📁 폴더 위치 입력</div>
|
||||
<div class="modal-body">
|
||||
<input type="text" id="folderPath" class="modal-input" placeholder="C:\Users\...\Documents\참고자료" style="width:100%; padding:12px; border-radius:6px; border:1px solid var(--ui-border); background:var(--ui-bg); color:var(--ui-text); font-size:13px;">
|
||||
<p style="margin-top:10px; font-size:11px; color:var(--ui-dim);">* 로컬 폴더 경로를 입력하세요. (Engine 실행 필요)</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="modal-btn" onclick="closeFolderModal()">취소</button>
|
||||
<button class="modal-btn primary" onclick="submitFolder()">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 참고 링크 모달 -->
|
||||
<div class="modal-overlay" id="linkModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">🔗 참고 링크 입력</div>
|
||||
<div class="modal-body">
|
||||
<div id="linkInputList">
|
||||
<input type="text" class="link-input" placeholder="https://..." style="width:100%; padding:10px; border-radius:6px; border:1px solid var(--ui-border); background:var(--ui-bg); color:var(--ui-text); font-size:12px; margin-bottom:8px;">
|
||||
</div>
|
||||
<button onclick="addLinkInput()" style="padding:8px 12px; border-radius:4px; border:1px dashed var(--ui-border); background:transparent; color:var(--ui-dim); font-size:12px; cursor:pointer; width:100%;">+ 링크 추가</button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="modal-btn" onclick="closeLinkModal()">취소</button>
|
||||
<button class="modal-btn primary" onclick="submitLinks()">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 부분 수정 팝업 -->
|
||||
<div class="ai-edit-popup" id="aiEditPopup">
|
||||
<button class="ai-edit-close" onclick="closeAiEditPopup()">✕</button>
|
||||
<div class="ai-edit-header">🤖 AI로 수정하기</div>
|
||||
<div class="ai-edit-selected">선택된 텍스트:</div>
|
||||
<div class="ai-edit-selected-text" id="aiEditSelectedText"></div>
|
||||
<textarea class="ai-edit-input" id="aiEditInput" rows="3" placeholder="예: 한 줄로 요약해줘 예: 표 형태로 만들어줘"></textarea>
|
||||
<button class="ai-edit-btn" onclick="submitAiEdit()">✨ 수정하기</button>
|
||||
</div>
|
||||
|
||||
<!-- 도메인 선택 모달 -->
|
||||
<div id="domainModal">
|
||||
<div class="domain-modal-content">
|
||||
<div class="domain-modal-header">
|
||||
<div class="domain-modal-title">📚 도메인 지식 선택</div>
|
||||
<button class="domain-modal-close" onclick="closeDomainModal()">✕</button>
|
||||
</div>
|
||||
<div class="domain-modal-body">
|
||||
<div class="domain-modal-desc">
|
||||
업로드 자료의 분야를 선택하면 해당 전문 지식이 AI에 전달됩니다.<br>
|
||||
선택하지 않으면 AI가 자동으로 분야를 판단합니다.
|
||||
</div>
|
||||
<div id="domainCategoryList"></div>
|
||||
</div>
|
||||
<div class="domain-modal-footer">
|
||||
<div class="domain-summary-text empty" id="domainSummaryText">선택된 도메인이 없습니다.</div>
|
||||
<div class="domain-modal-actions">
|
||||
<button class="domain-btn" onclick="clearAllDomains()">초기화</button>
|
||||
<button class="domain-btn primary" onclick="submitDomainSelection()">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 템플릿 추가 모달 -->
|
||||
<div class="template-modal" id="templateModal">
|
||||
<div class="template-modal-content">
|
||||
<div class="template-modal-header">
|
||||
<div class="template-modal-title">📁 템플릿 추가</div>
|
||||
<button class="template-modal-close" onclick="closeTemplateModal()">✕</button>
|
||||
</div>
|
||||
|
||||
<div class="template-input-group">
|
||||
<label class="template-input-label">템플릿 이름</label>
|
||||
<input type="text" class="template-name-input" id="templateNameInput"
|
||||
placeholder="예: 걸포4지구 교통영향평가">
|
||||
</div>
|
||||
|
||||
<div class="template-input-group">
|
||||
<label class="template-input-label">템플릿 파일</label>
|
||||
<div class="template-dropzone" id="templateDropzone" onclick="document.getElementById('templateFileInput').click()">
|
||||
<div class="template-dropzone-icon">📄</div>
|
||||
<div class="template-dropzone-text">파일을 드래그하거나 클릭하여 선택</div>
|
||||
<div class="template-dropzone-hint">HWPX, HWP, PDF 지원</div>
|
||||
</div>
|
||||
<input type="file" id="templateFileInput" accept=".hwpx,.hwp,.pdf" style="display:none" onchange="handleTemplateFile(this)">
|
||||
<div class="template-dropzone-file" id="templateFileInfo">
|
||||
<span class="filename" id="templateFileName"></span>
|
||||
<span class="remove" onclick="removeTemplateFile()">✕</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="template-submit-btn" id="templateSubmitBtn" onclick="submitTemplate()" disabled>
|
||||
<span class="spinner" id="templateSpinner"></span>
|
||||
<span id="templateSubmitText">✨ 분석 및 추가</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 문서 유형 추가 모달 -->
|
||||
<div class="modal-overlay" id="addDocTypeModal">
|
||||
<div class="modal" style="min-width: 500px; max-width: 550px;">
|
||||
<div class="modal-header">
|
||||
<span id="addDocTypeModalTitle">📄 문서 유형 추가</span>
|
||||
</div>
|
||||
|
||||
<!-- Step 1: 입력 -->
|
||||
<div id="docTypeStep1" class="modal-body">
|
||||
<div style="margin-bottom: 16px;">
|
||||
<label style="font-size: 12px; font-weight: 600; color: var(--ui-dim); display: block; margin-bottom: 8px;">문서 유형 이름</label>
|
||||
<input type="text" id="newDocTypeName" placeholder="예: 제안서, 회의록..." style="width:100%; padding:10px; border-radius:6px; border:1px solid var(--ui-border); background:var(--ui-bg); color:var(--ui-text); font-size:13px;">
|
||||
</div>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<label style="font-size: 12px; font-weight: 600; color: var(--ui-dim); display: block; margin-bottom: 8px;">설명 (선택)</label>
|
||||
<input type="text" id="newDocTypeDesc" placeholder="문서 유형에 대한 간단한 설명" style="width:100%; padding:10px; border-radius:6px; border:1px solid var(--ui-border); background:var(--ui-bg); color:var(--ui-text); font-size:13px;">
|
||||
</div>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<label style="font-size: 12px; font-weight: 600; color: var(--ui-dim); display: block; margin-bottom: 8px;">샘플 문서 (HWPX 권장)</label>
|
||||
<input type="file" id="newDocTypeFile" accept=".hwpx,.hwp,.pdf" style="width:100%; padding:10px; border-radius:6px; border:1px solid var(--ui-border); background:var(--ui-bg); color:var(--ui-text); font-size:12px;">
|
||||
<p style="margin-top:8px; font-size:11px; color:var(--ui-dim);">샘플 문서를 분석하여 스타일과 구조를 추출합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: 분석 진행 -->
|
||||
<div id="docTypeStep2" class="modal-body" style="display:none;">
|
||||
<div class="analysis-progress">
|
||||
<div id="analysisSteps"></div>
|
||||
<div class="progress-bar-container">
|
||||
<div id="analysisProgressBar" class="progress-bar" style="width: 0%"></div>
|
||||
</div>
|
||||
<p id="analysisProgressText" style="text-align: center; margin-top: 12px; font-size: 12px; color: var(--ui-dim);">준비 중...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: 결과 확인 -->
|
||||
<div id="docTypeStep3" class="modal-body" style="display:none;">
|
||||
<div class="analysis-result">
|
||||
<h4 style="margin-bottom: 16px; color: var(--ui-accent);">📋 분석 완료</h4>
|
||||
<div id="analysisResultSummary" class="result-summary"></div>
|
||||
<div id="analysisResultToc" class="result-toc" style="margin-top: 16px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" id="docTypeModalFooter">
|
||||
<button class="modal-btn" onclick="closeAddDocTypeModal()">취소</button>
|
||||
<button class="modal-btn primary" id="docTypeActionBtn" onclick="startDocTypeAnalysis()">분석 시작</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ===== 상태 변수 =====
|
||||
let inputContent = '';
|
||||
var generatedHTML = '';
|
||||
var currentDocType = 'briefing';
|
||||
let currentWriteMode = 'restructure';
|
||||
let currentZoom = 100;
|
||||
let folderPath = '';
|
||||
let referenceLinks = [];
|
||||
let currentTemplate = 'default';
|
||||
let templateElements = {};
|
||||
let analysisResult = null;
|
||||
|
||||
// ===== 문서 유형 데이터 =====
|
||||
let docTypes = []; // 전체 문서 유형 (default + user)
|
||||
let selectedDocTypeFile = null;
|
||||
|
||||
// ===== 템플릿 관련 =====
|
||||
let userTemplates = [];
|
||||
let selectedTemplateFile = null;
|
||||
|
||||
// ===== AI 부분 수정 =====
|
||||
let selectedText = '';
|
||||
let selectedRange = null;
|
||||
|
||||
|
||||
// ===== 활용 범위 선택 =====
|
||||
function selectWriteMode(mode) {
|
||||
currentWriteMode = mode;
|
||||
document.querySelectorAll('.write-mode-tab').forEach(tab => {
|
||||
const radio = tab.querySelector('input[type="radio"]');
|
||||
if (radio && radio.value === mode) {
|
||||
tab.classList.add('selected');
|
||||
radio.checked = true;
|
||||
} else {
|
||||
tab.classList.remove('selected');
|
||||
}
|
||||
});
|
||||
updateDomainSectionVisibility();
|
||||
}
|
||||
|
||||
function updateDomainSectionVisibility() {
|
||||
const section = document.getElementById('domainSection');
|
||||
if (!section) return;
|
||||
const hasFolder = folderPath && folderPath.trim() !== '';
|
||||
const hasLinks = referenceLinks && referenceLinks.length > 0;
|
||||
section.style.display = (hasFolder || hasLinks) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// ===== 썸네일 템플릿 =====
|
||||
const thumbnailTemplates = {
|
||||
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-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"></div>
|
||||
<div class="page-bottom"></div>
|
||||
</div>
|
||||
`,
|
||||
report: `
|
||||
<div class="line h1"></div>
|
||||
<div class="line body"></div>
|
||||
<div class="line body" style="width:90%"></div>
|
||||
<div class="line h2"></div>
|
||||
<div class="line body" style="width:85%"></div>
|
||||
`,
|
||||
ppt: `
|
||||
<div class="slide"><div class="slide-title">TITLE</div><div class="slide-body"></div></div>
|
||||
<div class="slide"><div class="slide-title">CONTENT</div><div class="slide-body"></div></div>
|
||||
`,
|
||||
custom: `
|
||||
<div class="line h1"></div>
|
||||
<div class="line body"></div>
|
||||
<div class="line h2"></div>
|
||||
<div class="line body"></div>
|
||||
`
|
||||
};
|
||||
|
||||
// ===== 초기화 =====
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setStatus('로딩 중...', false);
|
||||
|
||||
if (typeof loadDocTypes === 'function') loadDocTypes();
|
||||
if (typeof loadUserTemplates === 'function') loadUserTemplates();
|
||||
|
||||
// 드롭존 이벤트
|
||||
const dropzones = ['templateDropzone', 'docTypeDropzone'];
|
||||
dropzones.forEach(id => {
|
||||
const dropzone = document.getElementById(id);
|
||||
if (dropzone) {
|
||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||
dropzone.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
['dragenter', 'dragover'].forEach(eventName => {
|
||||
dropzone.addEventListener(eventName, () => dropzone.classList.add('dragover'));
|
||||
});
|
||||
['dragleave', 'drop'].forEach(eventName => {
|
||||
dropzone.addEventListener(eventName, () => dropzone.classList.remove('dragover'));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 템플릿 입력 이벤트
|
||||
const templateNameInput = document.getElementById('templateNameInput');
|
||||
if (templateNameInput) {
|
||||
if (typeof updateTemplateSubmitBtn === 'function') {
|
||||
templateNameInput.addEventListener('input', updateTemplateSubmitBtn);
|
||||
}
|
||||
}
|
||||
|
||||
setStatus('준비됨', true);
|
||||
});
|
||||
|
||||
// Enter 키로 피드백 제출
|
||||
document.getElementById('feedbackInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
submitFeedback();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="/static/js/ui.js"></script>
|
||||
<script src="/static/js/doc_type.js"></script>
|
||||
<script src="/static/js/template.js"></script>
|
||||
<script src="/static/js/modals.js"></script>
|
||||
<script src="/static/js/generator.js"></script>
|
||||
<script src="/static/js/demo_mode.js"></script>
|
||||
<script src="/static/js/export.js"></script>
|
||||
<script src="/static/js/ai_edit.js"></script>
|
||||
<script src="/static/js/editor.js"></script>
|
||||
<script src="/static/js/domain_selector.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user