Spaces:
No application file
No application file
| const STACK_OPTIONS = { | |
| language: ["Auto", "Python", "JavaScript", "TypeScript", "Java"], | |
| frontend: ["Auto", "None", "HTML/CSS/JavaScript", "React", "Next.js", "Vue"], | |
| backend: ["Auto", "None", "FastAPI", "Flask", "Express", "NestJS", "Spring Boot"], | |
| database: ["Auto", "None", "SQLite", "PostgreSQL", "MySQL", "MongoDB"], | |
| aiTools: ["Auto", "None", "Ollama", "OpenAI API", "LangChain"], | |
| deployment: ["Auto", "None", "Render", "Railway", "Vercel", "Docker"], | |
| }; | |
| const ideaInput = document.getElementById("ideaInput"); | |
| const suggestButton = document.getElementById("suggestButton"); | |
| const continueButton = document.getElementById("continueButton"); | |
| const skipQuestionsButton = document.getElementById("skipQuestionsButton"); | |
| const generateProjectButton = document.getElementById("generateProjectButton"); | |
| const regenerateButton = document.getElementById("regenerateButton"); | |
| const confirmButton = document.getElementById("confirmButton"); | |
| const clearButton = document.getElementById("clearButton"); | |
| const statusMessage = document.getElementById("statusMessage"); | |
| const generationModeSelect = document.getElementById("generationModeSelect"); | |
| const agentSection = document.getElementById("agentSection"); | |
| const advancedStackSection = document.getElementById("advancedStackSection"); | |
| const previewSection = document.getElementById("previewSection"); | |
| const downloadSection = document.getElementById("downloadSection"); | |
| const agentUnderstandingText = document.getElementById("agentUnderstandingText"); | |
| const agentAssumptionsList = document.getElementById("agentAssumptionsList"); | |
| const agentSuggestedStackList = document.getElementById("agentSuggestedStackList"); | |
| const questionCards = document.getElementById("questionCards"); | |
| const finalizeCard = document.getElementById("finalizeCard"); | |
| const finalizeSummaryText = document.getElementById("finalizeSummaryText"); | |
| const finalSelectedStackList = document.getElementById("finalSelectedStackList"); | |
| const finalRequirementsText = document.getElementById("finalRequirementsText"); | |
| const projectNameHeading = document.getElementById("projectNameHeading"); | |
| const detectedChoicesList = document.getElementById("detectedChoicesList"); | |
| const stackChips = document.getElementById("stackChips"); | |
| const selectedStackList = document.getElementById("selectedStackList"); | |
| const chosenStackList = document.getElementById("chosenStackList"); | |
| const assumptionsList = document.getElementById("assumptionsList"); | |
| const summaryText = document.getElementById("summaryText"); | |
| const problemStatementText = document.getElementById("problemStatementText"); | |
| const architectureList = document.getElementById("architectureList"); | |
| const packageRequirementsList = document.getElementById("packageRequirementsList"); | |
| const installCommandsList = document.getElementById("installCommandsList"); | |
| const runCommandsList = document.getElementById("runCommandsList"); | |
| const envVariablesList = document.getElementById("envVariablesList"); | |
| const requiredInputsBody = document.getElementById("requiredInputsBody"); | |
| const modulesList = document.getElementById("modulesList"); | |
| const fileTreeBlock = document.getElementById("fileTreeBlock"); | |
| const filesList = document.getElementById("filesList"); | |
| const downloadText = document.getElementById("downloadText"); | |
| const downloadLink = document.getElementById("downloadLink"); | |
| const stackSelects = { | |
| language: document.getElementById("languageSelect"), | |
| frontend: document.getElementById("frontendSelect"), | |
| backend: document.getElementById("backendSelect"), | |
| database: document.getElementById("databaseSelect"), | |
| aiTools: document.getElementById("aiToolsSelect"), | |
| deployment: document.getElementById("deploymentSelect"), | |
| }; | |
| let baseIdea = ""; | |
| let agentAnalysis = null; | |
| let agentAnswers = {}; | |
| let finalRequirements = ""; | |
| let currentPreview = null; | |
| let selectedStack = getDefaultStackState(); | |
| let currentQuestionIndex = 0; | |
| let currentQuestionDraft = ""; | |
| let showingSuggestion = false; | |
| initializeStackSelectors(); | |
| refreshUiState(); | |
| suggestButton.addEventListener("click", handleStartAgent); | |
| continueButton.addEventListener("click", handleContinueAgent); | |
| skipQuestionsButton.addEventListener("click", handleSkipQuestions); | |
| generateProjectButton.addEventListener("click", handleGenerateProject); | |
| regenerateButton.addEventListener("click", handleRegenerate); | |
| confirmButton.addEventListener("click", handleConfirmZip); | |
| clearButton.addEventListener("click", resetAll); | |
| Object.values(stackSelects).forEach((select) => { | |
| select.addEventListener("change", handleStackChange); | |
| }); | |
| function initializeStackSelectors() { | |
| Object.entries(stackSelects).forEach(([key, select]) => { | |
| STACK_OPTIONS[key].forEach((option) => { | |
| const optionElement = document.createElement("option"); | |
| optionElement.value = option; | |
| optionElement.textContent = option; | |
| select.appendChild(optionElement); | |
| }); | |
| }); | |
| applySelectedStackToControls(getDefaultStackState()); | |
| } | |
| function getDefaultStackState() { | |
| return { | |
| language: "Auto", | |
| frontend: "Auto", | |
| backend: "Auto", | |
| database: "Auto", | |
| aiTools: "Auto", | |
| deployment: "Auto", | |
| }; | |
| } | |
| function getQuestionList() { | |
| return Array.isArray(agentAnalysis?.questions) ? agentAnalysis.questions : []; | |
| } | |
| function getCurrentQuestion() { | |
| const questions = getQuestionList(); | |
| return questions[currentQuestionIndex] || null; | |
| } | |
| function getStoredAnswer(questionId) { | |
| return typeof agentAnswers[questionId] === "string" ? agentAnswers[questionId] : ""; | |
| } | |
| function setQuestionDraft(value) { | |
| currentQuestionDraft = String(value || ""); | |
| } | |
| function resetQuestionFlow() { | |
| currentQuestionIndex = 0; | |
| currentQuestionDraft = ""; | |
| showingSuggestion = false; | |
| } | |
| function collectSelectedStack() { | |
| return { | |
| language: stackSelects.language.value, | |
| frontend: stackSelects.frontend.value, | |
| backend: stackSelects.backend.value, | |
| database: stackSelects.database.value, | |
| aiTools: stackSelects.aiTools.value, | |
| deployment: stackSelects.deployment.value, | |
| }; | |
| } | |
| function applySelectedStackToControls(stack) { | |
| const safeStack = stack || getDefaultStackState(); | |
| Object.entries(stackSelects).forEach(([key, select]) => { | |
| select.value = safeStack[key] || "Auto"; | |
| }); | |
| selectedStack = collectSelectedStack(); | |
| } | |
| function handleStackChange() { | |
| selectedStack = collectSelectedStack(); | |
| refreshUiState(); | |
| } | |
| function setStatus(message, type) { | |
| statusMessage.hidden = false; | |
| statusMessage.className = `status-message ${type}`; | |
| statusMessage.textContent = message; | |
| } | |
| function clearStatus() { | |
| statusMessage.hidden = true; | |
| statusMessage.className = "status-message"; | |
| statusMessage.textContent = ""; | |
| } | |
| function setBusy(isBusy, message = "Working...") { | |
| suggestButton.disabled = isBusy; | |
| continueButton.disabled = isBusy || !agentAnalysis; | |
| skipQuestionsButton.disabled = isBusy || !agentAnalysis; | |
| generateProjectButton.disabled = isBusy || !finalRequirements; | |
| regenerateButton.disabled = isBusy || !currentPreview; | |
| confirmButton.disabled = isBusy || !currentPreview; | |
| clearButton.disabled = isBusy && !baseIdea && !currentPreview && !agentAnalysis; | |
| generationModeSelect.disabled = isBusy; | |
| Object.values(stackSelects).forEach((select) => { | |
| select.disabled = isBusy || !agentAnalysis; | |
| }); | |
| if (isBusy) { | |
| setStatus(message, "loading"); | |
| } | |
| } | |
| function refreshUiState() { | |
| continueButton.disabled = !agentAnalysis; | |
| skipQuestionsButton.disabled = !agentAnalysis; | |
| generateProjectButton.disabled = !finalRequirements; | |
| regenerateButton.disabled = !currentPreview; | |
| confirmButton.disabled = !currentPreview; | |
| clearButton.disabled = !baseIdea && !currentPreview && !agentAnalysis; | |
| Object.values(stackSelects).forEach((select) => { | |
| select.disabled = !agentAnalysis; | |
| }); | |
| const questions = getQuestionList(); | |
| const hasQuestionFlow = !!agentAnalysis && questions.length > 0 && !finalRequirements; | |
| if (hasQuestionFlow) { | |
| continueButton.disabled = showingSuggestion; | |
| continueButton.textContent = currentQuestionIndex >= questions.length - 1 ? "Finish Questions" : "Next"; | |
| } else if (!!agentAnalysis && !finalRequirements) { | |
| continueButton.textContent = "Continue"; | |
| } else { | |
| continueButton.textContent = "Next"; | |
| } | |
| } | |
| async function handleStartAgent() { | |
| const idea = ideaInput.value.trim(); | |
| if (!idea) { | |
| setStatus("Please enter a project idea before starting the agent.", "error"); | |
| return; | |
| } | |
| baseIdea = idea; | |
| currentPreview = null; | |
| finalRequirements = ""; | |
| agentAnswers = {}; | |
| resetQuestionFlow(); | |
| previewSection.hidden = true; | |
| downloadSection.hidden = true; | |
| finalizeCard.hidden = true; | |
| setBusy(true, "Analyzing your idea..."); | |
| try { | |
| const payload = await requestAgentAnalysis(idea); | |
| agentAnalysis = payload; | |
| selectedStack = payload.suggestedStack || getDefaultStackState(); | |
| applySelectedStackToControls(selectedStack); | |
| renderAgentAnalysis(payload); | |
| agentSection.hidden = false; | |
| advancedStackSection.hidden = false; | |
| setStatus("The agent reviewed your idea. Answer the questions one by one or skip and use the suggested defaults.", "success"); | |
| } catch (error) { | |
| agentAnalysis = null; | |
| agentSection.hidden = true; | |
| advancedStackSection.hidden = true; | |
| setStatus(error.message || "Could not start the agent.", "error"); | |
| } finally { | |
| clearBusyState(); | |
| } | |
| } | |
| async function handleContinueAgent() { | |
| if (!baseIdea || !agentAnalysis) { | |
| setStatus("Start the agent before continuing.", "error"); | |
| return; | |
| } | |
| const question = getCurrentQuestion(); | |
| if (!question) { | |
| await finalizeConversationOnly(); | |
| return; | |
| } | |
| const typedAnswer = currentQuestionDraft.trim(); | |
| if (!typedAnswer) { | |
| showingSuggestion = true; | |
| renderQuestionFlow(); | |
| setStatus("No answer was entered. Review the suggested default or edit your answer.", "info"); | |
| return; | |
| } | |
| storeQuestionAnswer(question.id, typedAnswer); | |
| moveToNextQuestion(); | |
| } | |
| async function handleSkipQuestions() { | |
| if (!baseIdea || !agentAnalysis) { | |
| setStatus("Start the agent before skipping questions.", "error"); | |
| return; | |
| } | |
| downloadSection.hidden = true; | |
| setBusy(true, "Generating project with suggested defaults..."); | |
| try { | |
| await finalizeAgentConversation({ fillDefaults: true }); | |
| await generatePreviewFromCurrentState( | |
| "Preview ready. Review the generated project, regenerate if needed, then confirm to create the ZIP.", | |
| ); | |
| } catch (error) { | |
| currentPreview = null; | |
| previewSection.hidden = true; | |
| setStatus(error.message || "Could not generate the project from the suggested defaults.", "error"); | |
| } finally { | |
| clearBusyState(); | |
| } | |
| } | |
| async function handleGenerateProject() { | |
| if (!baseIdea || !finalRequirements) { | |
| setStatus("Finalize the agent conversation before generating the project.", "error"); | |
| return; | |
| } | |
| downloadSection.hidden = true; | |
| setBusy(true, "Generating project preview..."); | |
| try { | |
| await generatePreviewFromCurrentState( | |
| "Preview ready. Review the generated project, regenerate if needed, then confirm to create the ZIP.", | |
| ); | |
| } catch (error) { | |
| currentPreview = null; | |
| previewSection.hidden = true; | |
| setStatus(error.message || "Could not generate the project preview.", "error"); | |
| } finally { | |
| clearBusyState(); | |
| } | |
| } | |
| async function handleRegenerate() { | |
| if (!baseIdea || !currentPreview) { | |
| setStatus("Generate a preview before regenerating with the selected stack.", "error"); | |
| return; | |
| } | |
| downloadSection.hidden = true; | |
| setBusy(true, "Generating project preview..."); | |
| try { | |
| await generatePreviewFromCurrentState( | |
| "Preview regenerated with the latest requirements and selected stack.", | |
| ); | |
| } catch (error) { | |
| setStatus(error.message || "Could not regenerate the preview.", "error"); | |
| } finally { | |
| clearBusyState(); | |
| } | |
| } | |
| async function handleConfirmZip() { | |
| if (!currentPreview) { | |
| setStatus("Generate a preview before creating a ZIP.", "error"); | |
| return; | |
| } | |
| setBusy(true, "Creating ZIP from the latest accepted preview..."); | |
| try { | |
| const response = await fetch("/api/zip", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify({ | |
| preview: currentPreview, | |
| }), | |
| }); | |
| const payload = await response.json(); | |
| if (!response.ok) { | |
| throw new Error(payload.detail || "Could not create the ZIP."); | |
| } | |
| downloadLink.href = payload.downloadUrl; | |
| downloadLink.download = payload.filename; | |
| downloadText.textContent = `Your generated project ZIP is ready: ${payload.filename}`; | |
| downloadSection.hidden = false; | |
| setStatus("ZIP created successfully. You can download it now.", "success"); | |
| } catch (error) { | |
| setStatus(error.message || "Could not create the ZIP.", "error"); | |
| } finally { | |
| clearBusyState(); | |
| } | |
| } | |
| async function requestAgentAnalysis(idea) { | |
| const response = await fetch("/api/agent/analyze", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify({ idea }), | |
| }); | |
| const payload = await response.json(); | |
| if (!response.ok) { | |
| throw new Error(payload.detail || "Could not analyze the idea."); | |
| } | |
| return payload; | |
| } | |
| async function requestAgentFinalize(body) { | |
| const response = await fetch("/api/agent/finalize", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify(body), | |
| }); | |
| const payload = await response.json(); | |
| if (!response.ok) { | |
| throw new Error(payload.detail || "Could not finalize the requirements."); | |
| } | |
| return payload; | |
| } | |
| async function requestPreview(body) { | |
| const response = await fetch("/api/suggest", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify({ | |
| generationMode: generationModeSelect.value || "fast", | |
| ...body, | |
| }), | |
| }); | |
| const payload = await response.json(); | |
| if (!response.ok) { | |
| throw new Error(payload.detail || "Could not generate a preview."); | |
| } | |
| return payload; | |
| } | |
| function renderAgentAnalysis(analysis) { | |
| agentUnderstandingText.textContent = analysis.understanding || "No understanding available."; | |
| renderList(agentAssumptionsList, analysis.assumptions, "No assumptions recorded."); | |
| renderStackSummary(agentSuggestedStackList, analysis.suggestedStack || getDefaultStackState()); | |
| resetQuestionFlow(); | |
| renderQuestionFlow(); | |
| } | |
| function renderQuestionFlow() { | |
| questionCards.replaceChildren(); | |
| const questions = getQuestionList(); | |
| if (questions.length === 0) { | |
| const empty = document.createElement("p"); | |
| empty.className = "text-block"; | |
| empty.textContent = "No follow-up questions are needed. Continue to finalize the recommended stack or skip straight to generation."; | |
| questionCards.appendChild(empty); | |
| refreshUiState(); | |
| return; | |
| } | |
| const question = getCurrentQuestion(); | |
| if (!question) { | |
| const completed = document.createElement("p"); | |
| completed.className = "text-block"; | |
| completed.textContent = "All important questions are complete. Continue to finalize your requirements."; | |
| questionCards.appendChild(completed); | |
| refreshUiState(); | |
| return; | |
| } | |
| if (!currentQuestionDraft && getStoredAnswer(question.id) && !showingSuggestion) { | |
| setQuestionDraft(getStoredAnswer(question.id)); | |
| } | |
| const card = document.createElement("article"); | |
| card.className = "question-card active-question-card"; | |
| const position = document.createElement("p"); | |
| position.className = "question-position"; | |
| position.textContent = `Question ${currentQuestionIndex + 1} of ${questions.length}`; | |
| const title = document.createElement("h4"); | |
| title.textContent = question.question || "Question"; | |
| const reason = document.createElement("p"); | |
| reason.className = "question-reason"; | |
| reason.textContent = "Type your preference first. If you leave it blank, the agent will suggest a default and explain why."; | |
| const inputLabel = document.createElement("label"); | |
| inputLabel.className = "field-label"; | |
| inputLabel.setAttribute("for", "activeQuestionInput"); | |
| inputLabel.textContent = "Your answer"; | |
| const input = document.createElement("input"); | |
| input.type = "text"; | |
| input.id = "activeQuestionInput"; | |
| input.className = "question-input"; | |
| input.placeholder = buildQuestionPlaceholder(question); | |
| input.value = currentQuestionDraft; | |
| input.addEventListener("input", (event) => { | |
| setQuestionDraft(event.target.value); | |
| }); | |
| card.append(position, title, reason, inputLabel, input); | |
| if (Array.isArray(question.options) && question.options.length) { | |
| const optionsHint = document.createElement("p"); | |
| optionsHint.className = "question-hint"; | |
| optionsHint.textContent = `Common choices: ${question.options.join(", ")}`; | |
| card.appendChild(optionsHint); | |
| } | |
| if (showingSuggestion) { | |
| const suggestionBlock = document.createElement("div"); | |
| suggestionBlock.className = "suggestion-block"; | |
| const suggestionTitle = document.createElement("p"); | |
| suggestionTitle.className = "suggestion-title"; | |
| suggestionTitle.textContent = `Suggested: ${question.default || "No default available"}`; | |
| const suggestionReason = document.createElement("p"); | |
| suggestionReason.className = "suggestion-reason"; | |
| suggestionReason.textContent = question.reason || "This default keeps the starter simple and runnable."; | |
| const suggestionActions = document.createElement("div"); | |
| suggestionActions.className = "question-actions"; | |
| const acceptButton = document.createElement("button"); | |
| acceptButton.type = "button"; | |
| acceptButton.className = "secondary-button"; | |
| acceptButton.textContent = "Accept Suggestion"; | |
| acceptButton.addEventListener("click", () => { | |
| storeQuestionAnswer(question.id, question.default || ""); | |
| moveToNextQuestion(); | |
| }); | |
| const editButton = document.createElement("button"); | |
| editButton.type = "button"; | |
| editButton.className = "ghost-button"; | |
| editButton.textContent = "Edit Answer"; | |
| editButton.addEventListener("click", () => { | |
| showingSuggestion = false; | |
| renderQuestionFlow(); | |
| const inputElement = document.getElementById("activeQuestionInput"); | |
| inputElement?.focus(); | |
| }); | |
| suggestionActions.append(acceptButton, editButton); | |
| suggestionBlock.append(suggestionTitle, suggestionReason, suggestionActions); | |
| card.appendChild(suggestionBlock); | |
| } | |
| questionCards.appendChild(card); | |
| refreshUiState(); | |
| } | |
| function buildQuestionPlaceholder(question) { | |
| if (Array.isArray(question.options) && question.options.length) { | |
| return `Example: ${question.options[0]}`; | |
| } | |
| return "Type your answer or leave blank for a suggestion"; | |
| } | |
| function storeQuestionAnswer(questionId, value) { | |
| agentAnswers[questionId] = String(value || "").trim(); | |
| } | |
| function moveToNextQuestion() { | |
| showingSuggestion = false; | |
| currentQuestionIndex += 1; | |
| currentQuestionDraft = ""; | |
| if (currentQuestionIndex >= getQuestionList().length) { | |
| renderQuestionFlow(); | |
| void finalizeConversationOnly(); | |
| return; | |
| } | |
| renderQuestionFlow(); | |
| clearStatus(); | |
| } | |
| async function finalizeConversationOnly() { | |
| setBusy(true, "Finalizing requirements..."); | |
| try { | |
| await finalizeAgentConversation({ fillDefaults: false }); | |
| setStatus("Requirements finalized. Generate the project when you are ready.", "success"); | |
| } catch (error) { | |
| setStatus(error.message || "Could not finalize the requirements.", "error"); | |
| } finally { | |
| clearBusyState(); | |
| } | |
| } | |
| function buildAnswersPayload(fillDefaults) { | |
| const answers = { ...agentAnswers }; | |
| if (!fillDefaults) { | |
| return answers; | |
| } | |
| getQuestionList().forEach((question) => { | |
| if (!answers[question.id]) { | |
| answers[question.id] = question.default || ""; | |
| } | |
| }); | |
| return answers; | |
| } | |
| function buildFinalizationSummary(finalizedStack, assumptions) { | |
| const scope = [ | |
| finalizedStack.frontend && finalizedStack.frontend !== "None" ? finalizedStack.frontend : null, | |
| finalizedStack.backend && finalizedStack.backend !== "None" ? finalizedStack.backend : null, | |
| ].filter(Boolean).join(" + "); | |
| const firstAssumption = Array.isArray(assumptions) && assumptions.length ? assumptions[0] : ""; | |
| return scope | |
| ? `The starter is now aligned around ${scope}. ${firstAssumption}`.trim() | |
| : `The starter requirements are finalized. ${firstAssumption}`.trim(); | |
| } | |
| function renderFinalization(finalized) { | |
| finalizeCard.hidden = false; | |
| finalRequirementsText.textContent = finalized.finalRequirements || "No finalized requirements available."; | |
| finalizeSummaryText.textContent = buildFinalizationSummary( | |
| finalized.selectedStack || selectedStack, | |
| finalized.assumptions || [], | |
| ); | |
| renderStackSummary(finalSelectedStackList, finalized.selectedStack || selectedStack); | |
| renderStackSummary(agentSuggestedStackList, finalized.selectedStack || selectedStack); | |
| const mergedAssumptions = dedupeList([ | |
| ...(Array.isArray(agentAnalysis?.assumptions) ? agentAnalysis.assumptions : []), | |
| ...(Array.isArray(finalized.assumptions) ? finalized.assumptions : []), | |
| ]); | |
| renderList(agentAssumptionsList, mergedAssumptions, "No assumptions recorded."); | |
| refreshUiState(); | |
| } | |
| function clearBusyState() { | |
| suggestButton.disabled = false; | |
| generationModeSelect.disabled = false; | |
| refreshUiState(); | |
| } | |
| async function finalizeAgentConversation({ fillDefaults }) { | |
| const payload = await requestAgentFinalize({ | |
| idea: baseIdea, | |
| answers: buildAnswersPayload(fillDefaults), | |
| suggestedStack: collectSelectedStack(), | |
| }); | |
| finalRequirements = payload.finalRequirements || ""; | |
| selectedStack = payload.selectedStack || selectedStack; | |
| applySelectedStackToControls(selectedStack); | |
| renderFinalization(payload); | |
| return payload; | |
| } | |
| async function generatePreviewFromCurrentState(successMessage) { | |
| selectedStack = collectSelectedStack(); | |
| const payload = await requestPreview({ | |
| idea: baseIdea, | |
| selectedStack, | |
| finalRequirements, | |
| }); | |
| currentPreview = payload; | |
| selectedStack = payload.selectedStack || selectedStack; | |
| renderPreview(payload); | |
| applySelectedStackToControls(selectedStack); | |
| setStatus(successMessage, "success"); | |
| return payload; | |
| } | |
| function resetAll() { | |
| baseIdea = ""; | |
| agentAnalysis = null; | |
| agentAnswers = {}; | |
| finalRequirements = ""; | |
| currentPreview = null; | |
| selectedStack = getDefaultStackState(); | |
| resetQuestionFlow(); | |
| ideaInput.value = ""; | |
| clearStatus(); | |
| applySelectedStackToControls(getDefaultStackState()); | |
| agentSection.hidden = true; | |
| advancedStackSection.hidden = true; | |
| advancedStackSection.open = false; | |
| previewSection.hidden = true; | |
| downloadSection.hidden = true; | |
| finalizeCard.hidden = true; | |
| generateProjectButton.disabled = true; | |
| generationModeSelect.value = "fast"; | |
| generationModeSelect.disabled = false; | |
| downloadLink.removeAttribute("href"); | |
| downloadLink.removeAttribute("download"); | |
| downloadText.textContent = "Your generated project ZIP is ready."; | |
| agentUnderstandingText.textContent = ""; | |
| clearCollection(agentAssumptionsList); | |
| clearCollection(agentSuggestedStackList); | |
| questionCards.replaceChildren(); | |
| finalizeSummaryText.textContent = ""; | |
| clearCollection(finalSelectedStackList); | |
| finalRequirementsText.textContent = ""; | |
| clearCollection(detectedChoicesList); | |
| stackChips.replaceChildren(); | |
| clearCollection(selectedStackList); | |
| clearCollection(chosenStackList); | |
| clearCollection(assumptionsList); | |
| clearCollection(architectureList); | |
| clearCollection(packageRequirementsList); | |
| clearCollection(installCommandsList); | |
| clearCollection(runCommandsList); | |
| clearCollection(envVariablesList); | |
| requiredInputsBody.replaceChildren(); | |
| modulesList.replaceChildren(); | |
| filesList.replaceChildren(); | |
| fileTreeBlock.textContent = ""; | |
| summaryText.textContent = ""; | |
| problemStatementText.textContent = ""; | |
| projectNameHeading.textContent = "Generated Project"; | |
| refreshUiState(); | |
| } | |
| function renderPreview(preview) { | |
| previewSection.hidden = false; | |
| projectNameHeading.textContent = preview.projectName || "Generated Project"; | |
| summaryText.textContent = preview.summary || "No summary available."; | |
| problemStatementText.textContent = preview.problemStatement || "No problem statement available."; | |
| fileTreeBlock.textContent = preview.fileTree || "No file tree available."; | |
| renderList(detectedChoicesList, preview.detectedUserChoices, "No explicit user choices detected."); | |
| renderStackChips(preview.selectedStack || getDefaultStackState()); | |
| renderStackSummary(selectedStackList, preview.selectedStack || getDefaultStackState()); | |
| renderList(chosenStackList, preview.chosenStack, "No chosen stack details available."); | |
| renderList(assumptionsList, preview.assumptions, "No assumptions recorded."); | |
| renderList(architectureList, preview.architecture, "No architecture details available."); | |
| renderList(packageRequirementsList, preview.packageRequirements, "No package requirements available."); | |
| renderList(installCommandsList, preview.installCommands, "No install commands available."); | |
| renderList(runCommandsList, preview.runCommands, "No run commands available."); | |
| renderRequiredInputs(preview.requiredInputs || []); | |
| renderEnvVariables(preview.envVariables || []); | |
| renderModules(preview.modules || []); | |
| renderFiles(preview.files || []); | |
| } | |
| function renderStackChips(stack) { | |
| stackChips.replaceChildren(); | |
| const featuredChips = [ | |
| ["language", "Language"], | |
| ["frontend", "Frontend"], | |
| ["backend", "Backend"], | |
| ["database", "Database"], | |
| ]; | |
| featuredChips.forEach(([key, label]) => { | |
| const chip = document.createElement("span"); | |
| chip.className = "stack-chip"; | |
| chip.innerHTML = `<strong>${escapeHtml(label)}</strong> ${escapeHtml(stack[key] || "Auto")}`; | |
| stackChips.appendChild(chip); | |
| }); | |
| } | |
| function renderList(element, items, fallback) { | |
| clearCollection(element); | |
| const safeItems = Array.isArray(items) && items.length ? items : [fallback]; | |
| safeItems.forEach((item) => { | |
| const listItem = document.createElement("li"); | |
| listItem.textContent = item; | |
| element.appendChild(listItem); | |
| }); | |
| } | |
| function renderStackSummary(element, stack) { | |
| clearCollection(element); | |
| const labels = { | |
| language: "Language", | |
| frontend: "Frontend", | |
| backend: "Backend", | |
| database: "Database", | |
| aiTools: "AI / Tools", | |
| deployment: "Deployment", | |
| }; | |
| Object.entries(labels).forEach(([key, label]) => { | |
| const listItem = document.createElement("li"); | |
| listItem.textContent = `${label}: ${stack[key] || "Auto"}`; | |
| element.appendChild(listItem); | |
| }); | |
| } | |
| function renderEnvVariables(envVariables) { | |
| clearCollection(envVariablesList); | |
| if (!Array.isArray(envVariables) || envVariables.length === 0) { | |
| renderList(envVariablesList, [], "No environment variables required."); | |
| return; | |
| } | |
| envVariables.forEach((variable) => { | |
| const listItem = document.createElement("li"); | |
| const description = variable.description ? ` - ${variable.description}` : ""; | |
| const value = variable.value ? ` = ${variable.value}` : ""; | |
| listItem.textContent = `${variable.name}${value}${description}`; | |
| envVariablesList.appendChild(listItem); | |
| }); | |
| } | |
| function renderRequiredInputs(requiredInputs) { | |
| requiredInputsBody.replaceChildren(); | |
| if (!Array.isArray(requiredInputs) || requiredInputs.length === 0) { | |
| const row = document.createElement("tr"); | |
| row.innerHTML = ` | |
| <td colspan="5">No required inputs detected. Copy <code>.env.example</code> to <code>.env</code> if you want to override defaults later.</td> | |
| `; | |
| requiredInputsBody.appendChild(row); | |
| return; | |
| } | |
| requiredInputs.forEach((item) => { | |
| const row = document.createElement("tr"); | |
| row.innerHTML = ` | |
| <td><code>${escapeHtml(item.name || "")}</code></td> | |
| <td>${item.required === false ? "Optional" : "Required"}</td> | |
| <td><code>${escapeHtml(item.example || "") || "-"}</code></td> | |
| <td>${escapeHtml(item.whereToAdd || ".env")}</td> | |
| <td>${escapeHtml(item.purpose || "No purpose provided.")}</td> | |
| `; | |
| requiredInputsBody.appendChild(row); | |
| }); | |
| } | |
| function renderModules(modules) { | |
| modulesList.replaceChildren(); | |
| if (!Array.isArray(modules) || modules.length === 0) { | |
| const empty = document.createElement("p"); | |
| empty.className = "text-block"; | |
| empty.textContent = "No modules available."; | |
| modulesList.appendChild(empty); | |
| return; | |
| } | |
| modules.forEach((module) => { | |
| const card = document.createElement("article"); | |
| card.className = "module-card"; | |
| const title = document.createElement("h4"); | |
| title.textContent = module.name || "Unnamed module"; | |
| const purpose = document.createElement("p"); | |
| purpose.textContent = module.purpose || "No purpose provided."; | |
| const keyFiles = document.createElement("p"); | |
| keyFiles.className = "key-files"; | |
| const files = Array.isArray(module.keyFiles) && module.keyFiles.length | |
| ? module.keyFiles.join(", ") | |
| : "No key files provided."; | |
| keyFiles.textContent = `Key files: ${files}`; | |
| card.append(title, purpose, keyFiles); | |
| modulesList.appendChild(card); | |
| }); | |
| } | |
| function renderFiles(files) { | |
| filesList.replaceChildren(); | |
| if (!Array.isArray(files) || files.length === 0) { | |
| const empty = document.createElement("p"); | |
| empty.className = "text-block"; | |
| empty.textContent = "No starter files were generated."; | |
| filesList.appendChild(empty); | |
| return; | |
| } | |
| files.forEach((file) => { | |
| const entry = document.createElement("details"); | |
| entry.className = "file-entry"; | |
| const summary = document.createElement("summary"); | |
| summary.innerHTML = ` | |
| <span class="file-entry-header"> | |
| <span class="file-entry-dots"><span></span><span></span><span></span></span> | |
| <span class="file-entry-path">${escapeHtml(file.path || "Unnamed file")}</span> | |
| </span> | |
| <span class="file-entry-tag">Source</span> | |
| `; | |
| const code = document.createElement("pre"); | |
| code.className = "code-block"; | |
| code.textContent = file.content || ""; | |
| entry.append(summary, code); | |
| filesList.appendChild(entry); | |
| }); | |
| } | |
| function clearCollection(element) { | |
| element.replaceChildren(); | |
| } | |
| function dedupeList(items) { | |
| return [...new Set((items || []).filter(Boolean))]; | |
| } | |
| function escapeHtml(value) { | |
| return String(value) | |
| .replaceAll("&", "&") | |
| .replaceAll("<", "<") | |
| .replaceAll(">", ">") | |
| .replaceAll('"', """) | |
| .replaceAll("'", "'"); | |
| } | |