feat(ai-admin-console): complete implementation and resolve lint compilation errors

This commit is contained in:
2026-05-21 21:42:25 +07:00
parent 1580ab2c18
commit 91e9c714df
39 changed files with 3724 additions and 72 deletions
+20 -1
View File
@@ -107,13 +107,21 @@ export interface ApiErrorResponse {
error: ApiErrorPayload;
}
export const AI_FEATURES_UNAVAILABLE_EVENT = 'ai-features-unavailable';
// แปลง Axios error เป็น Structured Error Response (ADR-007)
export function parseApiError(axiosError: AxiosError): ApiErrorResponse {
if (axiosError.response?.data) {
const data = axiosError.response.data;
// กรณีที่ backend ส่ง { error: { ... } } ตาม ADR-007
if (typeof data === 'object' && data !== null && 'error' in data) {
return data as ApiErrorResponse;
const parsed = data as ApiErrorResponse;
return {
error: {
...parsed.error,
statusCode: axiosError.response.status,
},
};
}
// กรณี NestJS validation error { message: [...], statusCode: 400 }
if (typeof data === 'object' && data !== null && 'message' in data) {
@@ -181,6 +189,17 @@ apiClient.interceptors.response.use(
}
// แปลง error เป็น structured format ตาม ADR-007 ก่อน reject
const structuredError = parseApiError(error);
if (
structuredError.error.statusCode === 503 &&
structuredError.error.code === 'AI_FEATURES_UNAVAILABLE' &&
typeof window !== 'undefined'
) {
window.dispatchEvent(
new CustomEvent(AI_FEATURES_UNAVAILABLE_EVENT, {
detail: structuredError.error,
})
);
}
return Promise.reject(structuredError);
}
);
+113
View File
@@ -0,0 +1,113 @@
// File: lib/services/admin-ai.service.ts
// Change Log
// - 2026-05-21: เพิ่ม service สำหรับ AI Admin Console toggle API.
// - 2026-05-21: เพิ่ม service method `getHealth` สำหรับดึงข้อมูลสุขภาพของระบบ AI (T028).
// - 2026-05-21: เพิ่ม API service สำหรับ Superadmin Sandbox RAG (T037).
// - 2026-05-21: เพิ่ม service method `submitSandboxExtract` สำหรับอัปโหลดไฟล์ใน OCR Sandbox (T043).
import api from '../api/client';
export interface AiAdminSettings {
aiFeaturesEnabled: boolean;
}
export interface QueueMetrics {
active?: number;
waiting?: number;
failed?: number;
completed?: number;
isPaused?: boolean;
error?: string;
}
export interface AiSystemHealth {
ollama: {
status: 'HEALTHY' | 'DEGRADED' | 'DOWN';
latencyMs: number;
models: string[];
error?: string;
};
qdrant: {
status: 'HEALTHY' | 'DEGRADED' | 'DOWN';
latencyMs: number;
collections?: string[];
error?: string;
};
queues: {
realtime: QueueMetrics;
batch: QueueMetrics;
};
timestamp: string;
}
export interface AiRagCitation {
pointId: string | number;
score: number;
docType?: string;
docNumber?: string;
snippet?: string;
}
export interface AiSandboxJobResult {
requestPublicId: string;
status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled' | 'not_found';
answer?: string;
citations?: AiRagCitation[];
confidence?: number;
usedFallbackModel?: boolean;
errorMessage?: string;
completedAt?: string;
}
const extractData = <T>(value: unknown): T => {
if (value && typeof value === 'object' && 'data' in value) {
return (value as { data: T }).data;
}
return value as T;
};
/** Service สำหรับเรียก AI Admin Console API ผ่าน DMS Backend เท่านั้น */
export const adminAiService = {
getStatus: async (): Promise<AiAdminSettings> => {
const { data } = await api.get('/ai/status');
return extractData<AiAdminSettings>(data);
},
getSettings: async (): Promise<AiAdminSettings> => {
const { data } = await api.get('/ai/admin/settings');
return extractData<AiAdminSettings>(data);
},
toggleFeatures: async (enabled: boolean): Promise<AiAdminSettings> => {
const { data } = await api.post('/ai/admin/toggle', { enabled });
return extractData<AiAdminSettings>(data);
},
getHealth: async (): Promise<AiSystemHealth> => {
const { data } = await api.get('/ai/admin/health');
return extractData<AiSystemHealth>(data);
},
submitSandboxRag: async (
projectPublicId: string,
question: string
): Promise<{ requestPublicId: string; jobId: string; status: string }> => {
const { data } = await api.post('/ai/admin/sandbox/rag', {
projectPublicId,
question,
});
return extractData<{ requestPublicId: string; jobId: string; status: string }>(data);
},
getSandboxJobStatus: async (id: string): Promise<AiSandboxJobResult> => {
const { data } = await api.get(`/ai/admin/sandbox/job/${id}`);
return extractData<AiSandboxJobResult>(data);
},
submitSandboxExtract: async (
file: File
): Promise<{ requestPublicId: string; jobId: string; status: string }> => {
const formData = new FormData();
formData.append('file', file);
const { data } = await api.post('/ai/admin/sandbox/extract', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return extractData<{ requestPublicId: string; jobId: string; status: string }>(data);
},
};