프로젝트 폴더 전체를 안전하게 탐색하고 문서를 보고 편집하는 웹 UI. - DocBrowserService: 루트 자동탐색(.git/.sln), 경로이탈·심볼릭·제외디렉토리 ·민감파일 가드, 목록/읽기/원본/쓰기/이름변경/삭제/폴더/업로드 - DocsController(/api/docs): config·tree·text·raw(공개) / 변경계열은 KB admin 토큰 - 뷰어: md(marked+DOMPurify+hljs+KaTeX+mermaid) / pdf(원본 iframe) / txt(pre) / 그 외 다운로드 - 인라인 편집(md 실시간 분할 미리보기) + 관리(새폴더/업로드/이름변경/삭제) - 사이드바 nav 스크롤 수정(.nav overflow), 헤더 컴팩트화로 문서 영역 확보 주의: 프론트 라이브러리(marked/mermaid/katex 등)는 wwwroot/lib/(.gitignore)라 미추적 — 별도 처리 필요. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10 KiB
10 KiB
ExperionCrawler — 작업 이력
작업 규칙
- 복잡한 작업은 항상 todo 목록 먼저 생성
- 각 단계 시작 전 todo 목록 확인
- 단계 완료 후 즉시 completed 표시
완료된 작업
문서 탐색기 (Tab 16) 구현 (2026-05-24)
배경
프로젝트 폴더의 문서를 Web UI에서 직접 보고 관리. 트리는 프로젝트 폴더 전체를 탐색, 뷰어는 txt · md · pdf 3종(그 외 다운로드), 보기 + 관리 + 인라인 편집 범위. 사용자 결정: PDF=원본 그대로(iframe), Excel=제외, md 렌더=marked+코드강조+KaTeX+mermaid, 뷰어 바탕=흰색.
구현 내역
| # | 항목 | 핵심 |
|---|---|---|
| 1 | 안전 파일트리 | 루트 자동탐색(.git/.sln) → 상대경로만 취급, Path.GetFullPath 정규화 후 루트이탈·심볼릭이탈 차단. 제외 디렉토리(.git/bin/obj/node_modules/storage…)·민감파일(.pfx/.env/appsettings*.json…) 숨김+차단 |
| 2 | 뷰어 디스패치 | md=marked→DOMPurify→hljs→KaTeX→mermaid / pdf=원본 iframe(Content-Type: application/pdf) / txt=pre / 그 외=다운로드. md 라이브러리는 첫 진입 시 지연로딩 |
| 3 | 인라인 편집 | admin이면 ✎ 편집 → textarea(md는 실시간 분할 미리보기) → PUT /api/docs/text 저장 |
| 4 | 관리 | 새 폴더·업로드·이름변경·재귀삭제 — KB admin 토큰(X-Kb-Token) 재사용 |
| 5 | UI | 다크 트리(지연확장·필터·노드별 hover 액션) + 흰색 종이 뷰어, 상대링크 클릭 시 탐색기 내 이동, 토스트 |
수정 파일
| 파일 | 변경 요약 |
|---|---|
src/Infrastructure/Docs/DocBrowserService.cs (신규) |
루트결정·SafeResolve(이탈/심볼릭/제외/민감 가드)·List/ReadText(바이너리·BOM·크기상한)/OpenRaw(MIME)/WriteText/Rename/Delete/MakeDir/SaveUploadAsync |
src/Web/Controllers/DocsController.cs (신규) |
/api/docs — config·tree·text·raw(공개) / PUT text·rename·mkdir·upload·DELETE(admin, IKbAuthService.ValidateAsync) |
src/Web/Program.cs |
DocBrowserService 싱글톤 등록 |
src/Web/appsettings.json |
DocBrowser 섹션(Root="", MaxTextBytes=2MB, MaxUploadBytes=50MB) |
src/Web/wwwroot/index.html |
nav-item(16 문서 탐색기), #pane-docs(트리+뷰어), docs.css 링크, docs.js 스크립트 |
src/Web/wwwroot/js/app.js |
탭 전환부 if (tab === 'docs') docsInit() |
src/Web/wwwroot/js/docs.js (신규) |
트리(지연확장·필터·관리액션 위임), 뷰어 디스패치, md 렌더 파이프라인, 지연 라이브러리 로더, 인라인 에디터, 관리(mkdir/upload/rename/delete), KB 토큰 재사용 잠금해제, 토스트 |
src/Web/wwwroot/css/docs.css (신규) |
다크 트리 + 흰색 종이 뷰어, GitHub-라이트 마크다운 타이포, 편집 분할 |
src/Web/wwwroot/lib/ (신규) |
marked@12, dompurify@3.1.6, highlight.js@11.10(+github 테마), katex@0.16.11(js/css/auto-render + woff2 20종), mermaid@10.9.1 — 전부 로컬 번들 |
설계 결정
| 항목 | 결정 |
|---|---|
| 적용 환경 | 소스 트리가 있는 개발 환경 전용 (배포본 /opt/ExperionCrawler엔 소스 없음). 루트는 DocBrowser:Root로 재설정 가능 |
| 인증 | 조회=공개, 변경계열=KB admin 토큰 재사용(별도 비번 없음). 프론트는 sessionStorage.kbToken 공유 |
| md 라이브러리 로딩 | 페이지 로드 시점이 아닌 md 첫 열람 시 지연로딩(mermaid 3.3MB 등 무게 회피) |
| XSS | marked 출력은 DOMPurify 살균. pdf/원본은 iframe·MIME 한정 |
| download 파라미터 | ASP.NET bool 바인딩은 true/false만 허용 — 프론트 download=true 사용(초기 download=1은 400, 수정함) |
| 뷰어 대상 | 우선 txt/md/pdf만. DOCS_TEXT_EXT/DOCS_MD_EXT 상수로 추후 확장 용이 |
빌드/검증 (라이브 :5000)
dotnet build경고 0 / 에러 0,node -c docs.js/app.jsOK, 정적자산 11종 200 서빙- 루트 자동탐색 → 프로젝트 루트, 트리
.git/bin/obj제외, 경로이탈·민감파일(appsettings/.env)·.git진입 차단 - md/txt 읽기·하위트리·PDF inline(
application/pdf)·다운로드 첨부 정상 - 미인증 PUT/DELETE → 401, admin 로그인 후 mkdir/쓰기/읽기/이름변경/재귀삭제 정상,
.env쓰기 차단, 정리 확인
잔여
- 브라우저 실물 렌더(트리 클릭·md/mermaid/KaTeX·편집 UI)는 미확인 — 사용자
Ctrl+F5후 탭 진입으로 확인 필요 - 검증 위해 기존
dotnet run(터미널)을 백그라운드 새 빌드로 교체 기동
Phase 7 + Phase 5 후순위 일괄 구현 (2026-05-14)
배경
plans/빅피클-잔여작업-코딩계획.md의 1~6번 항목을 일괄 구현. Phase 7 옵션 4종 + Phase 5
후순위 2종으로 채팅 UX·관리 편의·운영자 분석 능력을 강화.
구현 내역
| # | 항목 | 핵심 |
|---|---|---|
| 1 | 툴 카드 영구 보존 | sess.messages[*].toolCalls[] 저장 → llmRenderMessages에서 재렌더링. F5 새로고침 후에도 툴 카드 유지 |
| 2 | KB 청크 미리보기 UI | Qdrant Scroll API → /api/kb/documents/{id}/chunks → 모달에 청크 카드 (접기/펼침) |
| 3 | 시계열 미니 스파클라인 | llmDetectTimeSeries (timestamp+value 키 자동 감지) → uPlot 90px 차트가 표 위에 자동 렌더링 |
| 4 | NL2SQL 의도 라우터 | _classify_intent 정규식 6규칙 → query_with_nl 진입 시 알람/요약/태그검색/이벤트로 위임. classify_intent MCP 도구로도 노출 |
| 5 | 대화 요약 | sess.summary + summarizedUpTo 인덱스, LLM_MAX_HISTORY=20 초과 시 /api/ollama/summarize 호출 → systemPrompt에 누적 요약 prepend |
| 6 | 에이전트 모드 | #llm-agent-mode 토글 → AgentMode 요청 필드 → ComposeSystemPrompt에 AgentModeGuideKo (ReAct 사이클) 주입 |
수정 파일
| 파일 | 변경 요약 |
|---|---|
src/Web/wwwroot/js/app.js |
llmRenderToolCardsHtml(영구 렌더), llmDetectTimeSeries+llmBuildSparklineHtml+llmMountSparkline(uPlot), kbShowChunks+kbRenderChunks(모달), LLM_MAX_HISTORY+llmEnsureSummary+sess.summary 표시, llmAgentMode+llmToggleAgentMode, 툴 카드 표시 + 요약 prepend 로직 |
src/Web/wwwroot/index.html |
#kb-chunk-modal 모달, #llm-agent-row/#llm-agent-mode 토글 |
src/Web/wwwroot/css/style.css |
.kb-chunk-*(청크 카드/뱃지/locator), .llm-sparkline-box/-chart, .llm-summary-card(접기/펼침) |
src/Web/Controllers/OllamaController.cs |
AgentMode request 필드, ComposeSystemPrompt(agentMode), AgentModeGuideKo ReAct 가이드, POST /api/ollama/summarize 엔드포인트, OllamaSummarizeRequest DTO, ToolGuideKo에 classify_intent 추가 |
src/Web/Controllers/KbController.cs |
GET /api/kb/documents/{id}/chunks (admin) — KbQdrantClient Scroll 호출 |
src/Infrastructure/Kb/KbQdrantClient.cs |
GetChunksByDocIdAsync(collection, docId, limit) — payload-only Scroll API |
mcp-server/server.py |
_CLASSIFY_RULES+_classify_intent+@mcp.tool() classify_intent, query_with_nl 진입부에서 라우팅 후 5개 전용 도구로 위임 (실패 시 SQL fallback) |
의도 라우터 규칙
| 정규식 | 라우팅 대상 |
|---|---|
활성.*알람|현재.*알람|지금.*알람|active.*alarm |
active_alarms |
트립|trip |
active_alarms |
상태\s*보고서|교대.*보고|status.*report|운전.*보고 |
generate_status_report |
요약|보고서|리포트|summary|summarize|report |
summarize_events |
태그.*찾|tag.*찾|찾아\s*줘|find.*tag|어떤.*태그 |
find_tags |
이벤트.*조회|이벤트.*목록|event.*list|event.*query|로그.*조회 |
query_events |
| (그 외) | query_with_nl (기본 SQL 경로) |
스모크 테스트 10건 모두 통과 — "지금 알람 알려줘"→active_alarms / "FIC-6113.PV 값 보여줘"→query_with_nl / "안녕"→query_with_nl 등.
설계 결정
| 항목 | 결정 |
|---|---|
| 툴 카드 영구화 | assistantMsg.toolCalls에 {id,name,args,ok,preview,length,payload} 누적 저장. 기존 세션에 toolCalls 없으면 렌더링 생략(역호환) |
| AbortError 시 | content 또는 toolCalls가 비어있지 않으면 메시지 유지 (도구만 호출하고 중단된 경우도 보존) |
| 시계열 감지 | timestamp/recorded_at/ts/time/datetime + value/pv/val/fieldvalue/sp/op 페어. 3건 이상이어야 차트 생성. uPlot 미로딩 시 렌더 생략 |
| 스파클라인 부착 | innerHTML로 컨테이너만 만든 후 requestAnimationFrame에서 uPlot 생성 (DOM 마운트 후 실행 보장) |
| 요약 임계 | LLM_MAX_HISTORY=20 초과 시 오래된 절반을 요약. 누적 요약은 이전 요약을 system 메시지로 함께 전송 |
| 요약 송신 | sess.summary가 있으면 systemPrompt 맨 앞에 [이전 대화 요약] 블록 prepend. 사용자에게는 접이식 카드로 표시 |
| 의도 라우터 fallback | 라우팅 시도 실패(예외) 시 조용히 SQL 경로로 fallback |
| 에이전트 모드 조건부 표시 | llmType==='vllm' && llmUseTools 일 때만 토글 노출. localStorage 영속 |
| 청크 미리보기 권한 | admin 토큰 필요. payload만 조회(vector 제외), 최대 500개/문서 |
빌드/검증
dotnet build src/Web/ExperionCrawler.csproj— 경고 0건, 에러 0건python3 -m py_compile mcp-server/server.py mcp-server/worker/nl2sql_worker.py— OKpython3 -c "import server"— 9개 도구 모두 attribute로 노출 확인node -c src/Web/wwwroot/js/app.js— syntax OK- 의도 분류기 10/10 통과
런타임 셋업
mcp-server재시작 —classify_intent신규 도구 인식- 브라우저 캐시 무효화 (Ctrl+F5) — 신규 JS/CSS 적용
- 사용자 첫 진입 시 에이전트 모드 토글은 OFF (옵트인)
잔여
- 결정 보류: 현장 재고 데이터 출처 (KB 엑셀 업로드로 즉시 가능, 별도 개발 불필요), 임베딩 모델 BGE-M3 마이그레이션 (위험 대비 임계값 평가 필요)
- 모두 코드 작업 아닌 분석/결정 항목