|
|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useEffect, useState, useCallback } from "react";
|
|
|
|
|
import { useEffect, useState, useCallback, useRef } from "react";
|
|
|
|
|
import { useAppStore } from "@/store/app-store";
|
|
|
|
|
import { getElectronAPI } from "@/lib/electron";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
} from "@/components/ui/dialog";
|
|
|
|
|
import { Save, RefreshCw, FileText, Sparkles, Loader2, FilePlus2 } from "lucide-react";
|
|
|
|
|
import { Save, RefreshCw, FileText, Sparkles, Loader2, FilePlus2, AlertCircle, ListPlus } from "lucide-react";
|
|
|
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
|
|
|
import { XmlSyntaxEditor } from "@/components/ui/xml-syntax-editor";
|
|
|
|
|
import type { SpecRegenerationEvent } from "@/types/electron";
|
|
|
|
|
@@ -36,6 +36,19 @@ export function SpecView() {
|
|
|
|
|
const [projectOverview, setProjectOverview] = useState("");
|
|
|
|
|
const [isCreating, setIsCreating] = useState(false);
|
|
|
|
|
const [generateFeatures, setGenerateFeatures] = useState(true);
|
|
|
|
|
|
|
|
|
|
// Generate features only state
|
|
|
|
|
const [isGeneratingFeatures, setIsGeneratingFeatures] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Logs state (kept for internal tracking, but UI removed)
|
|
|
|
|
const [logs, setLogs] = useState<string>("");
|
|
|
|
|
const logsRef = useRef<string>("");
|
|
|
|
|
|
|
|
|
|
// Phase tracking and status
|
|
|
|
|
const [currentPhase, setCurrentPhase] = useState<string>("");
|
|
|
|
|
const [errorMessage, setErrorMessage] = useState<string>("");
|
|
|
|
|
const statusCheckRef = useRef<boolean>(false);
|
|
|
|
|
const stateRestoredRef = useRef<boolean>(false);
|
|
|
|
|
|
|
|
|
|
// Load spec from file
|
|
|
|
|
const loadSpec = useCallback(async () => {
|
|
|
|
|
@@ -69,6 +82,244 @@ export function SpecView() {
|
|
|
|
|
loadSpec();
|
|
|
|
|
}, [loadSpec]);
|
|
|
|
|
|
|
|
|
|
// Check if spec regeneration is running when component mounts or project changes
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const checkStatus = async () => {
|
|
|
|
|
if (!currentProject || statusCheckRef.current) return;
|
|
|
|
|
statusCheckRef.current = true;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const api = getElectronAPI();
|
|
|
|
|
if (!api.specRegeneration) {
|
|
|
|
|
statusCheckRef.current = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const status = await api.specRegeneration.status();
|
|
|
|
|
console.log("[SpecView] Status check on mount:", status);
|
|
|
|
|
|
|
|
|
|
if (status.success && status.isRunning) {
|
|
|
|
|
// Something is running - restore state
|
|
|
|
|
console.log("[SpecView] Spec generation is running - restoring state");
|
|
|
|
|
|
|
|
|
|
// Only restore state if we haven't already done so for this project
|
|
|
|
|
// This prevents resetting state when switching tabs
|
|
|
|
|
if (!stateRestoredRef.current) {
|
|
|
|
|
setIsCreating(true);
|
|
|
|
|
setIsRegenerating(true);
|
|
|
|
|
stateRestoredRef.current = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to extract the current phase from existing logs
|
|
|
|
|
let detectedPhase = "";
|
|
|
|
|
if (logsRef.current) {
|
|
|
|
|
// Look for the most recent phase in the logs
|
|
|
|
|
const phaseMatches = logsRef.current.matchAll(/\[Phase:\s*([^\]]+)\]/g);
|
|
|
|
|
const phases = Array.from(phaseMatches);
|
|
|
|
|
if (phases.length > 0) {
|
|
|
|
|
// Get the last phase mentioned in the logs
|
|
|
|
|
detectedPhase = phases[phases.length - 1][1];
|
|
|
|
|
console.log(`[SpecView] Detected phase from logs: ${detectedPhase}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Also check for feature generation indicators in logs
|
|
|
|
|
const hasFeatureGeneration = logsRef.current.includes("Feature Generation") ||
|
|
|
|
|
logsRef.current.includes("Feature Creation") ||
|
|
|
|
|
logsRef.current.includes("Creating feature") ||
|
|
|
|
|
logsRef.current.includes("feature_generation");
|
|
|
|
|
|
|
|
|
|
if (hasFeatureGeneration && !detectedPhase) {
|
|
|
|
|
detectedPhase = "feature_generation";
|
|
|
|
|
console.log("[SpecView] Detected feature generation from logs");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update phase from logs if we found one and don't have a specific phase set
|
|
|
|
|
// This allows the phase to update as new events come in
|
|
|
|
|
if (detectedPhase) {
|
|
|
|
|
setCurrentPhase((prevPhase) => {
|
|
|
|
|
// Only update if we don't have a phase or if the detected phase is more recent
|
|
|
|
|
if (!prevPhase || prevPhase === "unknown" || prevPhase === "in progress") {
|
|
|
|
|
return detectedPhase;
|
|
|
|
|
}
|
|
|
|
|
return prevPhase;
|
|
|
|
|
});
|
|
|
|
|
} else if (!currentPhase) {
|
|
|
|
|
// Use a more descriptive default instead of "unknown"
|
|
|
|
|
setCurrentPhase("in progress");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't clear logs - they may have been persisted
|
|
|
|
|
if (!logsRef.current) {
|
|
|
|
|
const resumeMessage = "[Status] Resumed monitoring existing spec generation process...\n";
|
|
|
|
|
logsRef.current = resumeMessage;
|
|
|
|
|
setLogs(resumeMessage);
|
|
|
|
|
} else if (!logsRef.current.includes("Resumed monitoring")) {
|
|
|
|
|
// Add a resume message to existing logs only if not already present
|
|
|
|
|
const resumeMessage = "\n[Status] Resumed monitoring existing spec generation process...\n";
|
|
|
|
|
logsRef.current = logsRef.current + resumeMessage;
|
|
|
|
|
setLogs(logsRef.current);
|
|
|
|
|
}
|
|
|
|
|
} else if (status.success && !status.isRunning) {
|
|
|
|
|
// Check if we might still be in feature generation phase based on logs
|
|
|
|
|
const mightBeGeneratingFeatures = logsRef.current && (
|
|
|
|
|
logsRef.current.includes("Feature Generation") ||
|
|
|
|
|
logsRef.current.includes("Feature Creation") ||
|
|
|
|
|
logsRef.current.includes("Creating feature") ||
|
|
|
|
|
logsRef.current.includes("feature_generation") ||
|
|
|
|
|
currentPhase === "feature_generation"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (mightBeGeneratingFeatures && specExists) {
|
|
|
|
|
// Spec exists and we might still be generating features - keep state active
|
|
|
|
|
console.log("[SpecView] Detected potential feature generation - keeping state active");
|
|
|
|
|
if (!isCreating && !isRegenerating) {
|
|
|
|
|
setIsCreating(true);
|
|
|
|
|
}
|
|
|
|
|
if (currentPhase !== "feature_generation") {
|
|
|
|
|
setCurrentPhase("feature_generation");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Not running - clear running state
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setCurrentPhase("");
|
|
|
|
|
stateRestoredRef.current = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[SpecView] Failed to check status:", error);
|
|
|
|
|
} finally {
|
|
|
|
|
statusCheckRef.current = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Reset restoration flag when project changes
|
|
|
|
|
stateRestoredRef.current = false;
|
|
|
|
|
checkStatus();
|
|
|
|
|
}, [currentProject]);
|
|
|
|
|
|
|
|
|
|
// Sync state when tab becomes visible (user returns to spec editor)
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleVisibilityChange = async () => {
|
|
|
|
|
if (!document.hidden && currentProject && (isCreating || isRegenerating || isGeneratingFeatures)) {
|
|
|
|
|
// Tab became visible and we think we're still generating - verify status
|
|
|
|
|
try {
|
|
|
|
|
const api = getElectronAPI();
|
|
|
|
|
if (!api.specRegeneration) return;
|
|
|
|
|
|
|
|
|
|
const status = await api.specRegeneration.status();
|
|
|
|
|
console.log("[SpecView] Visibility change - status check:", status);
|
|
|
|
|
|
|
|
|
|
if (!status.isRunning) {
|
|
|
|
|
// Not running but we think we are - check if we're truly done
|
|
|
|
|
// Look for recent activity in logs (within last 30 seconds worth of content)
|
|
|
|
|
const recentLogs = logsRef.current.slice(-5000); // Last ~5000 chars
|
|
|
|
|
const hasRecentFeatureActivity = recentLogs.includes("Feature Creation") ||
|
|
|
|
|
recentLogs.includes("Creating feature") ||
|
|
|
|
|
recentLogs.match(/\[Feature Creation\].*$/m);
|
|
|
|
|
|
|
|
|
|
// Check if we have a completion message or complete phase
|
|
|
|
|
const hasCompletion = logsRef.current.includes("All tasks completed") ||
|
|
|
|
|
logsRef.current.includes("[Complete] All tasks completed") ||
|
|
|
|
|
logsRef.current.includes("[Phase: complete]");
|
|
|
|
|
|
|
|
|
|
if (hasCompletion || (!hasRecentFeatureActivity && currentPhase !== "feature_generation")) {
|
|
|
|
|
// No recent activity and not running - we're done
|
|
|
|
|
console.log("[SpecView] Visibility change: Generation appears complete - clearing state");
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setIsGeneratingFeatures(false);
|
|
|
|
|
setCurrentPhase("");
|
|
|
|
|
stateRestoredRef.current = false;
|
|
|
|
|
loadSpec();
|
|
|
|
|
} else if (currentPhase === "feature_generation" && !hasRecentFeatureActivity) {
|
|
|
|
|
// We were in feature generation but no recent activity - might be done
|
|
|
|
|
// Wait a moment and check again
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
if (api.specRegeneration) {
|
|
|
|
|
const recheckStatus = await api.specRegeneration.status();
|
|
|
|
|
if (!recheckStatus.isRunning) {
|
|
|
|
|
console.log("[SpecView] Re-check after visibility: Still not running - clearing state");
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setIsGeneratingFeatures(false);
|
|
|
|
|
setCurrentPhase("");
|
|
|
|
|
stateRestoredRef.current = false;
|
|
|
|
|
loadSpec();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, 2000);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[SpecView] Failed to check status on visibility change:", error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
|
|
|
return () => {
|
|
|
|
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
|
|
|
};
|
|
|
|
|
}, [currentProject, isCreating, isRegenerating, isGeneratingFeatures, currentPhase, loadSpec]);
|
|
|
|
|
|
|
|
|
|
// Periodic status check to ensure state stays in sync (only when we think we're running)
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!currentProject || (!isCreating && !isRegenerating && !isGeneratingFeatures)) return;
|
|
|
|
|
|
|
|
|
|
const intervalId = setInterval(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const api = getElectronAPI();
|
|
|
|
|
if (!api.specRegeneration) return;
|
|
|
|
|
|
|
|
|
|
const status = await api.specRegeneration.status();
|
|
|
|
|
|
|
|
|
|
// If not running but we think we are, verify we're truly done
|
|
|
|
|
if (!status.isRunning && (isCreating || isRegenerating || isGeneratingFeatures)) {
|
|
|
|
|
// Check logs for completion indicators
|
|
|
|
|
const hasCompletion = logsRef.current.includes("All tasks completed") ||
|
|
|
|
|
logsRef.current.includes("[Complete] All tasks completed") ||
|
|
|
|
|
logsRef.current.includes("[Phase: complete]") ||
|
|
|
|
|
currentPhase === "complete";
|
|
|
|
|
|
|
|
|
|
// Also check if we haven't seen feature activity recently
|
|
|
|
|
const recentLogs = logsRef.current.slice(-3000); // Last 3000 chars (more context)
|
|
|
|
|
const hasRecentFeatureActivity = recentLogs.includes("Feature Creation") ||
|
|
|
|
|
recentLogs.includes("Creating feature") ||
|
|
|
|
|
recentLogs.includes("UpdateFeatureStatus") ||
|
|
|
|
|
recentLogs.includes("[Tool]") && recentLogs.includes("UpdateFeatureStatus");
|
|
|
|
|
|
|
|
|
|
// If we're in feature_generation phase and not running, we're likely done
|
|
|
|
|
// (features are created via tool calls, so when stream ends, they're done)
|
|
|
|
|
const isFeatureGenComplete = currentPhase === "feature_generation" &&
|
|
|
|
|
!hasRecentFeatureActivity;
|
|
|
|
|
|
|
|
|
|
if (hasCompletion || isFeatureGenComplete) {
|
|
|
|
|
console.log("[SpecView] Periodic check: Generation complete - clearing state", {
|
|
|
|
|
hasCompletion,
|
|
|
|
|
hasRecentFeatureActivity,
|
|
|
|
|
currentPhase,
|
|
|
|
|
isFeatureGenComplete
|
|
|
|
|
});
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setIsGeneratingFeatures(false);
|
|
|
|
|
setCurrentPhase("");
|
|
|
|
|
stateRestoredRef.current = false;
|
|
|
|
|
loadSpec();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[SpecView] Periodic status check error:", error);
|
|
|
|
|
}
|
|
|
|
|
}, 2000); // Check every 2 seconds (more frequent)
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
clearInterval(intervalId);
|
|
|
|
|
};
|
|
|
|
|
}, [currentProject, isCreating, isRegenerating, isGeneratingFeatures, currentPhase, loadSpec]);
|
|
|
|
|
|
|
|
|
|
// Subscribe to spec regeneration events
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const api = getElectronAPI();
|
|
|
|
|
@@ -77,18 +328,151 @@ export function SpecView() {
|
|
|
|
|
const unsubscribe = api.specRegeneration.onEvent((event: SpecRegenerationEvent) => {
|
|
|
|
|
console.log("[SpecView] Regeneration event:", event.type);
|
|
|
|
|
|
|
|
|
|
if (event.type === "spec_regeneration_complete") {
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setShowRegenerateDialog(false);
|
|
|
|
|
setShowCreateDialog(false);
|
|
|
|
|
setProjectDefinition("");
|
|
|
|
|
setProjectOverview("");
|
|
|
|
|
// Reload the spec to show the new content
|
|
|
|
|
loadSpec();
|
|
|
|
|
if (event.type === "spec_regeneration_progress") {
|
|
|
|
|
// Extract phase from content if present
|
|
|
|
|
const phaseMatch = event.content.match(/\[Phase:\s*([^\]]+)\]/);
|
|
|
|
|
if (phaseMatch) {
|
|
|
|
|
const phase = phaseMatch[1];
|
|
|
|
|
setCurrentPhase(phase);
|
|
|
|
|
console.log(`[SpecView] Phase updated: ${phase}`);
|
|
|
|
|
|
|
|
|
|
// If phase is "complete", clear running state immediately
|
|
|
|
|
if (phase === "complete") {
|
|
|
|
|
console.log("[SpecView] Phase is complete - clearing state");
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
stateRestoredRef.current = false;
|
|
|
|
|
// Small delay to ensure spec file is written
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
loadSpec();
|
|
|
|
|
}, 500);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for completion indicators in content
|
|
|
|
|
if (event.content.includes("All tasks completed") ||
|
|
|
|
|
event.content.includes("✓ All tasks completed")) {
|
|
|
|
|
// This indicates everything is done - clear state immediately
|
|
|
|
|
console.log("[SpecView] Detected completion in progress message - clearing state");
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setCurrentPhase("");
|
|
|
|
|
stateRestoredRef.current = false;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
loadSpec();
|
|
|
|
|
}, 500);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Append progress to logs
|
|
|
|
|
const newLog = logsRef.current + event.content;
|
|
|
|
|
logsRef.current = newLog;
|
|
|
|
|
setLogs(newLog);
|
|
|
|
|
console.log("[SpecView] Progress:", event.content.substring(0, 100));
|
|
|
|
|
|
|
|
|
|
// Clear error message when we get new progress
|
|
|
|
|
if (errorMessage) {
|
|
|
|
|
setErrorMessage("");
|
|
|
|
|
}
|
|
|
|
|
} else if (event.type === "spec_regeneration_tool") {
|
|
|
|
|
// Check if this is a feature creation tool
|
|
|
|
|
const isFeatureTool = event.tool === "mcp__automaker-tools__UpdateFeatureStatus" ||
|
|
|
|
|
event.tool === "UpdateFeatureStatus" ||
|
|
|
|
|
event.tool?.includes("Feature");
|
|
|
|
|
|
|
|
|
|
if (isFeatureTool) {
|
|
|
|
|
// Ensure we're in feature generation phase
|
|
|
|
|
if (currentPhase !== "feature_generation") {
|
|
|
|
|
setCurrentPhase("feature_generation");
|
|
|
|
|
setIsCreating(true);
|
|
|
|
|
setIsRegenerating(true);
|
|
|
|
|
console.log("[SpecView] Detected feature creation tool - setting phase to feature_generation");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Log tool usage with details
|
|
|
|
|
const toolInput = event.input ? ` (${JSON.stringify(event.input).substring(0, 100)}...)` : "";
|
|
|
|
|
const toolLog = `\n[Tool] ${event.tool}${toolInput}\n`;
|
|
|
|
|
const newLog = logsRef.current + toolLog;
|
|
|
|
|
logsRef.current = newLog;
|
|
|
|
|
setLogs(newLog);
|
|
|
|
|
console.log("[SpecView] Tool:", event.tool, event.input);
|
|
|
|
|
} else if (event.type === "spec_regeneration_complete") {
|
|
|
|
|
// Add completion message to logs first (before checking)
|
|
|
|
|
const completionLog = logsRef.current + `\n[Complete] ${event.message}\n`;
|
|
|
|
|
logsRef.current = completionLog;
|
|
|
|
|
setLogs(completionLog);
|
|
|
|
|
|
|
|
|
|
// Check if this is the final completion
|
|
|
|
|
const isFinalCompletion = event.message?.includes("All tasks completed") ||
|
|
|
|
|
event.message === "All tasks completed!" ||
|
|
|
|
|
event.message === "All tasks completed";
|
|
|
|
|
|
|
|
|
|
// Check if we've already seen a completion phase in logs (including the message we just added)
|
|
|
|
|
const hasSeenCompletePhase = logsRef.current.includes("[Phase: complete]");
|
|
|
|
|
|
|
|
|
|
// Check recent logs for feature activity
|
|
|
|
|
const recentLogs = logsRef.current.slice(-2000);
|
|
|
|
|
const hasRecentFeatureActivity = recentLogs.includes("Feature Creation") ||
|
|
|
|
|
recentLogs.includes("Creating feature") ||
|
|
|
|
|
recentLogs.includes("UpdateFeatureStatus");
|
|
|
|
|
|
|
|
|
|
// Check if we're still generating features (only for intermediate completion)
|
|
|
|
|
const isGeneratingFeatures = !isFinalCompletion &&
|
|
|
|
|
!hasSeenCompletePhase &&
|
|
|
|
|
(event.message?.includes("Features are being generated") ||
|
|
|
|
|
event.message?.includes("features are being generated"));
|
|
|
|
|
|
|
|
|
|
// If we're in feature_generation but no recent activity and we see completion, we're done
|
|
|
|
|
const shouldComplete = isFinalCompletion ||
|
|
|
|
|
hasSeenCompletePhase ||
|
|
|
|
|
(currentPhase === "feature_generation" && !hasRecentFeatureActivity && !isGeneratingFeatures);
|
|
|
|
|
|
|
|
|
|
if (shouldComplete) {
|
|
|
|
|
// Fully complete - clear all states immediately
|
|
|
|
|
console.log("[SpecView] Final completion detected - clearing state", {
|
|
|
|
|
isFinalCompletion,
|
|
|
|
|
hasSeenCompletePhase,
|
|
|
|
|
shouldComplete,
|
|
|
|
|
hasRecentFeatureActivity,
|
|
|
|
|
currentPhase,
|
|
|
|
|
message: event.message
|
|
|
|
|
});
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setIsGeneratingFeatures(false);
|
|
|
|
|
setCurrentPhase("");
|
|
|
|
|
setShowRegenerateDialog(false);
|
|
|
|
|
setShowCreateDialog(false);
|
|
|
|
|
setProjectDefinition("");
|
|
|
|
|
setProjectOverview("");
|
|
|
|
|
setErrorMessage("");
|
|
|
|
|
stateRestoredRef.current = false;
|
|
|
|
|
// Reload the spec to show the new content
|
|
|
|
|
loadSpec();
|
|
|
|
|
} else {
|
|
|
|
|
// Intermediate completion - keep state active for feature generation
|
|
|
|
|
setIsCreating(true);
|
|
|
|
|
setIsRegenerating(true);
|
|
|
|
|
setCurrentPhase("feature_generation");
|
|
|
|
|
console.log("[SpecView] Spec complete, continuing with feature generation", {
|
|
|
|
|
isGeneratingFeatures,
|
|
|
|
|
hasRecentFeatureActivity,
|
|
|
|
|
currentPhase
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("[SpecView] Spec generation event:", event.message);
|
|
|
|
|
} else if (event.type === "spec_regeneration_error") {
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setIsGeneratingFeatures(false);
|
|
|
|
|
setCurrentPhase("error");
|
|
|
|
|
setErrorMessage(event.error);
|
|
|
|
|
stateRestoredRef.current = false; // Reset restoration flag
|
|
|
|
|
// Add error to logs
|
|
|
|
|
const errorLog = logsRef.current + `\n\n[ERROR] ${event.error}\n`;
|
|
|
|
|
logsRef.current = errorLog;
|
|
|
|
|
setLogs(errorLog);
|
|
|
|
|
console.error("[SpecView] Regeneration error:", event.error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
@@ -126,6 +510,12 @@ export function SpecView() {
|
|
|
|
|
if (!currentProject || !projectDefinition.trim()) return;
|
|
|
|
|
|
|
|
|
|
setIsRegenerating(true);
|
|
|
|
|
setCurrentPhase("initialization");
|
|
|
|
|
setErrorMessage("");
|
|
|
|
|
// Reset logs when starting new regeneration
|
|
|
|
|
logsRef.current = "";
|
|
|
|
|
setLogs("");
|
|
|
|
|
console.log("[SpecView] Starting spec regeneration");
|
|
|
|
|
try {
|
|
|
|
|
const api = getElectronAPI();
|
|
|
|
|
if (!api.specRegeneration) {
|
|
|
|
|
@@ -139,13 +529,25 @@ export function SpecView() {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
console.error("[SpecView] Failed to start regeneration:", result.error);
|
|
|
|
|
const errorMsg = result.error || "Unknown error";
|
|
|
|
|
console.error("[SpecView] Failed to start regeneration:", errorMsg);
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setCurrentPhase("error");
|
|
|
|
|
setErrorMessage(errorMsg);
|
|
|
|
|
const errorLog = `[Error] Failed to start regeneration: ${errorMsg}\n`;
|
|
|
|
|
logsRef.current = errorLog;
|
|
|
|
|
setLogs(errorLog);
|
|
|
|
|
}
|
|
|
|
|
// If successful, we'll wait for the events to update the state
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[SpecView] Failed to regenerate spec:", error);
|
|
|
|
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
|
|
|
console.error("[SpecView] Failed to regenerate spec:", errorMsg);
|
|
|
|
|
setIsRegenerating(false);
|
|
|
|
|
setCurrentPhase("error");
|
|
|
|
|
setErrorMessage(errorMsg);
|
|
|
|
|
const errorLog = `[Error] Failed to regenerate spec: ${errorMsg}\n`;
|
|
|
|
|
logsRef.current = errorLog;
|
|
|
|
|
setLogs(errorLog);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -154,6 +556,12 @@ export function SpecView() {
|
|
|
|
|
|
|
|
|
|
setIsCreating(true);
|
|
|
|
|
setShowCreateDialog(false);
|
|
|
|
|
setCurrentPhase("initialization");
|
|
|
|
|
setErrorMessage("");
|
|
|
|
|
// Reset logs when starting new generation
|
|
|
|
|
logsRef.current = "";
|
|
|
|
|
setLogs("");
|
|
|
|
|
console.log("[SpecView] Starting spec creation, generateFeatures:", generateFeatures);
|
|
|
|
|
try {
|
|
|
|
|
const api = getElectronAPI();
|
|
|
|
|
if (!api.specRegeneration) {
|
|
|
|
|
@@ -168,13 +576,70 @@ export function SpecView() {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
console.error("[SpecView] Failed to start spec creation:", result.error);
|
|
|
|
|
const errorMsg = result.error || "Unknown error";
|
|
|
|
|
console.error("[SpecView] Failed to start spec creation:", errorMsg);
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setCurrentPhase("error");
|
|
|
|
|
setErrorMessage(errorMsg);
|
|
|
|
|
const errorLog = `[Error] Failed to start spec creation: ${errorMsg}\n`;
|
|
|
|
|
logsRef.current = errorLog;
|
|
|
|
|
setLogs(errorLog);
|
|
|
|
|
}
|
|
|
|
|
// If successful, we'll wait for the events to update the state
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[SpecView] Failed to create spec:", error);
|
|
|
|
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
|
|
|
console.error("[SpecView] Failed to create spec:", errorMsg);
|
|
|
|
|
setIsCreating(false);
|
|
|
|
|
setCurrentPhase("error");
|
|
|
|
|
setErrorMessage(errorMsg);
|
|
|
|
|
const errorLog = `[Error] Failed to create spec: ${errorMsg}\n`;
|
|
|
|
|
logsRef.current = errorLog;
|
|
|
|
|
setLogs(errorLog);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleGenerateFeatures = async () => {
|
|
|
|
|
if (!currentProject) return;
|
|
|
|
|
|
|
|
|
|
setIsGeneratingFeatures(true);
|
|
|
|
|
setShowRegenerateDialog(false);
|
|
|
|
|
setCurrentPhase("initialization");
|
|
|
|
|
setErrorMessage("");
|
|
|
|
|
// Reset logs when starting feature generation
|
|
|
|
|
logsRef.current = "";
|
|
|
|
|
setLogs("");
|
|
|
|
|
console.log("[SpecView] Starting feature generation from existing spec");
|
|
|
|
|
try {
|
|
|
|
|
const api = getElectronAPI();
|
|
|
|
|
if (!api.specRegeneration) {
|
|
|
|
|
console.error("[SpecView] Spec regeneration not available");
|
|
|
|
|
setIsGeneratingFeatures(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const result = await api.specRegeneration.generateFeatures(
|
|
|
|
|
currentProject.path
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
const errorMsg = result.error || "Unknown error";
|
|
|
|
|
console.error("[SpecView] Failed to start feature generation:", errorMsg);
|
|
|
|
|
setIsGeneratingFeatures(false);
|
|
|
|
|
setCurrentPhase("error");
|
|
|
|
|
setErrorMessage(errorMsg);
|
|
|
|
|
const errorLog = `[Error] Failed to start feature generation: ${errorMsg}\n`;
|
|
|
|
|
logsRef.current = errorLog;
|
|
|
|
|
setLogs(errorLog);
|
|
|
|
|
}
|
|
|
|
|
// If successful, we'll wait for the events to update the state
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
|
|
|
console.error("[SpecView] Failed to generate features:", errorMsg);
|
|
|
|
|
setIsGeneratingFeatures(false);
|
|
|
|
|
setCurrentPhase("error");
|
|
|
|
|
setErrorMessage(errorMsg);
|
|
|
|
|
const errorLog = `[Error] Failed to generate features: ${errorMsg}\n`;
|
|
|
|
|
logsRef.current = errorLog;
|
|
|
|
|
setLogs(errorLog);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -218,6 +683,36 @@ export function SpecView() {
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{(isCreating || isRegenerating) && (
|
|
|
|
|
<div className="flex items-center gap-3 px-6 py-3.5 rounded-xl bg-gradient-to-r from-primary/15 to-primary/5 border border-primary/30 shadow-lg backdrop-blur-md">
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Loader2 className="w-5 h-5 animate-spin text-primary flex-shrink-0" />
|
|
|
|
|
<div className="absolute inset-0 w-5 h-5 animate-ping text-primary/20" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-1 min-w-0">
|
|
|
|
|
<span className="text-sm font-semibold text-primary leading-tight tracking-tight">
|
|
|
|
|
{isCreating ? "Generating Specification" : "Regenerating Specification"}
|
|
|
|
|
</span>
|
|
|
|
|
{currentPhase && (
|
|
|
|
|
<span className="text-xs text-muted-foreground/90 leading-tight font-medium">
|
|
|
|
|
{currentPhase === "initialization" && "Initializing..."}
|
|
|
|
|
{currentPhase === "setup" && "Setting up tools..."}
|
|
|
|
|
{currentPhase === "analysis" && "Analyzing project structure..."}
|
|
|
|
|
{currentPhase === "spec_complete" && "Spec created! Generating features..."}
|
|
|
|
|
{currentPhase === "feature_generation" && "Creating features from roadmap..."}
|
|
|
|
|
{currentPhase === "complete" && "Complete!"}
|
|
|
|
|
{currentPhase === "error" && "Error occurred"}
|
|
|
|
|
{!["initialization", "setup", "analysis", "spec_complete", "feature_generation", "complete", "error"].includes(currentPhase) && currentPhase}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{errorMessage && (
|
|
|
|
|
<div className="flex items-center gap-2 text-destructive">
|
|
|
|
|
<span className="text-sm font-medium">Error: {errorMessage}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Empty State */}
|
|
|
|
|
@@ -225,26 +720,74 @@ export function SpecView() {
|
|
|
|
|
<div className="text-center max-w-md">
|
|
|
|
|
<div className="mb-6 flex justify-center">
|
|
|
|
|
<div className="p-4 rounded-full bg-primary/10">
|
|
|
|
|
<FilePlus2 className="w-12 h-12 text-primary" />
|
|
|
|
|
{isCreating ? (
|
|
|
|
|
<Loader2 className="w-12 h-12 text-primary animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<FilePlus2 className="w-12 h-12 text-primary" />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<h2 className="text-2xl font-semibold mb-3">No App Specification Found</h2>
|
|
|
|
|
<h2 className="text-2xl font-semibold mb-4">
|
|
|
|
|
{isCreating ? (
|
|
|
|
|
<>
|
|
|
|
|
<div className="mb-4">
|
|
|
|
|
<span>Generating App Specification</span>
|
|
|
|
|
</div>
|
|
|
|
|
{currentPhase && (
|
|
|
|
|
<div className="px-6 py-3.5 rounded-xl bg-gradient-to-r from-primary/15 to-primary/5 border border-primary/30 shadow-lg backdrop-blur-md inline-flex items-center justify-center">
|
|
|
|
|
<span className="text-sm font-semibold text-primary text-center tracking-tight">
|
|
|
|
|
{currentPhase === "initialization" && "Initializing..."}
|
|
|
|
|
{currentPhase === "setup" && "Setting up tools..."}
|
|
|
|
|
{currentPhase === "analysis" && "Analyzing project structure..."}
|
|
|
|
|
{currentPhase === "spec_complete" && "Spec created! Generating features..."}
|
|
|
|
|
{currentPhase === "feature_generation" && "Creating features from roadmap..."}
|
|
|
|
|
{currentPhase === "complete" && "Complete!"}
|
|
|
|
|
{currentPhase === "error" && "Error occurred"}
|
|
|
|
|
{!["initialization", "setup", "analysis", "spec_complete", "feature_generation", "complete", "error"].includes(currentPhase) && currentPhase}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
"No App Specification Found"
|
|
|
|
|
)}
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-muted-foreground mb-6">
|
|
|
|
|
Create an app specification to help our system understand your project.
|
|
|
|
|
We'll analyze your codebase and generate a comprehensive spec based on your description.
|
|
|
|
|
{isCreating
|
|
|
|
|
? currentPhase === "feature_generation"
|
|
|
|
|
? "The app specification has been created! Now generating features from the implementation roadmap..."
|
|
|
|
|
: "We're analyzing your project and generating a comprehensive specification. This may take a few moments..."
|
|
|
|
|
: "Create an app specification to help our system understand your project. We'll analyze your codebase and generate a comprehensive spec based on your description."}
|
|
|
|
|
</p>
|
|
|
|
|
<Button
|
|
|
|
|
size="lg"
|
|
|
|
|
onClick={() => setShowCreateDialog(true)}
|
|
|
|
|
>
|
|
|
|
|
<FilePlus2 className="w-5 h-5 mr-2" />
|
|
|
|
|
Create app_spec
|
|
|
|
|
</Button>
|
|
|
|
|
{errorMessage && (
|
|
|
|
|
<div className="mb-4 p-3 rounded-md bg-destructive/10 border border-destructive/20">
|
|
|
|
|
<p className="text-sm text-destructive font-medium">Error:</p>
|
|
|
|
|
<p className="text-sm text-destructive">{errorMessage}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{!isCreating && (
|
|
|
|
|
<div className="flex gap-2 justify-center">
|
|
|
|
|
<Button
|
|
|
|
|
size="lg"
|
|
|
|
|
onClick={() => setShowCreateDialog(true)}
|
|
|
|
|
>
|
|
|
|
|
<FilePlus2 className="w-5 h-5 mr-2" />
|
|
|
|
|
Create app_spec
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Create Dialog */}
|
|
|
|
|
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
|
|
|
|
<Dialog
|
|
|
|
|
open={showCreateDialog}
|
|
|
|
|
onOpenChange={(open) => {
|
|
|
|
|
if (!open && !isCreating) {
|
|
|
|
|
setShowCreateDialog(false);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<DialogContent className="max-w-2xl">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>Create App Specification</DialogTitle>
|
|
|
|
|
@@ -270,6 +813,7 @@ export function SpecView() {
|
|
|
|
|
onChange={(e) => setProjectOverview(e.target.value)}
|
|
|
|
|
placeholder="e.g., A project management tool that allows teams to track tasks, manage sprints, and visualize progress through kanban boards. It should support user authentication, real-time updates, and file attachments..."
|
|
|
|
|
autoFocus
|
|
|
|
|
disabled={isCreating}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@@ -278,11 +822,12 @@ export function SpecView() {
|
|
|
|
|
id="generate-features"
|
|
|
|
|
checked={generateFeatures}
|
|
|
|
|
onCheckedChange={(checked) => setGenerateFeatures(checked === true)}
|
|
|
|
|
disabled={isCreating}
|
|
|
|
|
/>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="generate-features"
|
|
|
|
|
className="text-sm font-medium cursor-pointer"
|
|
|
|
|
className={`text-sm font-medium ${isCreating ? "" : "cursor-pointer"}`}
|
|
|
|
|
>
|
|
|
|
|
Generate feature list
|
|
|
|
|
</label>
|
|
|
|
|
@@ -298,17 +843,27 @@ export function SpecView() {
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
onClick={() => setShowCreateDialog(false)}
|
|
|
|
|
disabled={isCreating}
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</Button>
|
|
|
|
|
<HotkeyButton
|
|
|
|
|
onClick={handleCreateSpec}
|
|
|
|
|
disabled={!projectOverview.trim()}
|
|
|
|
|
disabled={!projectOverview.trim() || isCreating}
|
|
|
|
|
hotkey={{ key: "Enter", cmdCtrl: true }}
|
|
|
|
|
hotkeyActive={showCreateDialog}
|
|
|
|
|
hotkeyActive={showCreateDialog && !isCreating}
|
|
|
|
|
>
|
|
|
|
|
<Sparkles className="w-4 h-4 mr-2" />
|
|
|
|
|
Generate Spec
|
|
|
|
|
{isCreating ? (
|
|
|
|
|
<>
|
|
|
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
|
|
|
Generating...
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Sparkles className="w-4 h-4 mr-2" />
|
|
|
|
|
Generate Spec
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</HotkeyButton>
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
@@ -333,30 +888,66 @@ export function SpecView() {
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={() => setShowRegenerateDialog(true)}
|
|
|
|
|
disabled={isRegenerating}
|
|
|
|
|
data-testid="regenerate-spec"
|
|
|
|
|
>
|
|
|
|
|
{isRegenerating ? (
|
|
|
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<Sparkles className="w-4 h-4 mr-2" />
|
|
|
|
|
)}
|
|
|
|
|
{isRegenerating ? "Regenerating..." : "Regenerate"}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={saveSpec}
|
|
|
|
|
disabled={!hasChanges || isSaving}
|
|
|
|
|
data-testid="save-spec"
|
|
|
|
|
>
|
|
|
|
|
<Save className="w-4 h-4 mr-2" />
|
|
|
|
|
{isSaving ? "Saving..." : hasChanges ? "Save Changes" : "Saved"}
|
|
|
|
|
</Button>
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
{(isRegenerating || isCreating || isGeneratingFeatures) && (
|
|
|
|
|
<div className="flex items-center gap-3 px-6 py-3.5 rounded-xl bg-gradient-to-r from-primary/15 to-primary/5 border border-primary/30 shadow-lg backdrop-blur-md">
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Loader2 className="w-5 h-5 animate-spin text-primary flex-shrink-0" />
|
|
|
|
|
<div className="absolute inset-0 w-5 h-5 animate-ping text-primary/20" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-1 min-w-0">
|
|
|
|
|
<span className="text-sm font-semibold text-primary leading-tight tracking-tight">
|
|
|
|
|
{isGeneratingFeatures ? "Generating Features" : isCreating ? "Generating Specification" : "Regenerating Specification"}
|
|
|
|
|
</span>
|
|
|
|
|
{currentPhase && (
|
|
|
|
|
<span className="text-xs text-muted-foreground/90 leading-tight font-medium">
|
|
|
|
|
{currentPhase === "initialization" && "Initializing..."}
|
|
|
|
|
{currentPhase === "setup" && "Setting up tools..."}
|
|
|
|
|
{currentPhase === "analysis" && "Analyzing project structure..."}
|
|
|
|
|
{currentPhase === "spec_complete" && "Spec created! Generating features..."}
|
|
|
|
|
{currentPhase === "feature_generation" && "Creating features from roadmap..."}
|
|
|
|
|
{currentPhase === "complete" && "Complete!"}
|
|
|
|
|
{currentPhase === "error" && "Error occurred"}
|
|
|
|
|
{!["initialization", "setup", "analysis", "spec_complete", "feature_generation", "complete", "error"].includes(currentPhase) && currentPhase}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{errorMessage && (
|
|
|
|
|
<div className="flex items-center gap-3 px-6 py-3.5 rounded-xl bg-gradient-to-r from-destructive/15 to-destructive/5 border border-destructive/30 shadow-lg backdrop-blur-md">
|
|
|
|
|
<AlertCircle className="w-5 h-5 text-destructive flex-shrink-0" />
|
|
|
|
|
<div className="flex flex-col gap-1 min-w-0">
|
|
|
|
|
<span className="text-sm font-semibold text-destructive leading-tight tracking-tight">Error</span>
|
|
|
|
|
<span className="text-xs text-destructive/90 leading-tight font-medium">{errorMessage}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={() => setShowRegenerateDialog(true)}
|
|
|
|
|
disabled={isRegenerating || isCreating || isGeneratingFeatures}
|
|
|
|
|
data-testid="regenerate-spec"
|
|
|
|
|
>
|
|
|
|
|
{isRegenerating ? (
|
|
|
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<Sparkles className="w-4 h-4 mr-2" />
|
|
|
|
|
)}
|
|
|
|
|
{isRegenerating ? "Regenerating..." : "Regenerate"}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={saveSpec}
|
|
|
|
|
disabled={!hasChanges || isSaving || isCreating || isRegenerating || isGeneratingFeatures}
|
|
|
|
|
data-testid="save-spec"
|
|
|
|
|
>
|
|
|
|
|
<Save className="w-4 h-4 mr-2" />
|
|
|
|
|
{isSaving ? "Saving..." : hasChanges ? "Save Changes" : "Saved"}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@@ -373,7 +964,14 @@ export function SpecView() {
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Regenerate Dialog */}
|
|
|
|
|
<Dialog open={showRegenerateDialog} onOpenChange={setShowRegenerateDialog}>
|
|
|
|
|
<Dialog
|
|
|
|
|
open={showRegenerateDialog}
|
|
|
|
|
onOpenChange={(open) => {
|
|
|
|
|
if (!open && !isRegenerating) {
|
|
|
|
|
setShowRegenerateDialog(false);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<DialogContent className="max-w-2xl">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>Regenerate App Specification</DialogTitle>
|
|
|
|
|
@@ -403,35 +1001,56 @@ export function SpecView() {
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
<DialogFooter className="flex justify-between sm:justify-between">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
onClick={() => setShowRegenerateDialog(false)}
|
|
|
|
|
disabled={isRegenerating}
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={handleGenerateFeatures}
|
|
|
|
|
disabled={isRegenerating || isGeneratingFeatures}
|
|
|
|
|
title="Generate features from the existing app_spec.txt without regenerating the spec"
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</Button>
|
|
|
|
|
<HotkeyButton
|
|
|
|
|
onClick={handleRegenerate}
|
|
|
|
|
disabled={!projectDefinition.trim() || isRegenerating}
|
|
|
|
|
hotkey={{ key: "Enter", cmdCtrl: true }}
|
|
|
|
|
hotkeyActive={showRegenerateDialog}
|
|
|
|
|
>
|
|
|
|
|
{isRegenerating ? (
|
|
|
|
|
{isGeneratingFeatures ? (
|
|
|
|
|
<>
|
|
|
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
|
|
|
Regenerating...
|
|
|
|
|
Generating...
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Sparkles className="w-4 h-4 mr-2" />
|
|
|
|
|
Regenerate Spec
|
|
|
|
|
<ListPlus className="w-4 h-4 mr-2" />
|
|
|
|
|
Generate Features
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</HotkeyButton>
|
|
|
|
|
</Button>
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
onClick={() => setShowRegenerateDialog(false)}
|
|
|
|
|
disabled={isRegenerating || isGeneratingFeatures}
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</Button>
|
|
|
|
|
<HotkeyButton
|
|
|
|
|
onClick={handleRegenerate}
|
|
|
|
|
disabled={!projectDefinition.trim() || isRegenerating || isGeneratingFeatures}
|
|
|
|
|
hotkey={{ key: "Enter", cmdCtrl: true }}
|
|
|
|
|
hotkeyActive={showRegenerateDialog && !isRegenerating && !isGeneratingFeatures}
|
|
|
|
|
>
|
|
|
|
|
{isRegenerating ? (
|
|
|
|
|
<>
|
|
|
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
|
|
|
Regenerating...
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Sparkles className="w-4 h-4 mr-2" />
|
|
|
|
|
Regenerate Spec
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</HotkeyButton>
|
|
|
|
|
</div>
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|