feat: implement QuestionBank CRUD with pagination and template query

- Add pagination support to findAll (page, limit query params)
- Add findByTemplateId method to service
- Add GET /by-template/:templateId endpoint to controller
- Service already includes CRUD for QuestionBank and QuestionBankItem
This commit is contained in:
Developer
2026-04-23 17:19:11 +08:00
commit 0a9588abb7
492 changed files with 112453 additions and 0 deletions
+781
View File
@@ -0,0 +1,781 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Knowledge Base - System Overview</title>
<style>
:root {
--primary: #6366f1;
--primary-light: #818cf8;
--primary-dark: #4f46e5;
--bg: #0f172a;
--bg-card: #1e293b;
--bg-code: #0d1117;
--text: #e2e8f0;
--text-muted: #94a3b8;
--border: #334155;
--accent-green: #34d399;
--accent-blue: #38bdf8;
--accent-orange: #fb923c;
--accent-pink: #f472b6;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans SC', sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.7;
}
.hero {
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 30%, #0f172a 70%, #0c0a09 100%);
padding: 80px 20px 60px;
text-align: center;
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle at 30% 50%, rgba(99, 102, 241, 0.15) 0%, transparent 50%),
radial-gradient(circle at 70% 50%, rgba(56, 189, 248, 0.1) 0%, transparent 50%);
animation: pulse 8s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
.hero h1 {
font-size: 2.8rem;
font-weight: 800;
background: linear-gradient(135deg, #c7d2fe, #818cf8, #38bdf8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
position: relative;
margin-bottom: 12px;
}
.hero .subtitle {
font-size: 1.15rem;
color: var(--text-muted);
position: relative;
max-width: 700px;
margin: 0 auto;
}
.badge-row {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
margin-top: 24px;
position: relative;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 14px;
border-radius: 999px;
font-size: 0.82rem;
font-weight: 600;
border: 1px solid;
}
.badge.react { color: #61dafb; border-color: #61dafb33; background: #61dafb10; }
.badge.nest { color: #e0234e; border-color: #e0234e33; background: #e0234e10; }
.badge.rag { color: var(--accent-green); border-color: #34d39933; background: #34d39910; }
.badge.ts { color: #3178c6; border-color: #3178c633; background: #3178c610; }
.container {
max-width: 1100px;
margin: 0 auto;
padding: 0 20px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin: -40px auto 60px;
position: relative;
z-index: 1;
}
.feature-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 28px;
transition: transform 0.2s, border-color 0.2s;
}
.feature-card:hover {
transform: translateY(-3px);
border-color: var(--primary);
}
.feature-card .icon {
width: 44px;
height: 44px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.3rem;
margin-bottom: 16px;
}
.feature-card h3 {
font-size: 1.05rem;
margin-bottom: 8px;
color: #f1f5f9;
}
.feature-card p {
font-size: 0.9rem;
color: var(--text-muted);
}
.icon-blue { background: #38bdf818; }
.icon-green { background: #34d39918; }
.icon-orange { background: #fb923c18; }
.icon-pink { background: #f472b618; }
.icon-purple { background: #a78bfa18; }
.icon-yellow { background: #fbbf2418; }
section {
margin-bottom: 56px;
}
.section-title {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 24px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
}
.section-title .num {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 8px;
background: var(--primary);
font-size: 0.85rem;
font-weight: 700;
flex-shrink: 0;
}
/* Architecture Diagram */
.arch-diagram {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 32px;
overflow-x: auto;
}
.arch-row {
display: flex;
justify-content: center;
gap: 16px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.arch-box {
padding: 14px 22px;
border-radius: 10px;
font-size: 0.85rem;
font-weight: 600;
text-align: center;
min-width: 140px;
border: 1px solid;
}
.arch-box.frontend { background: #61dafb12; border-color: #61dafb33; color: #61dafb; }
.arch-box.backend { background: #e0234e12; border-color: #e0234e33; color: #e0234e; }
.arch-box.infra { background: #34d39912; border-color: #34d39933; color: #34d399; }
.arch-box.ai { background: #a78bfa12; border-color: #a78bfa33; color: #a78bfa; }
.arch-box.data { background: #fb923c12; border-color: #fb923c33; color: #fb923c; }
.arch-arrow {
text-align: center;
color: var(--text-muted);
font-size: 1.2rem;
margin-bottom: 16px;
}
.arch-label {
text-align: center;
font-size: 0.78rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1.5px;
margin-bottom: 10px;
}
/* File Tree */
.file-tree {
background: var(--bg-code);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px 28px;
font-family: 'Cascadia Code', 'Fira Code', monospace;
font-size: 0.85rem;
line-height: 1.9;
overflow-x: auto;
}
.file-tree .dir { color: var(--accent-blue); font-weight: 600; }
.file-tree .comment { color: #64748b; font-style: italic; }
.file-tree .file { color: var(--text); }
/* Pipeline */
.pipeline {
display: flex;
align-items: center;
gap: 0;
flex-wrap: wrap;
justify-content: center;
margin: 20px 0;
}
.pipeline-step {
padding: 12px 20px;
border-radius: 10px;
font-size: 0.85rem;
font-weight: 600;
text-align: center;
border: 1px solid var(--border);
background: var(--bg-card);
}
.pipeline-arrow {
color: var(--text-muted);
font-size: 1.1rem;
padding: 0 6px;
}
.pipeline-step.active {
border-color: var(--primary);
background: #6366f118;
color: var(--primary-light);
}
/* Tables */
.info-table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
}
.info-table th, .info-table td {
padding: 10px 16px;
text-align: left;
border-bottom: 1px solid var(--border);
font-size: 0.9rem;
}
.info-table th {
color: var(--text-muted);
font-weight: 600;
font-size: 0.82rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.info-table .port {
font-family: 'Cascadia Code', monospace;
color: var(--accent-orange);
font-weight: 600;
}
.info-table .env-key {
font-family: 'Cascadia Code', monospace;
color: var(--accent-green);
font-size: 0.83rem;
}
/* Code Block */
.code-block {
background: var(--bg-code);
border: 1px solid var(--border);
border-radius: 10px;
padding: 18px 22px;
font-family: 'Cascadia Code', 'Fira Code', monospace;
font-size: 0.83rem;
line-height: 1.8;
overflow-x: auto;
margin: 12px 0;
}
.code-block .cmd { color: var(--accent-green); }
.code-block .comment { color: #64748b; }
/* Two Column Layout */
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.two-col { grid-template-columns: 1fr; }
.hero h1 { font-size: 2rem; }
.features-grid { grid-template-columns: 1fr; }
}
.card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 24px;
}
.card h4 {
font-size: 1rem;
margin-bottom: 14px;
color: #f1f5f9;
}
.card ul {
list-style: none;
padding: 0;
}
.card ul li {
padding: 6px 0;
font-size: 0.88rem;
color: var(--text-muted);
display: flex;
align-items: flex-start;
gap: 8px;
}
.card ul li::before {
content: '';
color: var(--primary-light);
font-weight: 700;
flex-shrink: 0;
}
.step-list {
list-style: none;
padding: 0;
counter-reset: step;
}
.step-list li {
counter-increment: step;
padding: 8px 0;
font-size: 0.88rem;
color: var(--text-muted);
display: flex;
align-items: flex-start;
gap: 10px;
}
.step-list li::before {
content: counter(step);
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 6px;
background: var(--primary);
color: white;
font-size: 0.72rem;
font-weight: 700;
flex-shrink: 0;
}
.troubleshoot-item {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px 20px;
margin-bottom: 10px;
}
.troubleshoot-item strong {
color: #f1f5f9;
font-size: 0.92rem;
}
.troubleshoot-item p {
color: var(--text-muted);
font-size: 0.85rem;
margin-top: 4px;
}
footer {
text-align: center;
padding: 40px 20px;
color: var(--text-muted);
font-size: 0.82rem;
border-top: 1px solid var(--border);
}
</style>
</head>
<body>
<!-- Hero -->
<div class="hero">
<h1>Simple Knowledge Base</h1>
<p class="subtitle">Full-stack RAG Q&A System &mdash; Retrieval-Augmented Generation powered by React 19 + NestJS</p>
<div class="badge-row">
<span class="badge react">React 19</span>
<span class="badge nest">NestJS</span>
<span class="badge rag">RAG System</span>
<span class="badge ts">TypeScript</span>
</div>
</div>
<div class="container">
<!-- Key Features -->
<div class="features-grid">
<div class="feature-card">
<div class="icon icon-purple"> </div>
<h3>Multi-Model Support</h3>
<p>OpenAI-compatible APIs (OpenAI, DeepSeek, Claude) + Google Gemini native SDK with configurable LLM, Embedding, and Rerank models.</p>
</div>
<div class="feature-card">
<div class="icon icon-blue"></div>
<h3>Dual Processing Modes</h3>
<p>Fast Mode via Apache Tika for text extraction, and High-Precision Mode via Vision Pipeline for mixed image/text documents.</p>
</div>
<div class="feature-card">
<div class="icon icon-green"> </div>
<h3>Hybrid Search</h3>
<p>Vector + keyword search with Elasticsearch, source citation, similarity scoring, and configurable chunk size &amp; overlap.</p>
</div>
<div class="feature-card">
<div class="icon icon-orange"> </div>
<h3>User Isolation</h3>
<p>JWT authentication with per-user knowledge bases. Each user has isolated data and configurations.</p>
</div>
<div class="feature-card">
<div class="icon icon-pink"> </div>
<h3>Streaming Responses</h3>
<p>Real-time streaming via Server-Sent Events (SSE) for smooth, low-latency chat interactions.</p>
</div>
<div class="feature-card">
<div class="icon icon-yellow"> </div>
<h3>Multi-Language</h3>
<p>Interface supports Japanese, Chinese, and English with full internationalization for error and API response messages.</p>
</div>
</div>
<!-- Architecture -->
<section>
<h2 class="section-title"><span class="num">1</span> Architecture Overview</h2>
<div class="arch-diagram">
<div class="arch-label">Frontend Layer</div>
<div class="arch-row">
<div class="arch-box frontend">React 19 + Vite<br><small style="opacity:0.7">Port 13001 (dev) / 80 (prod)</small></div>
</div>
<div class="arch-arrow"></div>
<div class="arch-label">Backend Layer</div>
<div class="arch-row">
<div class="arch-box backend">NestJS API<br><small style="opacity:0.7">Port 3001</small></div>
<div class="arch-box backend">JWT Auth</div>
<div class="arch-box backend">Chat / RAG</div>
<div class="arch-box backend">Vision Pipeline</div>
</div>
<div class="arch-arrow"></div>
<div class="arch-label">AI &amp; Data Layer</div>
<div class="arch-row">
<div class="arch-box ai">OpenAI / Gemini<br><small style="opacity:0.7">LLM + Embedding</small></div>
<div class="arch-box infra">Elasticsearch<br><small style="opacity:0.7">Port 9200</small></div>
<div class="arch-box infra">Apache Tika<br><small style="opacity:0.7">Port 9998</small></div>
<div class="arch-box infra">LibreOffice<br><small style="opacity:0.7">Port 8100</small></div>
<div class="arch-box data">SQLite<br><small style="opacity:0.7">Metadata</small></div>
</div>
</div>
<!-- Processing Pipeline -->
<h4 style="margin-top:28px; margin-bottom:14px; color:#f1f5f9;">Dual Processing Pipeline</h4>
<div class="two-col">
<div class="card">
<h4>Fast Mode (Tika)</h4>
<div class="pipeline">
<div class="pipeline-step active">Upload</div>
<span class="pipeline-arrow"></span>
<div class="pipeline-step active">Tika Extract</div>
<span class="pipeline-arrow"></span>
<div class="pipeline-step active">Embed</div>
<span class="pipeline-arrow"></span>
<div class="pipeline-step active">Store</div>
</div>
<p style="font-size:0.85rem; color:var(--text-muted); text-align:center;">Quick text extraction, no API cost</p>
</div>
<div class="card">
<h4>High-Precision Mode (Vision)</h4>
<div class="pipeline">
<div class="pipeline-step active">Upload</div>
<span class="pipeline-arrow"></span>
<div class="pipeline-step">LibreOffice</div>
<span class="pipeline-arrow"></span>
<div class="pipeline-step">PDF→Image</div>
<span class="pipeline-arrow"></span>
<div class="pipeline-step">Vision Model</div>
</div>
<p style="font-size:0.85rem; color:var(--text-muted); text-align:center;">Preserves layout, charts, and images</p>
</div>
</div>
</section>
<!-- Project Structure -->
<section>
<h2 class="section-title"><span class="num">2</span> Project Structure</h2>
<div class="file-tree">
<span class="dir">simple-kb/</span><br>
├── <span class="dir">web/</span> <span class="comment"># React frontend (Vite)</span><br>
│ ├── <span class="dir">components/</span> <span class="comment"># UI components (ChatInterface, ConfigPanel, etc.)</span><br>
│ ├── <span class="dir">contexts/</span> <span class="comment"># React Context providers</span><br>
│ ├── <span class="dir">services/</span> <span class="comment"># API client services</span><br>
│ └── <span class="dir">utils/</span> <span class="comment"># Utility functions</span><br>
├── <span class="dir">server/</span> <span class="comment"># NestJS backend</span><br>
│ ├── <span class="dir">src/</span><br>
│ │ ├── <span class="dir">ai/</span> <span class="comment"># AI services (embedding, etc.)</span><br>
│ │ ├── <span class="dir">api/</span> <span class="comment"># API module</span><br>
│ │ ├── <span class="dir">auth/</span> <span class="comment"># JWT authentication</span><br>
│ │ ├── <span class="dir">chat/</span> <span class="comment"># Chat / RAG module</span><br>
│ │ ├── <span class="dir">elasticsearch/</span> <span class="comment"># Elasticsearch integration</span><br>
│ │ ├── <span class="dir">import-task/</span> <span class="comment"># Import task management</span><br>
│ │ ├── <span class="dir">knowledge-base/</span> <span class="comment"># Knowledge base management</span><br>
│ │ ├── <span class="dir">libreoffice/</span> <span class="comment"># LibreOffice integration</span><br>
│ │ ├── <span class="dir">model-config/</span> <span class="comment"># Model configuration management</span><br>
│ │ ├── <span class="dir">vision/</span> <span class="comment"># Vision model integration</span><br>
│ │ └── <span class="dir">vision-pipeline/</span> <span class="comment"># Vision pipeline orchestration</span><br>
│ ├── <span class="dir">data/</span> <span class="comment"># SQLite database storage</span><br>
│ ├── <span class="dir">uploads/</span> <span class="comment"># Uploaded files storage</span><br>
│ └── <span class="dir">temp/</span> <span class="comment"># Temporary files</span><br>
├── <span class="dir">docs/</span> <span class="comment"># Documentation (Japanese/Chinese)</span><br>
├── <span class="dir">nginx/</span> <span class="comment"># Nginx configuration</span><br>
├── <span class="dir">libreoffice-server/</span> <span class="comment"># LibreOffice conversion service (Python/FastAPI)</span><br>
└── <span class="file">docker-compose.yml</span> <span class="comment"># Docker orchestration</span>
</div>
</section>
<!-- Development Setup -->
<section>
<h2 class="section-title"><span class="num">3</span> Development Setup</h2>
<h4 style="margin-bottom:10px; color:#f1f5f9;">Prerequisites</h4>
<div class="card" style="margin-bottom:20px;">
<ul>
<li>Node.js 18+</li>
<li>Yarn package manager</li>
<li>Docker &amp; Docker Compose</li>
</ul>
</div>
<h4 style="margin-bottom:10px; color:#f1f5f9;">Quick Start</h4>
<div class="code-block">
<span class="comment"># Install dependencies</span><br>
<span class="cmd">yarn install</span><br><br>
<span class="comment"># Start infrastructure services</span><br>
<span class="cmd">docker-compose up -d elasticsearch tika libreoffice</span><br><br>
<span class="comment"># Configure environment</span><br>
<span class="cmd">cp server/.env.sample server/.env</span><br><br>
<span class="comment"># Start both frontend and backend</span><br>
<span class="cmd">yarn dev</span>
</div>
<h4 style="margin:20px 0 10px; color:#f1f5f9;">Development Commands</h4>
<div class="code-block">
<span class="comment"># Frontend only (port 13001)</span><br>
<span class="cmd">cd web && yarn dev</span><br><br>
<span class="comment"># Backend only (port 3001)</span><br>
<span class="cmd">cd server && yarn start:dev</span><br><br>
<span class="comment"># Run tests</span><br>
<span class="cmd">cd server && yarn test</span><br>
<span class="cmd">cd server && yarn test:e2e</span><br><br>
<span class="comment"># Lint and format</span><br>
<span class="cmd">cd server && yarn lint</span><br>
<span class="cmd">cd server && yarn format</span>
</div>
</section>
<!-- Docker Services -->
<section>
<h2 class="section-title"><span class="num">4</span> Docker Services &amp; Ports</h2>
<div class="card">
<table class="info-table">
<thead>
<tr><th>Service</th><th>Port</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr><td>Elasticsearch</td><td class="port">9200</td><td>Vector storage &amp; hybrid search</td></tr>
<tr><td>Apache Tika</td><td class="port">9998</td><td>Document text extraction</td></tr>
<tr><td>LibreOffice Server</td><td class="port">8100</td><td>Document format conversion</td></tr>
<tr><td>Backend API</td><td class="port">3001</td><td>NestJS REST API</td></tr>
<tr><td>Frontend (dev)</td><td class="port">13001</td><td>Vite dev server</td></tr>
<tr><td>Frontend (prod)</td><td class="port">80 / 443</td><td>Nginx reverse proxy</td></tr>
</tbody>
</table>
</div>
</section>
<!-- Environment Configuration -->
<section>
<h2 class="section-title"><span class="num">5</span> Environment Configuration</h2>
<div class="card">
<p style="font-size:0.88rem; color:var(--text-muted); margin-bottom:14px;">Key environment variables in <code style="color:var(--accent-green); background:#0d1117; padding:2px 8px; border-radius:4px; font-size:0.83rem;">server/.env</code></p>
<table class="info-table">
<thead>
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td class="env-key">OPENAI_API_KEY</td><td>&mdash;</td><td>OpenAI-compatible API key</td></tr>
<tr><td class="env-key">GEMINI_API_KEY</td><td>&mdash;</td><td>Google Gemini API key</td></tr>
<tr><td class="env-key">ELASTICSEARCH_HOST</td><td>http://localhost:9200</td><td>Elasticsearch URL</td></tr>
<tr><td class="env-key">TIKA_HOST</td><td>http://localhost:9998</td><td>Apache Tika URL</td></tr>
<tr><td class="env-key">LIBREOFFICE_URL</td><td>http://localhost:8100</td><td>LibreOffice server URL</td></tr>
<tr><td class="env-key">JWT_SECRET</td><td>&mdash;</td><td>JWT signing secret</td></tr>
</tbody>
</table>
</div>
</section>
<!-- Code Standards -->
<section>
<h2 class="section-title"><span class="num">6</span> Code Standards</h2>
<div class="two-col">
<div class="card">
<h4>Language Requirements</h4>
<ul>
<li>Code comments must be in English</li>
<li>Log messages must be in English</li>
<li>Error messages must support internationalization</li>
<li>API response messages must support i18n</li>
<li>Interface supports Japanese, Chinese, and English</li>
</ul>
</div>
<div class="card">
<h4>Code Quality</h4>
<ul>
<li>Backend uses Jest for unit and e2e tests</li>
<li>ESLint and Prettier configured for backend</li>
<li>Format: <code style="color:var(--accent-green);">cd server && yarn format</code></li>
<li>Lint: <code style="color:var(--accent-green);">cd server && yarn lint</code></li>
</ul>
</div>
</div>
</section>
<!-- Common Tasks -->
<section>
<h2 class="section-title"><span class="num">7</span> Common Development Tasks</h2>
<div class="two-col">
<div class="card">
<h4>Adding a New API Endpoint</h4>
<ol class="step-list">
<li>Create controller in appropriate module under <code style="color:var(--accent-blue);">server/src/</code></li>
<li>Add service methods with English comments</li>
<li>Update DTOs and validation</li>
<li>Add tests in <code style="color:var(--accent-blue);">*.spec.ts</code> files</li>
</ol>
</div>
<div class="card">
<h4>Adding a New Frontend Component</h4>
<ol class="step-list">
<li>Create component in <code style="color:var(--accent-blue);">web/components/</code></li>
<li>Add TypeScript interfaces in <code style="color:var(--accent-blue);">web/types.ts</code></li>
<li>Use Tailwind CSS for styling</li>
<li>Connect to backend services in <code style="color:var(--accent-blue);">web/services/</code></li>
</ol>
</div>
</div>
</section>
<!-- Deployment -->
<section>
<h2 class="section-title"><span class="num">8</span> Deployment</h2>
<div class="two-col">
<div class="card">
<h4>Development</h4>
<div class="code-block">
<span class="cmd">docker-compose up -d elasticsearch tika libreoffice</span><br>
<span class="cmd">yarn dev</span>
</div>
</div>
<div class="card">
<h4>Production</h4>
<div class="code-block">
<span class="comment"># Build and start all services</span><br>
<span class="cmd">docker-compose up -d</span>
</div>
</div>
</div>
</section>
<!-- Troubleshooting -->
<section>
<h2 class="section-title"><span class="num">9</span> Troubleshooting</h2>
<div class="troubleshoot-item">
<strong>Elasticsearch not starting</strong>
<p>Check memory limits in docker-compose.yml</p>
</div>
<div class="troubleshoot-item">
<strong>File upload failures</strong>
<p>Ensure <code style="color:var(--accent-green);">uploads/</code> and <code style="color:var(--accent-green);">temp/</code> directories exist with proper permissions</p>
</div>
<div class="troubleshoot-item">
<strong>Vision pipeline errors</strong>
<p>Verify LibreOffice server is running and accessible at port 8100</p>
</div>
<div class="troubleshoot-item">
<strong>API key errors</strong>
<p>Check environment variables in <code style="color:var(--accent-green);">server/.env</code></p>
</div>
<div class="troubleshoot-item">
<strong>Database reset</strong>
<p>Delete <code style="color:var(--accent-green);">server/data/metadata.db</code> and Elasticsearch data volume</p>
</div>
</section>
<!-- Debugging -->
<section>
<h2 class="section-title"><span class="num">10</span> Debugging &amp; Health Checks</h2>
<div class="code-block">
<span class="comment"># Check Elasticsearch</span><br>
<span class="cmd">curl http://localhost:9200/_cat/indices</span><br><br>
<span class="comment"># Check Tika</span><br>
<span class="cmd">curl http://localhost:9998/tika</span><br><br>
<span class="comment"># Check LibreOffice</span><br>
<span class="cmd">curl http://localhost:8100/health</span>
</div>
</section>
</div>
<footer>
Simple Knowledge Base &mdash; Full-stack RAG Q&A System &mdash; React 19 + NestJS + Elasticsearch
</footer>
</body>
</html>