();
diff --git a/src/Web/appsettings.json b/src/Web/appsettings.json
index 8b45678..a57c12f 100644
--- a/src/Web/appsettings.json
+++ b/src/Web/appsettings.json
@@ -51,6 +51,11 @@
"WorkingDirectory": "../../mcp-server"
},
"PromptsDirectory": "../../prompts",
+ "DocBrowser": {
+ "Root": "",
+ "MaxTextBytes": 2097152,
+ "MaxUploadBytes": 52428800
+ },
"Kb": {
"QdrantUrl": "http://localhost:6333",
"VectorSize": 768,
diff --git a/src/Web/wwwroot/css/docs.css b/src/Web/wwwroot/css/docs.css
new file mode 100644
index 0000000..dd0e9cd
--- /dev/null
+++ b/src/Web/wwwroot/css/docs.css
@@ -0,0 +1,370 @@
+/* ════════════════════════════════════════════════════════════
+ 문서 탐색기 (Tab 16) — 다크 트리 + 흰색 종이 뷰어
+ ════════════════════════════════════════════════════════════ */
+
+/* 헤더 컴팩트화: 제목 위로 + 부제목을 제목 옆으로 → 문서 영역 확보 */
+#pane-docs .pane-hdr { margin-bottom: 10px; }
+#pane-docs .pane-hdr > div:first-child {
+ display: flex;
+ align-items: baseline;
+ gap: 14px;
+ flex-wrap: wrap;
+}
+#pane-docs .pane-hdr h1 { margin-bottom: 0; }
+#pane-docs .pane-hdr p { margin-bottom: 0; }
+
+.docs-layout {
+ display: flex;
+ gap: 0;
+ height: calc(100vh - 116px);
+ min-height: 560px;
+ border: 1px solid var(--bd);
+ border-radius: var(--r);
+ overflow: hidden;
+ background: var(--s1);
+}
+
+/* ── 좌: 파일 트리 패널 (다크) ─────────────────────────────── */
+.docs-tree-panel {
+ width: 300px;
+ flex-shrink: 0;
+ background: var(--s2);
+ border-right: 1px solid var(--bd);
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.docs-tree-toolbar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 10px;
+ border-bottom: 1px solid var(--bd);
+}
+.docs-tree-toolbar .docs-filter {
+ flex: 1;
+ min-width: 0;
+ height: 30px;
+ font-size: 12px;
+ padding: 4px 8px;
+}
+
+.docs-root-line {
+ padding: 5px 10px;
+ font-size: 10px;
+ color: var(--t2);
+ border-bottom: 1px solid var(--bd);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ direction: rtl; /* 긴 경로는 앞쪽을 ... 으로 */
+ text-align: left;
+}
+
+.docs-tree {
+ flex: 1;
+ overflow: auto;
+ padding: 6px 4px;
+ min-height: 0;
+}
+
+.docs-admin-bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ padding: 8px 10px;
+ border-top: 1px solid var(--bd);
+}
+.docs-admin-state { font-size: 11px; color: var(--t1); }
+
+/* ── 트리 노드 ─────────────────────────────────────────────── */
+.docs-ul, .docs-children { list-style: none; margin: 0; padding: 0; }
+.docs-li { margin: 0; }
+
+.docs-node {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 8px;
+ border-radius: 5px;
+ cursor: pointer;
+ color: var(--t1);
+ font-size: 12.5px;
+ user-select: none;
+ white-space: nowrap;
+}
+.docs-node:hover { background: var(--s4); color: var(--t0); }
+.docs-node.active { background: var(--ag); color: var(--blu); }
+
+.docs-node-ic {
+ flex-shrink: 0;
+ width: 16px;
+ text-align: center;
+ font-size: 11px;
+ transition: transform .15s ease;
+}
+.docs-node.is-dir > .docs-node-ic { color: var(--t2); }
+.docs-node.is-dir.open > .docs-node-ic { transform: rotate(90deg); }
+
+.docs-node-name {
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.docs-node-size {
+ flex-shrink: 0;
+ font-family: var(--fm);
+ font-size: 9.5px;
+ color: var(--t2);
+}
+
+.docs-node-act { display: none; flex-shrink: 0; gap: 2px; }
+.docs-tree.can-manage .docs-node:hover .docs-node-act { display: flex; }
+.docs-node-act button {
+ background: transparent;
+ border: none;
+ color: var(--t2);
+ cursor: pointer;
+ font-size: 11px;
+ padding: 0 3px;
+ border-radius: 4px;
+}
+.docs-node-act button:hover { color: var(--t0); background: rgba(255,255,255,.08); }
+.docs-act-delete:hover { color: var(--red) !important; }
+
+.docs-loading, .docs-error { padding: 8px 12px; font-size: 12px; color: var(--t2); }
+.docs-error { color: var(--red); white-space: pre-wrap; }
+
+/* ── 우: 뷰어 패널 ─────────────────────────────────────────── */
+.docs-viewer-panel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ min-height: 0;
+}
+
+.docs-viewer-bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 8px 14px;
+ background: var(--s2);
+ border-bottom: 1px solid var(--bd);
+}
+.docs-cur-path {
+ font-size: 11.5px;
+ color: var(--t1);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ direction: rtl;
+ text-align: left;
+}
+.docs-viewer-actions { display: flex; gap: 6px; flex-shrink: 0; }
+.docs-viewer-actions a.btn-b, .docs-viewer-actions a.btn-a { text-decoration: none; }
+
+/* ── 뷰어 본문: 흰색 종이 ──────────────────────────────────── */
+.docs-viewer {
+ flex: 1;
+ overflow: auto;
+ background: #ffffff;
+ color: #24292f;
+ min-height: 0;
+}
+.docs-viewer.mode-pdf { overflow: hidden; }
+
+.docs-empty {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #8b949e;
+ font-size: 14px;
+}
+
+.docs-trunc {
+ background: #fff8e6;
+ color: #7a5b00;
+ border-bottom: 1px solid #f0d98c;
+ padding: 8px 16px;
+ font-size: 12px;
+}
+
+/* 텍스트 뷰어 */
+.docs-text-pre {
+ margin: 0;
+ padding: 20px 24px;
+ font-family: var(--fm);
+ font-size: 13px;
+ line-height: 1.65;
+ color: #24292f;
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+/* PDF 뷰어 */
+.docs-pdf-frame { width: 100%; height: 100%; border: 0; background: #fff; }
+
+/* 미지원 */
+.docs-unsupported {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ color: #57606a;
+ text-align: center;
+}
+.docs-unsupported-ic { font-size: 48px; }
+.docs-unsupported .docs-muted { color: #8b949e; font-size: 12px; }
+.docs-unsupported a { margin-top: 8px; text-decoration: none; }
+
+/* ── 마크다운 본문 (GitHub-라이트 타이포) ──────────────────── */
+.md-body {
+ padding: 28px 36px;
+ max-width: 960px;
+ margin: 0 auto;
+ color: #24292f;
+ font-size: 15px;
+ line-height: 1.7;
+ word-wrap: break-word;
+}
+.md-body > *:first-child { margin-top: 0; }
+.md-body h1, .md-body h2, .md-body h3, .md-body h4, .md-body h5, .md-body h6 {
+ margin: 26px 0 14px;
+ font-weight: 700;
+ line-height: 1.3;
+ color: #1f2328;
+}
+.md-body h1 { font-size: 1.9em; padding-bottom: .3em; border-bottom: 1px solid #d8dee4; }
+.md-body h2 { font-size: 1.5em; padding-bottom: .3em; border-bottom: 1px solid #d8dee4; }
+.md-body h3 { font-size: 1.25em; }
+.md-body h4 { font-size: 1.05em; }
+.md-body p { margin: 0 0 14px; }
+.md-body a { color: #0969da; text-decoration: none; }
+.md-body a:hover { text-decoration: underline; }
+.md-body ul, .md-body ol { margin: 0 0 14px; padding-left: 2em; }
+.md-body li { margin: 3px 0; }
+.md-body li > p { margin: 4px 0; }
+.md-body blockquote {
+ margin: 0 0 14px;
+ padding: 2px 16px;
+ color: #57606a;
+ border-left: 4px solid #d0d7de;
+}
+.md-body hr { height: 1px; border: 0; background: #d8dee4; margin: 24px 0; }
+.md-body img { max-width: 100%; }
+
+/* 인라인 코드 */
+.md-body code {
+ font-family: var(--fm);
+ font-size: 85%;
+ background: rgba(175,184,193,.2);
+ padding: .2em .4em;
+ border-radius: 6px;
+}
+/* 코드블록 (highlight.js github 테마가 색 담당) */
+.md-body pre {
+ margin: 0 0 16px;
+ padding: 14px 16px;
+ background: #f6f8fa;
+ border: 1px solid #e4e8ec;
+ border-radius: 8px;
+ overflow: auto;
+ line-height: 1.5;
+}
+.md-body pre code {
+ background: transparent;
+ padding: 0;
+ font-size: 13px;
+ border-radius: 0;
+}
+
+/* GFM 표 */
+.md-body table {
+ border-collapse: collapse;
+ margin: 0 0 16px;
+ display: block;
+ width: max-content;
+ max-width: 100%;
+ overflow: auto;
+}
+.md-body th, .md-body td {
+ border: 1px solid #d0d7de;
+ padding: 6px 13px;
+}
+.md-body th { background: #f6f8fa; font-weight: 700; }
+.md-body tr:nth-child(2n) td { background: #f6f8fa; }
+
+/* 체크박스 리스트 */
+.md-body input[type="checkbox"] { margin-right: 6px; }
+
+/* mermaid */
+.md-body .mermaid { margin: 0 0 16px; text-align: center; }
+.md-body .mermaid svg { max-width: 100%; height: auto; }
+
+/* ── 편집 모드 ─────────────────────────────────────────────── */
+.docs-viewer.mode-edit { background: #ffffff; }
+.docs-edit-wrap {
+ display: flex;
+ height: 100%;
+ min-height: 0;
+}
+.docs-edit-ta {
+ flex: 1;
+ border: 0;
+ outline: none;
+ resize: none;
+ padding: 20px 24px;
+ font-family: var(--fm);
+ font-size: 13px;
+ line-height: 1.65;
+ color: #24292f;
+ background: #ffffff;
+ min-width: 0;
+}
+.docs-viewer.mode-edit.is-md .docs-edit-ta {
+ border-right: 1px solid #d8dee4;
+ flex: 0 0 50%;
+}
+.docs-edit-prev {
+ flex: 1;
+ overflow: auto;
+ padding: 20px 28px;
+ max-width: none;
+ margin: 0;
+ background: #fbfcfd;
+ min-width: 0;
+}
+
+/* ── 토스트 ────────────────────────────────────────────────── */
+.docs-toast {
+ position: fixed;
+ bottom: 24px;
+ left: 50%;
+ transform: translateX(-50%) translateY(20px);
+ background: var(--s3);
+ color: var(--t0);
+ border: 1px solid var(--bd2);
+ padding: 10px 18px;
+ border-radius: 8px;
+ font-size: 13px;
+ box-shadow: 0 8px 24px rgba(0,0,0,.4);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity .2s ease, transform .2s ease;
+ z-index: 1000;
+}
+.docs-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
+
+/* 좁은 화면 */
+@media (max-width: 900px) {
+ .docs-tree-panel { width: 200px; }
+ .docs-viewer.mode-edit.is-md .docs-edit-ta { flex: 0 0 100%; }
+ .docs-edit-prev { display: none; }
+}
diff --git a/src/Web/wwwroot/css/style.css b/src/Web/wwwroot/css/style.css
index 6cfd0df..d70a517 100644
--- a/src/Web/wwwroot/css/style.css
+++ b/src/Web/wwwroot/css/style.css
@@ -68,10 +68,11 @@ html, body { height: 100%; background: var(--s0); color: var(--t1); font-family:
}
/* nav */
-.nav { list-style: none; padding: 14px 10px; flex: 1; display: flex; flex-direction: column; gap: 3px; }
+.nav { list-style: none; padding: 14px 10px; flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 3px; }
.nav-item {
display: flex; align-items: center; gap: 10px;
+ flex-shrink: 0;
padding: 10px 12px; border-radius: var(--r);
cursor: pointer; transition: all var(--tr);
color: var(--t2); font-size: 13px; font-weight: 600;
diff --git a/src/Web/wwwroot/index.html b/src/Web/wwwroot/index.html
index d240288..33f9d8d 100644
--- a/src/Web/wwwroot/index.html
+++ b/src/Web/wwwroot/index.html
@@ -6,6 +6,7 @@
ExperionCrawler
+
@@ -92,6 +93,10 @@
15
OPC UA Write
+
+ 16
+ 문서 탐색기
+
@@ -1708,5 +1756,6 @@
+