feat(migrations): Rename indexes and update global integration settings fields for improved clarity and functionality

feat(admin): Add API monitoring, debug console, and system health templates for enhanced admin interface

docs: Add AI system cleanup summary and audit report detailing architecture, token management, and recommendations

docs: Introduce credits and tokens system guide outlining configuration, data flow, and monitoring strategies
This commit is contained in:
IGNY8 VPS (Salman)
2025-12-20 12:55:05 +00:00
parent eb6cba7920
commit 3283a83b42
51 changed files with 3578 additions and 5434 deletions

View File

@@ -22,7 +22,6 @@ import { FileIcon, DownloadIcon, BoltIcon, TaskIcon, ImageIcon, CheckCircleIcon
import { createImagesPageConfig } from '../../config/pages/images.config';
import ImageQueueModal, { ImageQueueItem } from '../../components/common/ImageQueueModal';
import SingleRecordStatusUpdateModal from '../../components/common/SingleRecordStatusUpdateModal';
import { useResourceDebug } from '../../hooks/useResourceDebug';
import PageHeader from '../../components/common/PageHeader';
import ModuleNavigationTabs from '../../components/navigation/ModuleNavigationTabs';
import { Modal } from '../../components/ui/modal';
@@ -30,33 +29,6 @@ import { Modal } from '../../components/ui/modal';
export default function Images() {
const toast = useToast();
// Resource Debug toggle - controls AI Function Logs
const resourceDebugEnabled = useResourceDebug();
// AI Function Logs state
const [aiLogs, setAiLogs] = useState<Array<{
timestamp: string;
type: 'request' | 'success' | 'error' | 'step';
action: string;
data: any;
stepName?: string;
percentage?: number;
}>>([]);
// Helper function to add log entry (only if Resource Debug is enabled)
const addAiLog = useCallback((log: {
timestamp: string;
type: 'request' | 'success' | 'error' | 'step';
action: string;
data: any;
stepName?: string;
percentage?: number;
}) => {
if (resourceDebugEnabled) {
setAiLogs(prev => [...prev, log]);
}
}, [resourceDebugEnabled]);
// Data state
const [images, setImages] = useState<ContentImagesGroup[]>([]);
const [loading, setLoading] = useState(true);
@@ -373,36 +345,16 @@ export default function Images() {
console.log('[Generate Images] Max in-article images from settings:', maxInArticleImages);
// STAGE 2: Start actual generation
addAiLog({
timestamp: new Date().toISOString(),
type: 'request',
action: 'generate_images',
data: { imageIds, contentId, totalImages: imageIds.length }
});
const result = await generateImages(imageIds, contentId);
if (result.success && result.task_id) {
// Task started successfully - polling will be handled by ImageQueueModal
setTaskId(result.task_id);
console.log('[Generate Images] Stage 2: Task started with ID:', result.task_id);
addAiLog({
timestamp: new Date().toISOString(),
type: 'step',
action: 'generate_images',
stepName: 'Task Queued',
data: { task_id: result.task_id, message: 'Image generation task queued' }
});
} else {
toast.error(result.error || 'Failed to start image generation');
setIsQueueModalOpen(false);
setTaskId(null);
addAiLog({
timestamp: new Date().toISOString(),
type: 'error',
action: 'generate_images',
data: { error: result.error || 'Failed to start image generation' }
});
}
} catch (error: any) {
@@ -581,7 +533,6 @@ export default function Images() {
model={imageModel || undefined}
provider={imageProvider || undefined}
onUpdateQueue={setImageQueue}
onLog={addAiLog}
/>
{/* Status Update Modal */}
@@ -623,74 +574,6 @@ export default function Images() {
</div>
)}
</Modal>
{/* AI Function Logs - Display below table (only when Resource Debug is enabled) */}
{resourceDebugEnabled && aiLogs.length > 0 && (
<div className="mt-6 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
AI Function Logs
</h3>
<button
onClick={() => setAiLogs([])}
className="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
>
Clear Logs
</button>
</div>
<div className="space-y-2 max-h-96 overflow-y-auto">
{aiLogs.slice().reverse().map((log, index) => (
<div
key={index}
className={`p-3 rounded border text-xs font-mono ${
log.type === 'request'
? 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800'
: log.type === 'success'
? 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800'
: log.type === 'error'
? 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800'
: 'bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800'
}`}
>
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2 flex-wrap">
<span className={`font-semibold ${
log.type === 'request'
? 'text-blue-700 dark:text-blue-300'
: log.type === 'success'
? 'text-green-700 dark:text-green-300'
: log.type === 'error'
? 'text-red-700 dark:text-red-300'
: 'text-purple-700 dark:text-purple-300'
}`}>
[{log.type.toUpperCase()}]
</span>
<span className="text-gray-700 dark:text-gray-300">
{log.action}
</span>
{log.stepName && (
<span className="text-xs px-2 py-0.5 rounded bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
{log.stepName}
</span>
)}
{log.percentage !== undefined && (
<span className="text-xs text-gray-500 dark:text-gray-400">
{log.percentage}%
</span>
)}
</div>
<span className="text-gray-500 dark:text-gray-400">
{new Date(log.timestamp).toLocaleTimeString()}
</span>
</div>
<pre className="text-xs text-gray-700 dark:text-gray-300 whitespace-pre-wrap break-words">
{JSON.stringify(log.data, null, 2)}
</pre>
</div>
))}
</div>
</div>
)}
</>
);
}

View File

@@ -23,7 +23,6 @@ import {
import FormModal from '../../components/common/FormModal';
import ProgressModal from '../../components/common/ProgressModal';
import { useProgressModal } from '../../hooks/useProgressModal';
import { useResourceDebug } from '../../hooks/useResourceDebug';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { TaskIcon, PlusIcon, DownloadIcon, FileIcon, ImageIcon, CheckCircleIcon } from '../../icons';
import { createTasksPageConfig } from '../../config/pages/tasks.config';
@@ -139,36 +138,11 @@ export default function Tasks() {
}, [tasks, totalCount]);
// AI Function Logs state
const [aiLogs, setAiLogs] = useState<Array<{
timestamp: string;
type: 'request' | 'success' | 'error' | 'step';
action: string;
data: any;
stepName?: string;
percentage?: number;
}>>([]);
// Resource Debug toggle - controls AI Function Logs
const resourceDebugEnabled = useResourceDebug();
// Track last logged step to avoid duplicates
const lastLoggedStepRef = useRef<string | null>(null);
const lastLoggedPercentageRef = useRef<number>(-1);
const hasReloadedRef = useRef<boolean>(false);
// Helper function to add log entry (only if Resource Debug is enabled)
const addAiLog = useCallback((log: {
timestamp: string;
type: 'request' | 'success' | 'error' | 'step';
action: string;
data: any;
stepName?: string;
percentage?: number;
}) => {
if (resourceDebugEnabled) {
setAiLogs(prev => [...prev, log]);
}
}, [resourceDebugEnabled]);
// Load clusters for filter dropdown
useEffect(() => {
@@ -311,65 +285,23 @@ export default function Tasks() {
// return;
// }
const requestData = {
ids: [row.id],
task_title: row.title,
task_id: row.id,
};
// Log request
addAiLog({
timestamp: new Date().toISOString(),
type: 'request',
action: 'generate_content (Row Action)',
data: requestData,
});
try {
const result = await autoGenerateContent([row.id]);
if (result.success) {
if (result.task_id) {
// Log success with task_id
addAiLog({
timestamp: new Date().toISOString(),
type: 'success',
action: 'generate_content (Row Action)',
data: { task_id: result.task_id, message: result.message },
});
// Async task - show progress modal
progressModal.openModal(result.task_id, 'Generating Content', 'ai-generate-content-03');
toast.success('Content generation started');
} else {
// Log success with results
addAiLog({
timestamp: new Date().toISOString(),
type: 'success',
action: 'generate_content (Row Action)',
data: { tasks_updated: result.tasks_updated || 0, message: result.message },
});
// Synchronous completion
toast.success(`Content generated successfully: ${result.tasks_updated || 0} article generated`);
await loadTasks();
}
} else {
// Log error
addAiLog({
timestamp: new Date().toISOString(),
type: 'error',
action: 'generate_content (Row Action)',
data: { error: result.error || 'Failed to generate content' },
});
toast.error(result.error || 'Failed to generate content');
}
} catch (error: any) {
// Log error
addAiLog({
timestamp: new Date().toISOString(),
type: 'error',
action: 'generate_content (Row Action)',
data: { error: error.message || 'Unknown error occurred' },
});
toast.error(`Failed to generate content: ${error.message}`);
}
}
@@ -389,64 +321,23 @@ export default function Tasks() {
}
const numIds = ids.map(id => parseInt(id));
const selectedTasks = tasks.filter(t => numIds.includes(t.id));
const requestData = {
ids: numIds,
task_count: numIds.length,
task_titles: selectedTasks.map(t => t.title),
};
// Log request
addAiLog({
timestamp: new Date().toISOString(),
type: 'request',
action: 'generate_images (Bulk Action)',
data: requestData,
});
try {
const result = await autoGenerateImages(numIds);
if (result.success) {
if (result.task_id) {
// Log success with task_id
addAiLog({
timestamp: new Date().toISOString(),
type: 'success',
action: 'generate_images (Bulk Action)',
data: { task_id: result.task_id, message: result.message, task_count: numIds.length },
});
// Async task - show progress modal
progressModal.openModal(result.task_id, 'Generating Images');
toast.success('Image generation started');
} else {
// Log success with results
addAiLog({
timestamp: new Date().toISOString(),
type: 'success',
action: 'generate_images (Bulk Action)',
data: { images_created: result.images_created || 0, message: result.message, task_count: numIds.length },
});
// Synchronous completion
toast.success(`Image generation complete: ${result.images_created || 0} images generated`);
await loadTasks();
}
} else {
// Log error
addAiLog({
timestamp: new Date().toISOString(),
type: 'error',
action: 'generate_images (Bulk Action)',
data: { error: result.error || 'Failed to generate images', task_count: numIds.length },
});
toast.error(result.error || 'Failed to generate images');
}
} catch (error: any) {
// Log error
addAiLog({
timestamp: new Date().toISOString(),
type: 'error',
action: 'generate_images (Bulk Action)',
data: { error: error.message || 'Unknown error occurred', task_count: numIds.length },
});
toast.error(`Failed to generate images: ${error.message}`);
}
} else {
@@ -454,96 +345,9 @@ export default function Tasks() {
}
}, [toast, loadTasks, progressModal, tasks]);
// Log AI function progress steps
useEffect(() => {
if (!progressModal.taskId || !progressModal.isOpen) {
return;
}
const progress = progressModal.progress;
const currentStep = progress.details?.phase || '';
const currentPercentage = progress.percentage;
const currentMessage = progress.message;
const currentStatus = progress.status;
// Log step changes
if (currentStep && currentStep !== lastLoggedStepRef.current) {
const stepType = currentStatus === 'error' ? 'error' :
currentStatus === 'completed' ? 'success' : 'step';
addAiLog({
timestamp: new Date().toISOString(),
type: stepType,
action: progressModal.title || 'AI Function',
stepName: currentStep,
percentage: currentPercentage,
data: {
step: currentStep,
message: currentMessage,
percentage: currentPercentage,
status: currentStatus,
details: progress.details,
},
});
lastLoggedStepRef.current = currentStep;
lastLoggedPercentageRef.current = currentPercentage;
}
// Log percentage changes for same step (if significant change)
else if (currentStep && Math.abs(currentPercentage - lastLoggedPercentageRef.current) >= 10) {
const stepType = currentStatus === 'error' ? 'error' :
currentStatus === 'completed' ? 'success' : 'step';
addAiLog({
timestamp: new Date().toISOString(),
type: stepType,
action: progressModal.title || 'AI Function',
stepName: currentStep,
percentage: currentPercentage,
data: {
step: currentStep,
message: currentMessage,
percentage: currentPercentage,
status: currentStatus,
details: progress.details,
},
});
lastLoggedPercentageRef.current = currentPercentage;
}
// Log status changes (error, completed)
else if (currentStatus === 'error' || currentStatus === 'completed') {
// Only log if we haven't already logged this status for this step
if (currentStep !== lastLoggedStepRef.current ||
(currentStatus === 'error' && lastLoggedStepRef.current !== 'error') ||
(currentStatus === 'completed' && lastLoggedStepRef.current !== 'completed')) {
const stepType = currentStatus === 'error' ? 'error' : 'success';
addAiLog({
timestamp: new Date().toISOString(),
type: stepType,
action: progressModal.title || 'AI Function',
stepName: currentStep || 'Final',
percentage: currentPercentage,
data: {
step: currentStep || 'Final',
message: currentMessage,
percentage: currentPercentage,
status: currentStatus,
details: progress.details,
},
});
lastLoggedStepRef.current = currentStep || currentStatus;
}
}
}, [progressModal.progress, progressModal.taskId, progressModal.isOpen, progressModal.title, addAiLog]);
// Reset step tracking when modal closes or opens
// Reset reload flag when modal closes or opens
useEffect(() => {
if (!progressModal.isOpen) {
lastLoggedStepRef.current = null;
lastLoggedPercentageRef.current = -1;
hasReloadedRef.current = false; // Reset reload flag when modal closes
} else {
// Reset reload flag when modal opens for a new task
@@ -804,74 +608,6 @@ export default function Tasks() {
}}
/>
{/* AI Function Logs - Display below table (only when Resource Debug is enabled) */}
{resourceDebugEnabled && aiLogs.length > 0 && (
<div className="mt-6 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
AI Function Logs
</h3>
<button
onClick={() => setAiLogs([])}
className="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
>
Clear Logs
</button>
</div>
<div className="space-y-2 max-h-96 overflow-y-auto">
{aiLogs.slice().reverse().map((log, index) => (
<div
key={index}
className={`p-3 rounded border text-xs font-mono ${
log.type === 'request'
? 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800'
: log.type === 'success'
? 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800'
: log.type === 'error'
? 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800'
: 'bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800'
}`}
>
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2 flex-wrap">
<span className={`font-semibold ${
log.type === 'request'
? 'text-blue-700 dark:text-blue-300'
: log.type === 'success'
? 'text-green-700 dark:text-green-300'
: log.type === 'error'
? 'text-red-700 dark:text-red-300'
: 'text-purple-700 dark:text-purple-300'
}`}>
[{log.type.toUpperCase()}]
</span>
<span className="text-gray-700 dark:text-gray-300">
{log.action}
</span>
{log.stepName && (
<span className="text-xs px-2 py-0.5 rounded bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
{log.stepName}
</span>
)}
{log.percentage !== undefined && (
<span className="text-xs text-gray-500 dark:text-gray-400">
{log.percentage}%
</span>
)}
</div>
<span className="text-gray-500 dark:text-gray-400">
{new Date(log.timestamp).toLocaleTimeString()}
</span>
</div>
<pre className="text-xs text-gray-700 dark:text-gray-300 whitespace-pre-wrap break-words">
{JSON.stringify(log.data, null, 2)}
</pre>
</div>
))}
</div>
</div>
)}
{/* Create/Edit Modal */}
<FormModal
isOpen={isModalOpen}