12
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo, useState, useCallback, useRef } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -22,6 +22,9 @@ import { BusinessDetailsStep } from "./steps/BusinessDetailsStep";
|
|||||||
import { BriefStep } from "./steps/BriefStep";
|
import { BriefStep } from "./steps/BriefStep";
|
||||||
import { ObjectivesStep } from "./steps/ObjectivesStep";
|
import { ObjectivesStep } from "./steps/ObjectivesStep";
|
||||||
import { StyleStep } from "./steps/StyleStep";
|
import { StyleStep } from "./steps/StyleStep";
|
||||||
|
import { useProgressModal } from "../../../hooks/useProgressModal";
|
||||||
|
import { useResourceDebug } from "../../../hooks/useResourceDebug";
|
||||||
|
import ProgressModal from "../../../components/common/ProgressModal";
|
||||||
|
|
||||||
export default function SiteBuilderWizard() {
|
export default function SiteBuilderWizard() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -52,6 +55,41 @@ export default function SiteBuilderWizard() {
|
|||||||
loadMetadata,
|
loadMetadata,
|
||||||
} = useBuilderStore();
|
} = useBuilderStore();
|
||||||
|
|
||||||
|
// Progress modal for AI functions
|
||||||
|
const progressModal = useProgressModal();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}>>([]);
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
syncContextFromStores();
|
syncContextFromStores();
|
||||||
}, [activeSite?.id, activeSite?.name]);
|
}, [activeSite?.id, activeSite?.name]);
|
||||||
@@ -60,6 +98,122 @@ export default function SiteBuilderWizard() {
|
|||||||
loadMetadata();
|
loadMetadata();
|
||||||
}, [loadMetadata]);
|
}, [loadMetadata]);
|
||||||
|
|
||||||
|
// Track structure generation task and open progress modal
|
||||||
|
const structureTaskId = useBuilderStore((state) => state.structureTaskId);
|
||||||
|
useEffect(() => {
|
||||||
|
if (structureTaskId) {
|
||||||
|
// Log initial request
|
||||||
|
addAiLog({
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
type: 'request',
|
||||||
|
action: 'Generate Site Structure',
|
||||||
|
data: {
|
||||||
|
taskId: structureTaskId,
|
||||||
|
blueprintId: activeBlueprint?.id,
|
||||||
|
message: 'Structure generation task queued',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
progressModal.openModal(structureTaskId, 'Generating Site Structure', 'ai-generate-site-structure-01-desktop');
|
||||||
|
}
|
||||||
|
}, [structureTaskId, progressModal, addAiLog, activeBlueprint?.id]);
|
||||||
|
|
||||||
|
// 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 || 'Generate Site Structure',
|
||||||
|
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 || 'Generate Site Structure',
|
||||||
|
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 || 'Generate Site Structure',
|
||||||
|
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
|
||||||
|
useEffect(() => {
|
||||||
|
if (!progressModal.isOpen) {
|
||||||
|
lastLoggedStepRef.current = null;
|
||||||
|
lastLoggedPercentageRef.current = -1;
|
||||||
|
hasReloadedRef.current = false;
|
||||||
|
} else {
|
||||||
|
hasReloadedRef.current = false;
|
||||||
|
}
|
||||||
|
}, [progressModal.isOpen]);
|
||||||
|
|
||||||
const selectedSectors = useMemo(
|
const selectedSectors = useMemo(
|
||||||
() =>
|
() =>
|
||||||
form.sectorIds.map((id) => ({
|
form.sectorIds.map((id) => ({
|
||||||
@@ -335,6 +489,92 @@ export default function SiteBuilderWizard() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ProgressModal
|
||||||
|
isOpen={progressModal.isOpen}
|
||||||
|
progress={progressModal.progress}
|
||||||
|
title={progressModal.title}
|
||||||
|
taskId={progressModal.taskId || undefined}
|
||||||
|
functionId={progressModal.functionId}
|
||||||
|
onClose={() => {
|
||||||
|
progressModal.closeModal();
|
||||||
|
// Reload pages when modal closes if task was completed
|
||||||
|
if (progressModal.progress.status === 'completed' && !hasReloadedRef.current && activeBlueprint) {
|
||||||
|
hasReloadedRef.current = true;
|
||||||
|
refreshPages(activeBlueprint.id);
|
||||||
|
}
|
||||||
|
// Clear structure task ID
|
||||||
|
useBuilderStore.setState({ structureTaskId: null });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* AI Function Logs - Display below content (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>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ interface BuilderState {
|
|||||||
activeBlueprint?: SiteBlueprint;
|
activeBlueprint?: SiteBlueprint;
|
||||||
pages: PageBlueprint[];
|
pages: PageBlueprint[];
|
||||||
selectedPageIds: number[];
|
selectedPageIds: number[];
|
||||||
|
structureTaskId?: string | null;
|
||||||
generationProgress?: {
|
generationProgress?: {
|
||||||
pagesQueued: number;
|
pagesQueued: number;
|
||||||
taskIds: number[];
|
taskIds: number[];
|
||||||
@@ -96,6 +97,7 @@ export const useBuilderStore = create<BuilderState>((set, get) => ({
|
|||||||
metadataError: undefined,
|
metadataError: undefined,
|
||||||
pages: [],
|
pages: [],
|
||||||
selectedPageIds: [],
|
selectedPageIds: [],
|
||||||
|
structureTaskId: null,
|
||||||
|
|
||||||
setField: (key, value) =>
|
setField: (key, value) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
@@ -284,6 +286,10 @@ export const useBuilderStore = create<BuilderState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (generation?.task_id) {
|
||||||
|
set({ structureTaskId: generation.task_id });
|
||||||
|
}
|
||||||
|
|
||||||
if (generation?.structure) {
|
if (generation?.structure) {
|
||||||
lastStructure = generation.structure;
|
lastStructure = generation.structure;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user