690529:1116 ADR-030-230 context aware #04
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
// Change Log
|
// Change Log
|
||||||
// - 2026-05-21: เพิ่ม getSystemHealth พร้อมระบบแคช Redis 30 วินาทีตาม ADR-027.
|
// - 2026-05-21: เพิ่ม getSystemHealth พร้อมระบบแคช Redis 30 วินาทีตาม ADR-027.
|
||||||
// - 2026-05-21: แก้ไข ESLint unsafe return error ใน getSystemHealth โดยใช้ interface SystemHealthResponse
|
// - 2026-05-21: แก้ไข ESLint unsafe return error ใน getSystemHealth โดยใช้ interface SystemHealthResponse
|
||||||
|
// - 2026-05-29: เพิ่ม OcrService.checkHealth() เข้า getSystemHealth() เพื่อแสดงสถานะ OCR sidecar
|
||||||
import { Injectable, Logger, Optional } from '@nestjs/common';
|
import { Injectable, Logger, Optional } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { HttpService } from '@nestjs/axios';
|
import { HttpService } from '@nestjs/axios';
|
||||||
@@ -45,6 +46,7 @@ import { AiBatchJobData } from './processors/ai-batch.processor';
|
|||||||
import { AuditLog } from '../../common/entities/audit-log.entity';
|
import { AuditLog } from '../../common/entities/audit-log.entity';
|
||||||
import { OllamaService } from './services/ollama.service';
|
import { OllamaService } from './services/ollama.service';
|
||||||
import { AiQdrantService } from './qdrant.service';
|
import { AiQdrantService } from './qdrant.service';
|
||||||
|
import { OcrService, OcrHealthResult } from './services/ocr.service';
|
||||||
|
|
||||||
// ผลลัพธ์ของ Real-time Extraction
|
// ผลลัพธ์ของ Real-time Extraction
|
||||||
export interface ExtractionResult {
|
export interface ExtractionResult {
|
||||||
@@ -120,6 +122,7 @@ export interface SystemHealthResponse {
|
|||||||
collections?: string[];
|
collections?: string[];
|
||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
ocr: OcrHealthResult;
|
||||||
queues: {
|
queues: {
|
||||||
realtime:
|
realtime:
|
||||||
| {
|
| {
|
||||||
@@ -176,6 +179,8 @@ export class AiService {
|
|||||||
@Optional()
|
@Optional()
|
||||||
private readonly qdrantService?: AiQdrantService,
|
private readonly qdrantService?: AiQdrantService,
|
||||||
@Optional()
|
@Optional()
|
||||||
|
private readonly ocrService?: OcrService,
|
||||||
|
@Optional()
|
||||||
@InjectRedis()
|
@InjectRedis()
|
||||||
private readonly redis?: Redis
|
private readonly redis?: Redis
|
||||||
) {
|
) {
|
||||||
@@ -816,7 +821,7 @@ export class AiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [ollama, qdrant, realtimeQueueMetrics, batchQueueMetrics] =
|
const [ollama, qdrant, ocr, realtimeQueueMetrics, batchQueueMetrics] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.ollamaService
|
this.ollamaService
|
||||||
? this.ollamaService.checkHealth()
|
? this.ollamaService.checkHealth()
|
||||||
@@ -833,12 +838,21 @@ export class AiService {
|
|||||||
latencyMs: 0,
|
latencyMs: 0,
|
||||||
error: 'AiQdrantService not injected',
|
error: 'AiQdrantService not injected',
|
||||||
}),
|
}),
|
||||||
|
this.ocrService
|
||||||
|
? this.ocrService.checkHealth()
|
||||||
|
: Promise.resolve({
|
||||||
|
status: 'DOWN' as const,
|
||||||
|
latencyMs: 0,
|
||||||
|
url: 'not configured',
|
||||||
|
error: 'OcrService not injected',
|
||||||
|
}),
|
||||||
this.getQueueMetrics(this.aiRealtimeQueue),
|
this.getQueueMetrics(this.aiRealtimeQueue),
|
||||||
this.getQueueMetrics(this.aiBatchQueue),
|
this.getQueueMetrics(this.aiBatchQueue),
|
||||||
]);
|
]);
|
||||||
const health = {
|
const health = {
|
||||||
ollama,
|
ollama,
|
||||||
qdrant,
|
qdrant,
|
||||||
|
ocr,
|
||||||
queues: {
|
queues: {
|
||||||
realtime: realtimeQueueMetrics,
|
realtime: realtimeQueueMetrics,
|
||||||
batch: batchQueueMetrics,
|
batch: batchQueueMetrics,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// - 2026-05-15: เพิ่ม OCR auto-detection service สำหรับ ADR-023A.
|
// - 2026-05-15: เพิ่ม OCR auto-detection service สำหรับ ADR-023A.
|
||||||
// - 2026-05-25: แก้ไข AggregateError (empty message) จาก axios โดย wrap เป็น Error พร้อม context ที่ชัดเจน.
|
// - 2026-05-25: แก้ไข AggregateError (empty message) จาก axios โดย wrap เป็น Error พร้อม context ที่ชัดเจน.
|
||||||
// - 2026-05-25: เพิ่ม path remapping (OCR_UPLOAD_BASE_PATH) เพื่อแปลง local upload path เป็น path ที่ sidecar เห็นผ่าน CIFS.
|
// - 2026-05-25: เพิ่ม path remapping (OCR_UPLOAD_BASE_PATH) เพื่อแปลง local upload path เป็น path ที่ sidecar เห็นผ่าน CIFS.
|
||||||
|
// - 2026-05-29: เพิ่ม checkHealth() เพื่อตรวจสอบสุขภาพของ PaddleOCR sidecar สำหรับ getSystemHealth() (ADR-027)
|
||||||
|
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
@@ -23,6 +24,13 @@ interface PaddleOcrResponse {
|
|||||||
text?: string;
|
text?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OcrHealthResult {
|
||||||
|
status: 'HEALTHY' | 'DOWN';
|
||||||
|
latencyMs: number;
|
||||||
|
url: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/** บริการเลือก fast path หรือ PaddleOCR sidecar ตามจำนวนตัวอักษรที่ extract ได้ */
|
/** บริการเลือก fast path หรือ PaddleOCR sidecar ตามจำนวนตัวอักษรที่ extract ได้ */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OcrService {
|
export class OcrService {
|
||||||
@@ -56,6 +64,28 @@ export class OcrService {
|
|||||||
return localPath;
|
return localPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ตรวจสอบสุขภาพและ latency ของ PaddleOCR sidecar ผ่าน GET /health */
|
||||||
|
async checkHealth(): Promise<OcrHealthResult> {
|
||||||
|
const startTime = Date.now();
|
||||||
|
try {
|
||||||
|
await axios.get(`${this.ocrApiUrl}/health`, { timeout: 5000 });
|
||||||
|
return {
|
||||||
|
status: 'HEALTHY',
|
||||||
|
latencyMs: Date.now() - startTime,
|
||||||
|
url: this.ocrApiUrl,
|
||||||
|
};
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const cause = err instanceof Error ? err.message : String(err);
|
||||||
|
this.logger.warn(`OCR sidecar health check failed: ${cause}`);
|
||||||
|
return {
|
||||||
|
status: 'DOWN',
|
||||||
|
latencyMs: Date.now() - startTime,
|
||||||
|
url: this.ocrApiUrl,
|
||||||
|
error: cause,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** ตรวจสอบ text layer ก่อนเลือก OCR slow path */
|
/** ตรวจสอบ text layer ก่อนเลือก OCR slow path */
|
||||||
async detectAndExtract(
|
async detectAndExtract(
|
||||||
input: OcrDetectionInput
|
input: OcrDetectionInput
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Brain, Loader2, Power, ShieldCheck, Cpu, Database, Activity, Search, Info, HelpCircle, AlertCircle, Settings2, Trash2 } from 'lucide-react';
|
import { Brain, Loader2, Power, ShieldCheck, Cpu, Database, Activity, Search, Info, HelpCircle, AlertCircle, Settings2, Trash2, ScanText } from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@@ -268,6 +268,28 @@ export default function AiAdminConsolePage() {
|
|||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card className="relative overflow-hidden border border-border/50 bg-background/50 backdrop-blur-md">
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
||||||
|
<ScanText className="h-4 w-4 text-primary" />
|
||||||
|
PaddleOCR Sidecar
|
||||||
|
</CardTitle>
|
||||||
|
{isHealthLoading ? <Loader2 className="h-3 w-3 animate-spin text-muted-foreground" /> : renderStatusBadge(health?.ocr?.status)}
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||||
|
<span>ความเร็วตอบสนอง</span>
|
||||||
|
<span className="font-semibold text-foreground">{health?.ocr?.latencyMs !== undefined ? `${health.ocr.latencyMs} ms` : '-'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||||
|
<span>URL</span>
|
||||||
|
<span className="font-mono text-[10px] text-foreground truncate max-w-[160px]">{health?.ocr?.url ?? '-'}</span>
|
||||||
|
</div>
|
||||||
|
{health?.ocr?.error && (
|
||||||
|
<p className="mt-1 text-[10px] text-destructive line-clamp-2">{health.ocr.error}</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
<Card className="relative overflow-hidden border border-border/50 bg-background/50 backdrop-blur-md">
|
<Card className="relative overflow-hidden border border-border/50 bg-background/50 backdrop-blur-md">
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||||
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// - 2026-05-21: เพิ่ม API service สำหรับ Superadmin Sandbox RAG (T037).
|
// - 2026-05-21: เพิ่ม API service สำหรับ Superadmin Sandbox RAG (T037).
|
||||||
// - 2026-05-21: เพิ่ม service method `submitSandboxExtract` สำหรับอัปโหลดไฟล์ใน OCR Sandbox (T043).
|
// - 2026-05-21: เพิ่ม service method `submitSandboxExtract` สำหรับอัปโหลดไฟล์ใน OCR Sandbox (T043).
|
||||||
// - 2026-05-25: เพิ่ม methods สำหรับจัดการโมเดล AI แบบไดนามิก (ADR-027).
|
// - 2026-05-25: เพิ่ม methods สำหรับจัดการโมเดล AI แบบไดนามิก (ADR-027).
|
||||||
|
// - 2026-05-29: เพิ่ม ocr field ใน AiSystemHealth interface ตาม OcrService.checkHealth()
|
||||||
|
|
||||||
import api from '../api/client';
|
import api from '../api/client';
|
||||||
|
|
||||||
@@ -34,6 +35,12 @@ export interface AiSystemHealth {
|
|||||||
collections?: string[];
|
collections?: string[];
|
||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
ocr: {
|
||||||
|
status: 'HEALTHY' | 'DOWN';
|
||||||
|
latencyMs: number;
|
||||||
|
url: string;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
queues: {
|
queues: {
|
||||||
realtime: QueueMetrics;
|
realtime: QueueMetrics;
|
||||||
batch: QueueMetrics;
|
batch: QueueMetrics;
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
# docker compose up -d --build
|
# docker compose up -d --build
|
||||||
#
|
#
|
||||||
# ทดสอบ:
|
# ทดสอบ:
|
||||||
# curl http://localhost:8765/health
|
# curl http://192.168.10.100:8765/health
|
||||||
|
|
||||||
name: lcbp3-ocr
|
name: lcbp3-ocr
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user