From 12e230332e24f1f84e26659f76dce11dfd5099ee Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 19 Jun 2026 11:28:59 +0700 Subject: [PATCH] 690619:1128 240 #02 --- .../ai/services/vram-monitor.service.ts | 23 ++- .../ai/tests/vram-monitor.service.spec.ts | 16 ++ frontend/app/(admin)/admin/ai/page.tsx | 161 +++++++++--------- 3 files changed, 120 insertions(+), 80 deletions(-) diff --git a/backend/src/modules/ai/services/vram-monitor.service.ts b/backend/src/modules/ai/services/vram-monitor.service.ts index 11790ebf..3f7988f1 100644 --- a/backend/src/modules/ai/services/vram-monitor.service.ts +++ b/backend/src/modules/ai/services/vram-monitor.service.ts @@ -138,10 +138,31 @@ export class VramMonitorService { /** * ตรวจสอบว่า VRAM เพียงพอสำหรับความต้องการโหลดโมเดลหรือไม่ + * ถ้าไม่มีโมเดลโหลดอยู่เลย จะอนุญาตให้โหลดโมเดลใหม่ได้เสมอ (ป้องกัน false positive) */ async hasVramCapacity(requiredMb: number): Promise { const headroom = await this.getVramHeadroom(); - return headroom.availableMb >= requiredMb; + // ถ้าไม่มีโมเดลโหลดอยู่เลย อนุญาตให้โหลดโมเดลใหม่ได้เสมอ + if (headroom.usedMb === 0 && headroom.querySuccess) { + this.logger.log( + `No models loaded in VRAM, allowing model load (required=${requiredMb}MB)` + ); + return true; + } + // ถ้า query ล้มเหลว ใช้ optimistic fallback (assume no VRAM used) + if (!headroom.querySuccess) { + this.logger.log( + `VRAM query failed, using optimistic fallback (required=${requiredMb}MB)` + ); + return true; + } + const hasCapacity = headroom.availableMb >= requiredMb; + if (!hasCapacity) { + this.logger.warn( + `VRAM insufficient: available=${headroom.availableMb}MB, required=${requiredMb}MB` + ); + } + return hasCapacity; } /** diff --git a/backend/src/modules/ai/tests/vram-monitor.service.spec.ts b/backend/src/modules/ai/tests/vram-monitor.service.spec.ts index 016c88b2..935c977e 100644 --- a/backend/src/modules/ai/tests/vram-monitor.service.spec.ts +++ b/backend/src/modules/ai/tests/vram-monitor.service.spec.ts @@ -100,6 +100,22 @@ describe('VramMonitorService', () => { const result = await service.hasVramCapacity(3000); // query available is 2048MB, required 3000MB expect(result).toBe(false); }); + + it('ควรคืน true เมื่อไม่มีโมเดลโหลดอยู่เลย (ป้องกัน false positive)', async () => { + mockedAxios.get.mockResolvedValue({ + data: { + models: [], // ไม่มีโมเดลโหลด + }, + }); + const result = await service.hasVramCapacity(5000); // ต้องการ 5GB แม้ availableMb = 8192MB + expect(result).toBe(true); + }); + + it('ควรคืน true เมื่อ query ล้มเหลว (optimistic fallback)', async () => { + mockedAxios.get.mockRejectedValue(new Error('Connection timeout')); + const result = await service.hasVramCapacity(5000); + expect(result).toBe(true); + }); }); describe('getVramStatus', () => { it('ควรคืน status ที่ถูกต้องเมื่อ Ollama คืน models', async () => { diff --git a/frontend/app/(admin)/admin/ai/page.tsx b/frontend/app/(admin)/admin/ai/page.tsx index 2b3c66be..a8fdea48 100644 --- a/frontend/app/(admin)/admin/ai/page.tsx +++ b/frontend/app/(admin)/admin/ai/page.tsx @@ -615,85 +615,6 @@ export default function AiAdminConsolePage() { - - - - - System Toggle - - - -
-
-
- {aiEnabled ? 'AI พร้อมให้ผู้ใช้ทั่วไปใช้งาน' : 'AI ถูกปิดสำหรับผู้ใช้ทั่วไป'} -
-
- Superadmin ยังสามารถเข้าถึงส่วนทดสอบและดูแลระบบได้ตามสิทธิ์ -
-
- Active Models: - - {isHealthLoading ? 'Loading...' : toCanonicalModel(health?.activeModels?.main ?? 'np-dms-ai')} - - + - - {isHealthLoading ? 'Loading...' : toCanonicalModel(health?.activeModels?.ocr ?? 'np-dms-ocr')} - -
-
-
- {busy && } - -
-
- {isError && ( -
- ไม่สามารถโหลดสถานะ AI ได้ กรุณาลองใหม่อีกครั้ง -
- )} -
-
- -
- - - - - Protection - - - - เมื่อปิด AI ระบบจะบล็อก AI inference endpoints สำหรับผู้ใช้ทั่วไปด้วย HTTP 503 - และให้ผู้ใช้กรอกข้อมูลเองชั่วคราว - - - - - Polling - - - - อัปเดตสถานะทุก 30 วินาที - {(isFetching || isHealthLoading) && !(isLoading || isHealthLoading) ? ' (กำลังรีเฟรช)' : ''} - - - - -
System Toggle @@ -701,6 +622,88 @@ export default function AiAdminConsolePage() { 3-Step Pipeline Sandbox + + + + + + System Toggle + + + +
+
+
+ {aiEnabled ? 'AI พร้อมให้ผู้ใช้ทั่วไปใช้งาน' : 'AI ถูกปิดสำหรับผู้ใช้ทั่วไป'} +
+
+ Superadmin ยังสามารถเข้าถึงส่วนทดสอบและดูแลระบบได้ตามสิทธิ์ +
+
+ Active Models: + + {isHealthLoading ? 'Loading...' : toCanonicalModel(health?.activeModels?.main ?? 'np-dms-ai')} + + + + + {isHealthLoading ? 'Loading...' : toCanonicalModel(health?.activeModels?.ocr ?? 'np-dms-ocr')} + +
+
+
+ {busy && } + +
+
+ {isError && ( +
+ ไม่สามารถโหลดสถานะ AI ได้ กรุณาลองใหม่อีกครั้ง +
+ )} +
+
+ +
+ + + + + Protection + + + + เมื่อปิด AI ระบบจะบล็อก AI inference endpoints สำหรับผู้ใช้ทั่วไปด้วย HTTP 503 + และให้ผู้ใช้กรอกข้อมูลเองชั่วคราว + + + + + Polling + + + + อัปเดตสถานะทุก 30 วินาที + {(isFetching || isHealthLoading) && !(isLoading || isHealthLoading) ? ' (กำลังรีเฟรช)' : ''} + + + + +
+
+