asd
This commit is contained in:
@@ -182,29 +182,25 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
|||||||
logger.error("Image generation settings not found")
|
logger.error("Image generation settings not found")
|
||||||
return {'success': False, 'error': 'Image generation settings not found'}
|
return {'success': False, 'error': 'Image generation settings not found'}
|
||||||
|
|
||||||
# Get provider API key
|
# Get provider API key (using same approach as test image generation)
|
||||||
|
# Note: API key is stored as 'apiKey' (camelCase) in IntegrationSettings.config
|
||||||
try:
|
try:
|
||||||
if provider == 'openai':
|
provider_settings = IntegrationSettings.objects.get(
|
||||||
provider_settings = IntegrationSettings.objects.get(
|
account=account,
|
||||||
account=account,
|
integration_type=provider, # 'openai' or 'runware'
|
||||||
integration_type='openai',
|
is_active=True
|
||||||
is_active=True
|
)
|
||||||
)
|
api_key = provider_settings.config.get('apiKey') if provider_settings.config else None
|
||||||
api_key = provider_settings.config.get('api_key') if provider_settings.config else None
|
|
||||||
elif provider == 'runware':
|
|
||||||
provider_settings = IntegrationSettings.objects.get(
|
|
||||||
account=account,
|
|
||||||
integration_type='runware',
|
|
||||||
is_active=True
|
|
||||||
)
|
|
||||||
api_key = provider_settings.config.get('api_key') if provider_settings.config else None
|
|
||||||
else:
|
|
||||||
return {'success': False, 'error': f'Unknown provider: {provider}'}
|
|
||||||
|
|
||||||
if not api_key:
|
if not api_key:
|
||||||
return {'success': False, 'error': f'{provider} API key not configured'}
|
logger.error(f"[process_image_generation_queue] {provider.upper()} API key not found in config")
|
||||||
|
return {'success': False, 'error': f'{provider.upper()} API key not configured'}
|
||||||
|
logger.info(f"[process_image_generation_queue] {provider.upper()} API key retrieved successfully")
|
||||||
except IntegrationSettings.DoesNotExist:
|
except IntegrationSettings.DoesNotExist:
|
||||||
return {'success': False, 'error': f'{provider} integration not found'}
|
logger.error(f"[process_image_generation_queue] {provider.upper()} integration settings not found")
|
||||||
|
return {'success': False, 'error': f'{provider.upper()} integration not found or not active'}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[process_image_generation_queue] Error getting {provider} API key: {e}", exc_info=True)
|
||||||
|
return {'success': False, 'error': f'Error retrieving {provider} API key: {str(e)}'}
|
||||||
|
|
||||||
# Get image prompt template (has placeholders: {image_type}, {post_title}, {image_prompt})
|
# Get image prompt template (has placeholders: {image_type}, {post_title}, {image_prompt})
|
||||||
try:
|
try:
|
||||||
@@ -291,8 +287,12 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
|||||||
logger.warning(f"Prompt template formatting failed: {e}, using image prompt directly")
|
logger.warning(f"Prompt template formatting failed: {e}, using image prompt directly")
|
||||||
formatted_prompt = image.prompt
|
formatted_prompt = image.prompt
|
||||||
|
|
||||||
# Generate image
|
# Generate image (using same approach as test image generation)
|
||||||
logger.info(f"Generating image {index}/{total_images} (ID: {image_id})")
|
logger.info(f"[process_image_generation_queue] Generating image {index}/{total_images} (ID: {image_id})")
|
||||||
|
logger.info(f"[process_image_generation_queue] Provider: {provider}, Model: {model}")
|
||||||
|
logger.info(f"[process_image_generation_queue] Prompt length: {len(formatted_prompt)}")
|
||||||
|
logger.info(f"[process_image_generation_queue] Image type: {image_type}")
|
||||||
|
|
||||||
result = ai_core.generate_image(
|
result = ai_core.generate_image(
|
||||||
prompt=formatted_prompt,
|
prompt=formatted_prompt,
|
||||||
provider=provider,
|
provider=provider,
|
||||||
@@ -303,6 +303,8 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
|||||||
function_name='generate_images_from_prompts'
|
function_name='generate_images_from_prompts'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info(f"[process_image_generation_queue] Image generation result: has_url={bool(result.get('url'))}, has_error={bool(result.get('error'))}")
|
||||||
|
|
||||||
# Check for errors
|
# Check for errors
|
||||||
if result.get('error'):
|
if result.get('error'):
|
||||||
logger.error(f"Image generation failed for {image_id}: {result.get('error')}")
|
logger.error(f"Image generation failed for {image_id}: {result.get('error')}")
|
||||||
|
|||||||
@@ -437,6 +437,7 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
model = request.data.get('model', 'dall-e-3')
|
model = request.data.get('model', 'dall-e-3')
|
||||||
|
|
||||||
logger.info(f"[generate_image] Request parameters: provider={provider}, model={model}, image_type={image_type}, image_size={image_size}, prompt_length={len(prompt)}")
|
logger.info(f"[generate_image] Request parameters: provider={provider}, model={model}, image_type={image_type}, image_size={image_size}, prompt_length={len(prompt)}")
|
||||||
|
logger.info(f"[generate_image] IMPORTANT: Using ONLY {provider.upper()} provider for this request. NOT using both providers.")
|
||||||
|
|
||||||
if not prompt:
|
if not prompt:
|
||||||
logger.error("[generate_image] ERROR: Prompt is empty")
|
logger.error("[generate_image] ERROR: Prompt is empty")
|
||||||
@@ -445,79 +446,50 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
'error': 'Prompt is required'
|
'error': 'Prompt is required'
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
# Get API keys from saved settings
|
# Get API key from saved settings for the specified provider only
|
||||||
logger.info("[generate_image] Step 3: Getting API keys from saved settings")
|
logger.info(f"[generate_image] Step 3: Getting API key for provider: {provider}")
|
||||||
from .models import IntegrationSettings
|
from .models import IntegrationSettings
|
||||||
|
|
||||||
# Get OpenAI settings
|
# Only fetch settings for the specified provider
|
||||||
openai_api_key = None
|
api_key = None
|
||||||
openai_enabled = False
|
integration_enabled = False
|
||||||
try:
|
integration_type = provider # 'openai' or 'runware'
|
||||||
openai_settings = IntegrationSettings.objects.get(
|
|
||||||
integration_type='openai',
|
|
||||||
account=account
|
|
||||||
)
|
|
||||||
openai_api_key = openai_settings.config.get('apiKey')
|
|
||||||
openai_enabled = openai_settings.is_active
|
|
||||||
logger.info(f"[generate_image] OpenAI settings found: enabled={openai_enabled}, has_key={bool(openai_api_key)}")
|
|
||||||
except IntegrationSettings.DoesNotExist:
|
|
||||||
logger.warning("[generate_image] OpenAI settings not found in database")
|
|
||||||
openai_api_key = None
|
|
||||||
openai_enabled = False
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[generate_image] Error getting OpenAI settings: {e}")
|
|
||||||
openai_api_key = None
|
|
||||||
openai_enabled = False
|
|
||||||
|
|
||||||
# Get Runware settings
|
|
||||||
runware_api_key = None
|
|
||||||
runware_enabled = False
|
|
||||||
try:
|
try:
|
||||||
runware_settings = IntegrationSettings.objects.get(
|
integration_settings = IntegrationSettings.objects.get(
|
||||||
integration_type='runware',
|
integration_type=integration_type,
|
||||||
account=account
|
account=account
|
||||||
)
|
)
|
||||||
runware_api_key = runware_settings.config.get('apiKey')
|
api_key = integration_settings.config.get('apiKey')
|
||||||
runware_enabled = runware_settings.is_active
|
integration_enabled = integration_settings.is_active
|
||||||
logger.info(f"[generate_image] Runware settings found: enabled={runware_enabled}, has_key={bool(runware_api_key)}")
|
logger.info(f"[generate_image] {integration_type.upper()} settings found: enabled={integration_enabled}, has_key={bool(api_key)}")
|
||||||
except IntegrationSettings.DoesNotExist:
|
except IntegrationSettings.DoesNotExist:
|
||||||
logger.warning("[generate_image] Runware settings not found in database")
|
logger.warning(f"[generate_image] {integration_type.upper()} settings not found in database")
|
||||||
runware_api_key = None
|
api_key = None
|
||||||
runware_enabled = False
|
integration_enabled = False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[generate_image] Error getting Runware settings: {e}")
|
logger.error(f"[generate_image] Error getting {integration_type.upper()} settings: {e}")
|
||||||
runware_api_key = None
|
api_key = None
|
||||||
runware_enabled = False
|
integration_enabled = False
|
||||||
|
|
||||||
# Validate provider and API key
|
# Validate provider and API key
|
||||||
logger.info("[generate_image] Step 4: Validating provider and API key")
|
logger.info(f"[generate_image] Step 4: Validating {provider} provider and API key")
|
||||||
if provider == 'openai':
|
if provider not in ['openai', 'runware']:
|
||||||
logger.info(f"[generate_image] Provider is OpenAI, checking API key: has_key={bool(openai_api_key)}, enabled={openai_enabled}")
|
|
||||||
if not openai_api_key or not openai_enabled:
|
|
||||||
logger.error("[generate_image] ERROR: OpenAI API key not configured or integration not enabled")
|
|
||||||
return Response({
|
|
||||||
'success': False,
|
|
||||||
'error': 'OpenAI API key not configured or integration not enabled'
|
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
api_key = openai_api_key
|
|
||||||
logger.info("[generate_image] OpenAI API key validated")
|
|
||||||
elif provider == 'runware':
|
|
||||||
logger.info(f"[generate_image] Provider is Runware, checking API key: has_key={bool(runware_api_key)}, enabled={runware_enabled}")
|
|
||||||
if not runware_api_key or not runware_enabled:
|
|
||||||
logger.error("[generate_image] ERROR: Runware API key not configured or integration not enabled")
|
|
||||||
return Response({
|
|
||||||
'success': False,
|
|
||||||
'error': 'Runware API key not configured or integration not enabled'
|
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
api_key = runware_api_key
|
|
||||||
logger.info("[generate_image] Runware API key validated")
|
|
||||||
else:
|
|
||||||
logger.error(f"[generate_image] ERROR: Invalid provider: {provider}")
|
logger.error(f"[generate_image] ERROR: Invalid provider: {provider}")
|
||||||
return Response({
|
return Response({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': f'Invalid provider: {provider}'
|
'error': f'Invalid provider: {provider}. Must be "openai" or "runware"'
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if not api_key or not integration_enabled:
|
||||||
|
logger.error(f"[generate_image] ERROR: {provider.upper()} API key not configured or integration not enabled")
|
||||||
|
return Response({
|
||||||
|
'success': False,
|
||||||
|
'error': f'{provider.upper()} API key not configured or integration not enabled'
|
||||||
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
logger.info(f"[generate_image] {provider.upper()} API key validated successfully")
|
||||||
|
|
||||||
# Generate image using AIProcessor
|
# Generate image using AIProcessor
|
||||||
logger.info("[generate_image] Step 5: Creating AIProcessor and generating image")
|
logger.info("[generate_image] Step 5: Creating AIProcessor and generating image")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -55,9 +55,23 @@ export default function ImageQueueModal({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen || !taskId) return;
|
if (!isOpen || !taskId) return;
|
||||||
|
|
||||||
|
let pollAttempts = 0;
|
||||||
|
const maxPollAttempts = 300; // 5 minutes max (300 * 1 second)
|
||||||
|
|
||||||
const pollInterval = setInterval(async () => {
|
const pollInterval = setInterval(async () => {
|
||||||
|
pollAttempts++;
|
||||||
|
|
||||||
|
// Stop polling after max attempts
|
||||||
|
if (pollAttempts > maxPollAttempts) {
|
||||||
|
console.warn('Polling timeout reached, stopping');
|
||||||
|
clearInterval(pollInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log(`[ImageQueueModal] Polling task status (attempt ${pollAttempts}):`, taskId);
|
||||||
const data = await fetchAPI(`/v1/system/settings/task_progress/${taskId}/`);
|
const data = await fetchAPI(`/v1/system/settings/task_progress/${taskId}/`);
|
||||||
|
console.log(`[ImageQueueModal] Task status response:`, data);
|
||||||
|
|
||||||
// Check if data is valid (not HTML error page)
|
// Check if data is valid (not HTML error page)
|
||||||
if (!data || typeof data !== 'object') {
|
if (!data || typeof data !== 'object') {
|
||||||
@@ -67,14 +81,18 @@ export default function ImageQueueModal({
|
|||||||
|
|
||||||
// Check state (task_progress returns 'state', not 'status')
|
// Check state (task_progress returns 'state', not 'status')
|
||||||
const taskState = data.state || data.status;
|
const taskState = data.state || data.status;
|
||||||
|
console.log(`[ImageQueueModal] Task state:`, taskState);
|
||||||
|
|
||||||
if (taskState === 'SUCCESS' || taskState === 'FAILURE') {
|
if (taskState === 'SUCCESS' || taskState === 'FAILURE') {
|
||||||
|
console.log(`[ImageQueueModal] Task completed with state:`, taskState);
|
||||||
clearInterval(pollInterval);
|
clearInterval(pollInterval);
|
||||||
// Update final state
|
// Update final state
|
||||||
if (taskState === 'SUCCESS' && data.result) {
|
if (taskState === 'SUCCESS' && data.result) {
|
||||||
|
console.log(`[ImageQueueModal] Updating queue from result:`, data.result);
|
||||||
updateQueueFromTaskResult(data.result);
|
updateQueueFromTaskResult(data.result);
|
||||||
} else if (taskState === 'SUCCESS' && data.meta && data.meta.result) {
|
} else if (taskState === 'SUCCESS' && data.meta && data.meta.result) {
|
||||||
// Some responses have result in meta
|
// Some responses have result in meta
|
||||||
|
console.log(`[ImageQueueModal] Updating queue from meta result:`, data.meta.result);
|
||||||
updateQueueFromTaskResult(data.meta.result);
|
updateQueueFromTaskResult(data.meta.result);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -82,7 +100,10 @@ export default function ImageQueueModal({
|
|||||||
|
|
||||||
// Update progress from task meta
|
// Update progress from task meta
|
||||||
if (data.meta) {
|
if (data.meta) {
|
||||||
|
console.log(`[ImageQueueModal] Updating queue from meta:`, data.meta);
|
||||||
updateQueueFromTaskMeta(data.meta);
|
updateQueueFromTaskMeta(data.meta);
|
||||||
|
} else {
|
||||||
|
console.log(`[ImageQueueModal] No meta data in response`);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Check if it's a JSON parse error (HTML response) or API error
|
// Check if it's a JSON parse error (HTML response) or API error
|
||||||
|
|||||||
@@ -16,10 +16,38 @@ import { useToast } from '../../components/ui/toast/ToastContainer';
|
|||||||
import { FileIcon, DownloadIcon, BoltIcon } from '../../icons';
|
import { FileIcon, DownloadIcon, BoltIcon } from '../../icons';
|
||||||
import { createImagesPageConfig } from '../../config/pages/images.config';
|
import { createImagesPageConfig } from '../../config/pages/images.config';
|
||||||
import ImageQueueModal, { ImageQueueItem } from '../../components/common/ImageQueueModal';
|
import ImageQueueModal, { ImageQueueItem } from '../../components/common/ImageQueueModal';
|
||||||
|
import { useResourceDebug } from '../../hooks/useResourceDebug';
|
||||||
|
|
||||||
export default function Images() {
|
export default function Images() {
|
||||||
const toast = useToast();
|
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
|
// Data state
|
||||||
const [images, setImages] = useState<ContentImagesGroup[]>([]);
|
const [images, setImages] = useState<ContentImagesGroup[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -237,16 +265,35 @@ export default function Images() {
|
|||||||
console.log('[Generate Images] Max in-article images from settings:', maxInArticleImages);
|
console.log('[Generate Images] Max in-article images from settings:', maxInArticleImages);
|
||||||
|
|
||||||
// STAGE 2: Start actual generation
|
// 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);
|
const result = await generateImages(imageIds, contentId);
|
||||||
|
|
||||||
if (result.success && result.task_id) {
|
if (result.success && result.task_id) {
|
||||||
// Task started successfully - polling will be handled by ImageQueueModal
|
// Task started successfully - polling will be handled by ImageQueueModal
|
||||||
setTaskId(result.task_id);
|
setTaskId(result.task_id);
|
||||||
console.log('[Generate Images] Stage 2: Task started with ID:', result.task_id);
|
console.log('[Generate Images] Stage 2: Task started with ID:', result.task_id);
|
||||||
|
addAiLog({
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
type: 'success',
|
||||||
|
action: 'generate_images',
|
||||||
|
data: { task_id: result.task_id, message: 'Task queued successfully' }
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.error(result.error || 'Failed to start image generation');
|
toast.error(result.error || 'Failed to start image generation');
|
||||||
setIsQueueModalOpen(false);
|
setIsQueueModalOpen(false);
|
||||||
setTaskId(null);
|
setTaskId(null);
|
||||||
|
addAiLog({
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
type: 'error',
|
||||||
|
action: 'generate_images',
|
||||||
|
data: { error: result.error || 'Failed to start image generation' }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -354,6 +401,74 @@ export default function Images() {
|
|||||||
taskId={taskId}
|
taskId={taskId}
|
||||||
onUpdateQueue={setImageQueue}
|
onUpdateQueue={setImageQueue}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user