Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>MathResearch AI | Premium Research Assistant</title> | |
| <!-- Dependencies --> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <script src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLMIYUद्दCRn8RstnGvr4+R+xOK/z/" crossorigin="anonymous"></script> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-Xi8rHCpR0kafdGucokKAPTRZvDahAgjS/Gf7h+JDO7nSyB/NBuGvU/EAcI3iGfD4/:" crossorigin="anonymous"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.7.0/styles/github.min.css"> | |
| <style> | |
| /* Custom Styles (Unchanged) */ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| :root { --primary: #4F46E5; --primary-dark: #4338CA; --secondary: #10B981; --accent: #F59E0B; --dark: #1F2937; --light: #F9FAFB; --surface: #FFFFFF; --border: #E5E7EB; } | |
| body { font-family: 'Inter', sans-serif; background-color: var(--light); color: var(--dark); } | |
| .glass-card { background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.18); } | |
| .gradient-bg { background: linear-gradient(135deg, #4F46E5 0%, #7C3AED 50%, #10B981 100%); } | |
| .shadow-soft { box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); } | |
| .shadow-hard { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); } | |
| .ai-response::before, .user-input::before, .feedback-panel::before, .consensus-panel::before { background: #eee; } | |
| .progress-step.completed .step-icon { background: linear-gradient(135deg, var(--secondary), #34D399); border-color: transparent; color: white;} | |
| .progress-step.active .step-icon { background: linear-gradient(135deg, var(--primary), #818CF8); border-color: transparent; color: white;} | |
| .sidebar-item { transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } | |
| .sidebar-item:hover { transform: translateX(4px); background: rgba(79, 70, 229, 0.05); } | |
| .sidebar-item.active { background: rgba(79, 70, 229, 0.08); } | |
| .katex-display { background-color: rgba(249, 250, 251, 0.8); backdrop-filter: blur(8px); padding: 1em; border-radius: 0.5rem; overflow-x: auto; } | |
| .hljs { background-color: rgba(249, 250, 251, 0.8); backdrop-filter: blur(8px); padding: 1em; border-radius: 0.5rem; } | |
| .fade-in { animation: fadeIn 0.3s ease-in-out; } | |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } | |
| .pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } | |
| @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } | |
| .hidden { display: none; } | |
| .spinner { border: 4px solid rgba(0, 0, 0, 0.1); width: 36px; height: 36px; border-radius: 50%; border-left-color: var(--primary); animation: spin 1s ease infinite; margin: 20px auto; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| textarea { resize: vertical; } | |
| .idea-item.selected { background-color: rgba(79, 70, 229, 0.1); border-left: 4px solid var(--primary); } | |
| .latex-section { border: 1px solid var(--border); border-radius: 0.5rem; margin-bottom: 1rem; background: white; } | |
| .latex-section-header { background-color: var(--light); padding: 0.75rem 1.25rem; border-bottom: 1px solid var(--border); font-weight: 600; border-top-left-radius: 0.5rem; border-top-right-radius: 0.5rem; } | |
| .latex-section-content { display: flex; flex-direction: column; lg:flex-row; gap: 1rem; padding: 1.25rem; } | |
| .latex-code-view { flex: 1; min-width: 0; } | |
| .latex-preview-view { flex: 1; min-width: 0; border-left: 0; lg:border-left: 1px dashed var(--border); padding-left: 0; lg:padding-left: 1rem; margin-top: 1rem; lg:margin-top: 0; } | |
| button:disabled { opacity: 0.5; cursor: not-allowed; } | |
| .modal-backdrop { position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.3); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 50; transition: opacity 0.3s ease-out; opacity: 0; } | |
| .modal-content { background-color: white; border-radius: 1rem; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); max-width: 90vw; width: 500px; transform: scale(0.95); transition: all 0.3s ease-out; opacity: 0; } | |
| .modal-backdrop.active { opacity: 1; } | |
| .modal-backdrop.active .modal-content { transform: scale(1); opacity: 1; } | |
| </style> | |
| </head> | |
| <body class="min-h-screen"> | |
| <!-- App Container --> | |
| <div class="flex flex-col min-h-screen"> | |
| <!-- Header --> | |
| <header class="gradient-bg text-white shadow-hard sticky top-0 z-20"> | |
| <div class="container mx-auto px-6 py-4"><div class="flex justify-between items-center"><div class="flex items-center space-x-3"><div class="w-10 h-10 rounded-xl bg-white/20 flex items-center justify-center"><i class="fas fa-square-root-alt text-white text-xl"></i></div><h1 class="text-2xl font-bold">MathResearch AI</h1></div><div class="flex items-center space-x-4"><button id="apiKeysBtn" class="px-4 py-2 glass-card text-white/90 hover:text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-all"><i class="fas fa-key"></i><span>API Keys</span></button><button id="exportButton" class="px-4 py-2 bg-white text-gray-900 hover:bg-white/90 rounded-xl text-sm font-medium flex items-center space-x-2 transition-all shadow-soft" disabled><i class="fas fa-download"></i><span>Export LaTeX</span></button></div></div></div> | |
| </header> | |
| <!-- Main Content --> | |
| <div class="flex flex-1 overflow-hidden"> | |
| <!-- Sidebar --> | |
| <div class="w-72 bg-white/80 backdrop-blur-lg border-r border-gray-100 overflow-y-auto flex-shrink-0"> | |
| <div class="p-6"><div class="flex items-center justify-between mb-6"><h2 class="font-semibold text-gray-700">Project History</h2></div><div class="space-y-2" id="historySidebar"><p class="text-xs text-gray-400">Workflow steps will appear here.</p></div></div> | |
| </div> | |
| <!-- Main Panel --> | |
| <div class="flex-1 overflow-y-auto p-8 bg-gray-50/50"> | |
| <!-- Progress Bar --> | |
| <div class="glass-card rounded-2xl shadow-soft p-6 mb-8 sticky top-[calc(68px+1rem)] z-10"> | |
| <div class="flex justify-between items-center mb-6"><h2 class="text-lg font-semibold text-gray-800">Research Workflow Progress</h2><div class="flex items-center space-x-3"><span class="text-sm text-gray-500/90" id="currentStageLabel">Idea Generation</span><span class="text-sm font-medium bg-gray-100 text-gray-700 px-2 py-1 rounded-lg" id="currentStepIndicator">Step 1 of 8</span></div></div> | |
| <div class="flex justify-between items-start relative px-2 sm:px-4"> | |
| <div class="absolute top-6 left-0 right-0 h-1.5 mx-8 sm:mx-12 z-0"><div class="h-full bg-gray-200 rounded-full"></div><div id="progressBarFill" class="absolute top-0 left-0 h-full bg-gradient-to-r from-indigo-400 to-green-400 rounded-full transition-all duration-500 ease-out" style="width: 0%;"></div></div> | |
| <div class="progress-step text-center relative z-10" data-step="1" data-stage="ideaGeneration"><div class="flex flex-col items-center"><div class="step-icon w-12 h-12 rounded-xl border-2 border-gray-200 bg-white flex items-center justify-center mb-2 text-gray-400 shadow-soft transition-all"><i class="fas fa-lightbulb"></i></div><span class="text-xs font-medium text-gray-700">Idea Gen</span></div></div> | |
| <div class="progress-step text-center relative z-10" data-step="2" data-stage="ideaDevelopment"><div class="flex flex-col items-center"><div class="step-icon w-12 h-12 rounded-xl border-2 border-gray-200 bg-white flex items-center justify-center mb-2 text-gray-400 shadow-soft transition-all"><i class="fas fa-seedling"></i></div><span class="text-xs font-medium text-gray-700">Develop</span></div></div> | |
| <div class="progress-step text-center relative z-10" data-step="3" data-stage="firstCritique"><div class="flex flex-col items-center"><div class="step-icon w-12 h-12 rounded-xl border-2 border-gray-200 bg-white flex items-center justify-center mb-2 text-gray-400 shadow-soft transition-all"><i class="fas fa-comment-medical"></i></div><span class="text-xs font-medium text-gray-700">Critique 1</span></div></div> | |
| <div class="progress-step text-center relative z-10" data-step="4" data-stage="iterativeRefinement"><div class="flex flex-col items-center"><div class="step-icon w-12 h-12 rounded-xl border-2 border-gray-200 bg-white flex items-center justify-center mb-2 text-gray-400 shadow-soft transition-all"><i class="fas fa-sync-alt"></i></div><span class="text-xs font-medium text-gray-700">Refine</span></div></div> | |
| <div class="progress-step text-center relative z-10" data-step="5" data-stage="deepCritique"><div class="flex flex-col items-center"><div class="step-icon w-12 h-12 rounded-xl border-2 border-gray-200 bg-white flex items-center justify-center mb-2 text-gray-400 shadow-soft transition-all"><i class="fas fa-search"></i></div><span class="text-xs font-medium text-gray-700">Critique 2</span></div></div> | |
| <div class="progress-step text-center relative z-10" data-step="6" data-stage="consensus"><div class="flex flex-col items-center"><div class="step-icon w-12 h-12 rounded-xl border-2 border-gray-200 bg-white flex items-center justify-center mb-2 text-gray-400 shadow-soft transition-all"><i class="fas fa-handshake"></i></div><span class="text-xs font-medium text-gray-700">Consensus</span></div></div> | |
| <div class="progress-step text-center relative z-10" data-step="7" data-stage="paperOutline"><div class="flex flex-col items-center"><div class="step-icon w-12 h-12 rounded-xl border-2 border-gray-200 bg-white flex items-center justify-center mb-2 text-gray-400 shadow-soft transition-all"><i class="fas fa-list-ol"></i></div><span class="text-xs font-medium text-gray-700">Outline</span></div></div> | |
| <div class="progress-step text-center relative z-10" data-step="8" data-stage="latexDrafting"><div class="flex flex-col items-center"><div class="step-icon w-12 h-12 rounded-xl border-2 border-gray-200 bg-white flex items-center justify-center mb-2 text-gray-400 shadow-soft transition-all"><i class="fas fa-file-alt"></i></div><span class="text-xs font-medium text-gray-700">Draft</span></div></div> | |
| </div> | |
| </div> | |
| <!-- Dynamic Stage Content Area --> | |
| <div id="stageContent" class="pt-8"> | |
| <div id="loadingSpinner" class="spinner hidden"></div> | |
| <div id="errorDisplay" class="hidden bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-6" role="alert"><strong class="font-bold">Error:</strong> <span class="block sm:inline" id="errorMessageText"></span><button class="absolute top-0 bottom-0 right-0 px-4 py-3" onclick="document.getElementById('errorDisplay').classList.add('hidden')"><i class="fas fa-times"></i></button></div> | |
| <!-- STAGES (HTML structure unchanged from previous version) --> | |
| <div class="stage-panel fade-in" id="ideaGenerationStage"><div class="glass-card rounded-2xl shadow-soft overflow-hidden mb-8 p-8"><h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center"><i class="fas fa-lightbulb text-indigo-500 mr-3"></i> Initial Idea Generation</h3><p class="text-sm text-gray-600 mb-4">Enter initial idea. GPT suggests 7 directions.</p><textarea id="initialIdeaInput" class="w-full p-3 border border-gray-200 rounded-lg shadow-inner focus:outline-none focus:ring-2 focus:ring-indigo-200 min-h-[100px]" placeholder="e.g., Sheaf theory in distributed systems..."></textarea><div class="mt-4 text-right"><button id="generateIdeasBtn" data-action="generateIdeas" class="px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft inline-flex"><i class="fas fa-lightbulb"></i> <span>Generate Directions</span></button></div></div></div> | |
| <div class="stage-panel fade-in hidden" id="ideaDevelopmentStage"><div class="glass-card rounded-2xl shadow-soft overflow-hidden mb-8 p-8"><h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center"><i class="fas fa-seedling text-green-500 mr-3"></i> Develop Direction</h3><p class="text-sm text-gray-600 mb-4">Select direction, then develop.</p><div id="ideaListContainer" class="space-y-3 mb-6 max-h-96 overflow-y-auto p-4 bg-gray-50 rounded-lg border border-gray-200"><p class="text-sm text-gray-500">Generating...</p></div><p class="text-sm text-gray-600 mb-2 font-medium">Selected:</p><div id="selectedIdeaDisplay" class="p-3 border border-gray-200 rounded-lg bg-gray-100 text-sm text-gray-700 min-h-[50px] mb-4"><span class="italic text-gray-500">Select above.</span></div><div class="mt-4 text-right"><button id="developIdeaBtn" data-action="developIdea" class="px-5 py-2.5 bg-green-600 hover:bg-green-700 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft inline-flex" disabled><i class="fas fa-seedling"></i> <span>Develop Idea</span></button></div></div></div> | |
| <div class="stage-panel fade-in hidden" id="firstCritiqueStage"><div class="glass-card rounded-2xl shadow-soft overflow-hidden mb-8 p-8"><h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center"><i class="fas fa-comment-medical text-amber-500 mr-3"></i> First Critique (Gemini)</h3><p class="text-sm text-gray-600 mb-4">Gemini reviews developed idea.</p><div class="mb-6 p-4 border border-gray-200 rounded-lg bg-white shadow-inner"><h4 class="text-sm font-semibold text-indigo-600 mb-2">Developed Idea:</h4><div id="critique1-developedIdea" class="prose max-w-none text-sm text-gray-700">Loading...</div></div><div class="mt-4 text-right"><button id="critique1Btn" data-action="sendToGeminiCritique1" class="px-5 py-2.5 bg-amber-500 hover:bg-amber-600 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft inline-flex"><i class="fas fa-comment-dots"></i> <span>Critique</span></button></div></div></div> | |
| <div class="stage-panel fade-in hidden" id="iterativeRefinementStage"><div class="glass-card rounded-2xl shadow-soft overflow-hidden mb-8"><div class="px-8 py-6 border-b border-gray-100"><h3 class="text-xl font-semibold text-gray-900 flex items-center"><i class="fas fa-sync-alt text-blue-500 mr-3"></i> Iterative Refinement</h3><p class="text-sm text-gray-500 mt-1">Refine w/ feedback. Iteration: <span id="iterationCount">1</span></p></div><div class="p-8"><div class="grid grid-cols-1 lg:grid-cols-2 gap-6"><div class="space-y-6"><div class="bg-white rounded-xl border border-gray-100 shadow-soft"><div class="px-5 py-4 border-b border-gray-100 flex items-center justify-between"><div class="flex items-center space-x-2"><div class="w-6 h-6 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center text-xs"><i class="fas fa-robot"></i></div><span class="text-xs font-semibold text-gray-600">CURRENT IDEA (Edit)</span></div></div><textarea id="refinementIdeaInput" class="w-full p-5 text-sm text-gray-700 focus:outline-none min-h-[250px] placeholder-gray-400 resize-none rounded-b-xl" placeholder="Loading..."></textarea></div></div><div class="space-y-6"><div class="feedback-panel bg-white rounded-xl border border-gray-100 p-5 shadow-soft min-h-[300px]"><div class="flex justify-between items-center mb-3"><div class="flex items-center space-x-2"><div class="w-6 h-6 rounded-full bg-amber-100 text-amber-600 flex items-center justify-center text-xs"><i class="fas fa-comment-dots"></i></div><span class="text-xs font-semibold text-amber-600">LATEST FEEDBACK</span></div></div><div id="refinementFeedbackDisplay" class="prose max-w-none text-sm text-gray-700 mt-3">Loading...</div></div></div></div><div class="flex flex-wrap justify-between items-center mt-8 gap-4"><div class="flex space-x-3"><button data-action="restartCurrentStage" class="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors"><i class="fas fa-redo"></i> <span>Restart Step</span></button></div><div class="flex space-x-3"><button data-action="sendToGeminiRefine" class="px-5 py-2.5 bg-amber-500 hover:bg-amber-600 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft"><i class="fas fa-sync-alt"></i> <span>Get More Feedback</span></button><button id="refinementNextStageBtn" data-action="proceedToDeepCritique" class="px-5 py-2.5 bg-gradient-to-r from-indigo-600 to-blue-500 hover:from-indigo-700 hover:to-blue-600 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft"><span>Proceed</span> <i class="fas fa-arrow-right"></i></button></div></div></div></div><div class="glass-card rounded-2xl shadow-soft overflow-hidden p-6 mt-6"><h3 class="text-lg font-semibold text-gray-900 flex items-center mb-3"><i class="fas fa-chart-line text-green-500 mr-2"></i> Convergence</h3><div class="flex items-center"><div class="flex-1 bg-gray-100 rounded-full h-3 overflow-hidden"><div id="convergenceBar" class="bg-gradient-to-r from-green-400 to-green-600 h-3 rounded-full transition-all duration-500" style="width: 10%"></div></div><span id="convergenceText" class="ml-4 text-sm font-medium text-gray-700">10% (Iter 1)</span></div><p class="text-xs text-gray-500 mt-2">Illustrative. Manual assessment needed.</p></div></div> | |
| <div class="stage-panel fade-in hidden" id="deepCritiqueStage"><div class="glass-card rounded-2xl shadow-soft overflow-hidden mb-8 p-8"><h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center"><i class="fas fa-search text-pink-500 mr-3"></i> Deep Critique (Grok)</h3><p class="text-sm text-gray-600 mb-4">Grok reviews. (Placeholder API).</p><div class="mb-6 p-4 border border-gray-200 rounded-lg bg-white shadow-inner"><h4 class="text-sm font-semibold text-indigo-600 mb-2">Refined Idea:</h4><div id="critique2-refinedIdea" class="prose max-w-none text-sm text-gray-700">Loading...</div></div><div class="flex flex-wrap justify-between items-center mt-4 gap-4"><button data-action="restartCurrentStage" class="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors"><i class="fas fa-redo"></i> <span>Restart</span></button><button id="critique2Btn" data-action="sendToGrok" class="px-5 py-2.5 bg-pink-500 hover:bg-pink-600 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft inline-flex"><i class="fas fa-search-plus"></i> <span>Critique w/ Grok</span></button></div><div id="grokFeedbackDisplayContainer" class="mt-6 hidden"><div class="feedback-panel bg-white rounded-xl border border-gray-100 p-5 shadow-soft"><div class="flex justify-between items-center mb-3"><div class="flex items-center space-x-2"><div class="w-6 h-6 rounded-full bg-pink-100 text-pink-600 flex items-center justify-center text-xs"><i class="fas fa-comment-alt"></i></div><span class="text-xs font-semibold text-pink-600">GROK FEEDBACK</span></div></div><div id="grokFeedbackDisplay" class="prose max-w-none text-sm text-gray-700 mt-3"></div></div><div class="mt-4 text-right"><button data-action="proceedToConsensus" class="px-5 py-2.5 bg-gradient-to-r from-indigo-600 to-blue-500 hover:from-indigo-700 hover:to-blue-600 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft"><span>Finalize</span> <i class="fas fa-arrow-right"></i></button></div></div></div></div> | |
| <div class="stage-panel fade-in hidden" id="consensusStage"><div class="glass-card rounded-2xl shadow-soft overflow-hidden mb-8 p-8"><h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center"><i class="fas fa-handshake text-purple-500 mr-3"></i> Consensus Report</h3><p class="text-sm text-gray-600 mb-4">Review/edit final report.</p><div class="mb-6 p-4 border border-gray-200 rounded-lg bg-white shadow-inner"><h4 class="text-sm font-semibold text-indigo-600 mb-2">Final Refined Idea:</h4><div id="consensus-refinedIdea" class="prose max-w-none text-sm text-gray-700">Loading...</div></div><div class="mb-6 p-4 border border-gray-200 rounded-lg bg-white shadow-inner"><h4 class="text-sm font-semibold text-pink-600 mb-2">Grok Critique:</h4><div id="consensus-grokCritique" class="prose max-w-none text-sm text-gray-700">Loading...</div></div><h4 class="text-sm font-semibold text-gray-700 mb-2">Final Report (Editable):</h4><textarea id="consensusReportInput" class="w-full p-3 border border-gray-200 rounded-lg shadow-inner focus:outline-none focus:ring-2 focus:ring-indigo-200 min-h-[200px]" placeholder="Synthesize..."></textarea><div class="flex flex-wrap justify-between items-center mt-4 gap-4"><button data-action="restartCurrentStage" class="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors"><i class="fas fa-redo"></i> <span>Restart</span></button><button data-action="saveConsensusAndProceedToOutline" class="px-5 py-2.5 bg-gradient-to-r from-indigo-600 to-blue-500 hover:from-indigo-700 hover:to-blue-600 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft"><span>Generate Outline</span> <i class="fas fa-arrow-right"></i></button></div></div></div> | |
| <div class="stage-panel fade-in hidden" id="paperOutlineStage"><div class="glass-card rounded-2xl shadow-soft overflow-hidden mb-8 p-8"><h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center"><i class="fas fa-list-ol text-teal-500 mr-3"></i> Paper Outline (Claude)</h3><p class="text-sm text-gray-600 mb-4">Claude generates outline.</p><div class="mb-6 p-4 border border-gray-200 rounded-lg bg-white shadow-inner"><h4 class="text-sm font-semibold text-indigo-600 mb-2">Consensus Report:</h4><div id="outline-consensusReport" class="prose max-w-none text-sm text-gray-700">Loading...</div></div><div class="flex flex-wrap justify-between items-center mt-4 gap-4"><button data-action="restartCurrentStage" class="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors"><i class="fas fa-redo"></i> <span>Restart</span></button><button id="generateOutlineBtn" data-action="generateOutline" class="px-5 py-2.5 bg-teal-500 hover:bg-teal-600 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft inline-flex"><i class="fas fa-stream"></i> <span>Generate Outline</span></button></div><div id="outlineDisplayContainer" class="mt-6 hidden"><div class="feedback-panel bg-white rounded-xl border border-gray-100 p-5 shadow-soft"><div class="flex justify-between items-center mb-3"><div class="flex items-center space-x-2"><div class="w-6 h-6 rounded-full bg-teal-100 text-teal-600 flex items-center justify-center text-xs"><i class="fas fa-list-ol"></i></div><span class="text-xs font-semibold text-teal-600">GENERATED OUTLINE</span></div></div><div id="outlineDisplay" class="prose max-w-none text-sm text-gray-700 mt-3"></div></div><div class="mt-4 text-right"><button data-action="proceedToDrafting" class="px-5 py-2.5 bg-gradient-to-r from-indigo-600 to-blue-500 hover:from-indigo-700 hover:to-blue-600 text-white rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft"><span>Proceed to Draft</span> <i class="fas fa-arrow-right"></i></button></div></div></div></div> | |
| <div class="stage-panel fade-in hidden" id="latexDraftingStage"><div class="glass-card rounded-2xl shadow-soft overflow-hidden mb-8 p-8"><h3 class="text-xl font-semibold text-gray-900 mb-4 flex items-center"><i class="fas fa-file-alt text-orange-500 mr-3"></i> LaTeX Drafting (Claude)</h3><p class="text-sm text-gray-600 mb-4">Generate LaTeX section by section.</p><div class="mb-6 p-4 border border-gray-200 rounded-lg bg-white shadow-inner"><h4 class="text-sm font-semibold text-teal-600 mb-2">Paper Outline:</h4><div id="latex-paperOutline" class="prose max-w-none text-sm text-gray-700">Loading...</div></div><div class="mb-6"><div class="flex flex-wrap items-center gap-4"><div><label for="sectionSelect" class="block text-sm font-medium text-gray-700 mb-1">Select Section:</label><select id="sectionSelect" class="w-full sm:w-auto p-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"><option value="">-- Select --</option></select></div><button id="generateSectionBtn" data-action="generateLatexSection" class="mt-2 sm:mt-5 px-4 py-2 bg-orange-500 hover:bg-orange-600 text-white rounded-md text-sm font-medium flex items-center space-x-2 transition-colors shadow-soft inline-flex" disabled><i class="fas fa-file-code"></i> <span>Generate LaTeX</span></button><button data-action="restartCurrentStage" class="mt-2 sm:mt-5 px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-xl text-sm font-medium flex items-center space-x-2 transition-colors"><i class="fas fa-redo"></i> <span>Clear All</span></button></div></div><h4 class="text-lg font-semibold text-gray-800 mb-4 mt-8">Generated Sections:</h4><div id="latexSectionsContainer" class="space-y-4"><p class="text-sm text-gray-500">Sections appear here.</p></div></div></div> | |
| </div> <!-- End stageContent --> | |
| </div> <!-- End Main Panel --> | |
| </div> <!-- End Main Content Flex --> | |
| </div> <!-- End App Container --> | |
| <!-- Introductory Modal --> | |
| <div id="introModal" class="modal-backdrop hidden"> <div class="modal-content"> <div class="px-8 py-6 border-b border-gray-100"> <h3 class="text-xl font-semibold text-gray-900 flex items-center"> <div class="w-10 h-10 rounded-lg bg-indigo-100 text-indigo-600 flex items-center justify-center mr-3"> <i class="fas fa-square-root-alt"></i> </div> Welcome to MathResearch AI! </h3> </div> <div class="p-8 text-sm text-gray-700 space-y-3"> <p>Streamline your mathematical research from concept to draft using multiple AI models.</p> <p><strong>How it works:</strong></p> <ul class="list-disc list-inside space-y-1 pl-2 text-gray-600"> <li>Get research directions (GPT).</li> <li>Develop a chosen direction.</li> <li>Iteratively refine with feedback (Gemini).</li> <li>Get deep critique (Grok - *placeholder*).</li> <li>Finalize consensus.</li> <li>Generate paper outline & LaTeX draft (Claude).</li> </ul> <p class="mt-4"><strong>Benefit:</strong> Leverage diverse AI strengths to brainstorm, critique, and formalize research, accelerating early-stage paper writing.</p> <p class="text-xs text-gray-500">Requires API keys from OpenAI, Google AI Studio, and Anthropic. Use the "API Keys" button.</p> </div> <div class="px-8 py-4 bg-gray-50 border-t border-gray-100 text-right rounded-b-xl"> <button id="closeIntroModalBtn" class="px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl text-sm font-medium transition-colors shadow-soft"> Get Started </button> </div> </div> </div> | |
| <!-- API Keys Modal --> | |
| <div id="apiKeysModal" class="modal-backdrop hidden"> <div class="modal-content"> <div class="px-8 py-6 border-b border-gray-100"><h3 class="text-xl font-semibold text-gray-900 flex items-center"><div class="w-10 h-10 rounded-lg bg-indigo-100 text-indigo-600 flex items-center justify-center mr-3"><i class="fas fa-key"></i></div>API Key Management</h3></div> <div class="p-8"> <div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6 rounded-r-lg"><div class="flex"><div class="flex-shrink-0"><i class="fas fa-exclamation-triangle text-yellow-500 mr-2"></i></div><div class="ml-3"><p class="text-sm text-yellow-700"><strong>Security:</strong> Keys stored in local storage. Caution on shared PCs.</p></div></div></div> <div class="space-y-5"> <div> <label class="block text-sm font-medium text-gray-700 mb-1 flex items-center">OpenAI API Key <span class="ml-2 text-xs bg-indigo-100 text-indigo-600 px-2 py-0.5 rounded">GPT-4</span></label> <div class="relative"><input type="password" id="openaiApiKey" class="apiKeyInput w-full px-4 py-2 border border-gray-200 rounded-xl shadow-soft focus:outline-none focus:ring-1 focus:ring-indigo-200 focus:border-indigo-300" placeholder="sk-..."><button class="absolute right-3 top-2.5 text-gray-400 hover:text-gray-600 togglePw" type="button"><i class="far fa-eye"></i></button></div> <div class="text-xs text-gray-500 mt-1 text-right"><a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" class="text-indigo-600 hover:underline">Get Key <i class="fas fa-external-link-alt text-xs ml-1"></i></a></div> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1 flex items-center">Gemini API Key <span class="ml-2 text-xs bg-amber-100 text-amber-600 px-2 py-0.5 rounded">Google</span></label> <div class="relative"><input type="password" id="geminiApiKey" class="apiKeyInput w-full px-4 py-2 border border-gray-200 rounded-xl shadow-soft focus:outline-none focus:ring-1 focus:ring-amber-200 focus:border-amber-300" placeholder="AIza..."><button class="absolute right-3 top-2.5 text-gray-400 hover:text-gray-600 togglePw" type="button"><i class="far fa-eye"></i></button></div> <div class="text-xs text-gray-500 mt-1 text-right"><a href="https://aistudio.google.com/app/apikey" target="_blank" rel="noopener noreferrer" class="text-indigo-600 hover:underline">Get Key (AI Studio) <i class="fas fa-external-link-alt text-xs ml-1"></i></a></div> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1 flex items-center">Grok API Key <span class="ml-2 text-xs bg-pink-100 text-pink-600 px-2 py-0.5 rounded">xAI (Placeholder)</span></label> <div class="relative"><input type="password" id="grokApiKey" class="apiKeyInput w-full px-4 py-2 border border-gray-200 rounded-xl shadow-soft focus:outline-none focus:ring-1 focus:ring-pink-200 focus:border-pink-300" placeholder="gk-..."><button class="absolute right-3 top-2.5 text-gray-400 hover:text-gray-600 togglePw" type="button"><i class="far fa-eye"></i></button></div> <div class="text-xs text-gray-500 mt-1 text-right"><a href="https://x.ai/" target="_blank" rel="noopener noreferrer" class="text-indigo-600 hover:underline">Check xAI Portal <i class="fas fa-external-link-alt text-xs ml-1"></i></a></div> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1 flex items-center">Claude API Key <span class="ml-2 text-xs bg-blue-100 text-blue-600 px-2 py-0.5 rounded">Anthropic</span></label> <div class="relative"><input type="password" id="claudeApiKey" class="apiKeyInput w-full px-4 py-2 border border-gray-200 rounded-xl shadow-soft focus:outline-none focus:ring-1 focus:ring-blue-200 focus:border-blue-300" placeholder="sk-ant-..."><button class="absolute right-3 top-2.5 text-gray-400 hover:text-gray-600 togglePw" type="button"><i class="far fa-eye"></i></button></div> <div class="text-xs text-gray-500 mt-1 text-right"><a href="https://console.anthropic.com/settings/keys" target="_blank" rel="noopener noreferrer" class="text-indigo-600 hover:underline">Get Key <i class="fas fa-external-link-alt text-xs ml-1"></i></a></div> </div> </div> <div class="flex justify-end mt-8 space-x-3"><button id="cancelApiKeys" class="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-xl text-sm font-medium transition-colors">Cancel</button><button id="saveApiKeys" class="px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl text-sm font-medium transition-colors shadow-soft">Save Keys</button></div> </div> </div> </div> | |
| <!-- Toast Notification Container --> | |
| <div id="toastContainer" class="fixed bottom-6 right-6 space-y-3 z-50"></div> | |
| <script> | |
| /** | |
| * ============================================================================= | |
| * AI-Assisted Math Research Workflow - Frontend Application Logic | |
| * (v4: API Fallback, Intro Modal fix, Overlap fix, Key Links, Comment Fixes, Restart Step) | |
| * ============================================================================= | |
| */ | |
| document.addEventListener('DOMContentLoaded', () => { | |
| initApp(); | |
| }); | |
| // --- Constants --- | |
| const API_ENDPOINTS = { openai: 'https://api.openai.com/v1/chat/completions', gemini: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent', grok: 'https://api.grok.x/v1/chat/completions', claude: 'https://api.anthropic.com/v1/messages', }; | |
| const MODELS = { openai: 'gpt-4o', gemini: 'gemini-pro', grok: 'grok-3', claude: 'claude-3-opus-20240229', }; | |
| const STAGES = [ 'ideaGeneration', 'ideaDevelopment', 'firstCritique', 'iterativeRefinement', 'deepCritique', 'consensus', 'paperOutline', 'latexDrafting' ]; | |
| const INTRO_SEEN_FLAG = 'mathResearchIntroSeen_v1'; | |
| // --- State Management --- | |
| let appState = { currentStage: STAGES[0], apiKeys: { openai: null, gemini: null, grok: null, claude: null }, research: { initialIdea: '', potentialDirections: [], selectedDirectionIndex: -1, developedIdea: '', critiques: [], currentIdeaForRefinement: '', grokCritique: null, consensusReport: '', paperOutline: '', latexSections: {}, }, history: [], ui: { isLoading: false, errorMessage: null, currentIteration: 0, selectedSectionForLatex: null, } }; | |
| function updateState(newState) { appState = { ...appState, ...newState, apiKeys: newState.apiKeys !== undefined ? { ...appState.apiKeys, ...newState.apiKeys } : appState.apiKeys, research: newState.research !== undefined ? { ...appState.research, ...newState.research } : appState.research, ui: newState.ui !== undefined ? { ...appState.ui, ...newState.ui } : appState.ui, history: newState.history !== undefined ? newState.history : appState.history, critiques: newState.research?.critiques !== undefined ? newState.research.critiques : appState.research.critiques, latexSections: newState.research?.latexSections !== undefined ? newState.research.latexSections : appState.research.latexSections, }; console.log("State Updated:", appState); updateLoadingState(appState.ui.isLoading); updateErrorDisplay(appState.ui.errorMessage); } | |
| // --- Modal Management --- | |
| function openModal(modalId) { const modal = document.getElementById(modalId); const content = modal.querySelector('.modal-content'); if (!modal || !content) return; modal.classList.remove('hidden'); setTimeout(() => { modal.classList.add('active'); }, 10); } | |
| function closeModal(modalId) { const modal = document.getElementById(modalId); const content = modal.querySelector('.modal-content'); if (!modal || !content) return; modal.classList.remove('active'); setTimeout(() => { modal.classList.add('hidden'); }, 300); } | |
| // --- API Key Management --- | |
| const apiKeyInputs = { openai: document.getElementById('openaiApiKey'), gemini: document.getElementById('geminiApiKey'), grok: document.getElementById('grokApiKey'), claude: document.getElementById('claudeApiKey'), }; function openApiKeyModal() { Object.keys(apiKeyInputs).forEach(key => { if (apiKeyInputs[key]) { apiKeyInputs[key].value = appState.apiKeys[key] || ''; apiKeyInputs[key].type = 'password'; const icon = apiKeyInputs[key].closest('.relative').querySelector('.togglePw i'); if (icon) icon.className = 'far fa-eye'; } }); openModal('apiKeysModal'); } function closeApiKeyModal() { closeModal('apiKeysModal'); } function saveApiKeys() { const newKeys = {}; Object.keys(apiKeyInputs).forEach(key => { if (apiKeyInputs[key]) { newKeys[key] = apiKeyInputs[key].value.trim(); } }); updateState({ apiKeys: newKeys }); try { localStorage.setItem('mathResearchApiKeys', JSON.stringify(appState.apiKeys)); showToast('API keys saved.', 'success'); closeApiKeyModal(); checkApiKeysAndProceed(); } catch (e) { console.error("Failed save keys:", e); showToast('Failed save keys.', 'error'); } } function loadApiKeys() { try { const savedKeys = localStorage.getItem('mathResearchApiKeys'); if (savedKeys) { updateState({ apiKeys: JSON.parse(savedKeys) }); console.log("Keys loaded"); } else { console.log("No keys found"); } } catch (e) { console.error("Failed parse keys:", e); localStorage.removeItem('mathResearchApiKeys'); } } function togglePasswordVisibility(event) { const button = event.target.closest('.togglePw'); if (!button) return; const input = button.closest('.relative').querySelector('input'); const icon = button.querySelector('i'); if (input.type === "password") { input.type = "text"; icon.className = 'far fa-eye-slash'; } else { input.type = "password"; icon.className = 'far fa-eye'; } } | |
| // --- API Client Functions (with Fallback Logic) --- | |
| async function makeApiRequest(url, options, serviceName, apiKey) { /* Checks key internally now */ updateState({ ui: { ...appState.ui, isLoading: true, errorMessage: null } }); try { console.log(`API req: ${serviceName}...`); const response = await fetch(url, options); if (!response.ok) { let body = 'N/A'; try {body = await response.text();} catch(e){} console.error(`${serviceName} Err (${response.status})`, body); throw new Error(`${serviceName} Err (${response.status}).`); } const data = await response.json(); console.log(`${serviceName} Resp:`, data); return data; } catch (error) { console.error(`API error (${serviceName}):`, error); const msg = error instanceof Error ? error.message : String(error); updateState({ ui: { ...appState.ui, isLoading: false, errorMessage: msg } }); throw error; } } | |
| async function callOpenAI(messages, temperature = 0.7) { | |
| const apiKey = appState.apiKeys.openai; | |
| if (!apiKey) throw new Error(`OpenAI API Key missing.`); // OpenAI is the base, no fallback from here | |
| const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; | |
| const body = JSON.stringify({ model: MODELS.openai, messages, temperature }); | |
| const data = await makeApiRequest(API_ENDPOINTS.openai, { method: 'POST', headers, body }, 'OpenAI', apiKey); | |
| if (data.choices?.[0]?.message?.content) return data.choices[0].message.content.trim(); | |
| else throw new Error("Bad OpenAI fmt."); | |
| } | |
| async function callGemini(promptText, temperature = 0.2, maxOutputTokens = 2048) { | |
| const geminiKey = appState.apiKeys.gemini; | |
| const openaiKey = appState.apiKeys.openai; // Default fallback key | |
| if (geminiKey) { | |
| // Use Gemini | |
| const url = `${API_ENDPOINTS.gemini}?key=${geminiKey}`; | |
| const headers = { 'Content-Type': 'application/json' }; | |
| const body = JSON.stringify({ contents: [{ parts: [{ text: promptText }] }], generationConfig: { temperature, maxOutputTokens } }); | |
| const data = await makeApiRequest(url, { method: 'POST', headers, body }, 'Gemini', geminiKey); | |
| if (data.candidates?.[0]?.content?.parts?.[0]?.text) return data.candidates[0].content.parts[0].text.trim(); | |
| else if (data.promptFeedback?.blockReason) throw new Error(`Gemini block: ${data.promptFeedback.blockReason}`); | |
| else throw new Error("Bad Gemini fmt."); | |
| } else if (openaiKey) { | |
| // Fallback to OpenAI | |
| showToast("Gemini key missing. Using OpenAI fallback.", "warning"); | |
| console.log("Fallback: Gemini -> OpenAI"); | |
| const messages = [{ role: 'user', content: promptText }]; // Adapt input | |
| return await callOpenAI(messages, 0.7); // Use OpenAI's default temp for general tasks | |
| } else { | |
| // Both keys missing | |
| throw new Error("Gemini key missing, and fallback OpenAI key also missing."); | |
| } | |
| } | |
| async function callGrok(messages, temperature = 0.7) { | |
| const grokKey = appState.apiKeys.grok; | |
| const openaiKey = appState.apiKeys.openai; | |
| if (grokKey) { | |
| // Try Grok API (placeholder) | |
| showToast("Attempting Grok API call (placeholder).", "info"); | |
| const headers = { 'Authorization': `Bearer ${grokKey}`, 'Content-Type': 'application/json' }; | |
| const body = JSON.stringify({ model: MODELS.grok, messages, temperature }); | |
| try { | |
| const data = await makeApiRequest(API_ENDPOINTS.grok, { method: 'POST', headers, body }, 'Grok', grokKey); | |
| if (data.choices?.[0]?.message?.content) return data.choices[0].message.content.trim(); | |
| else throw new Error("Bad Grok fmt."); | |
| } catch (error) { | |
| console.warn("Grok API call failed:", error.message); | |
| // Do NOT fallback if the key exists but the call fails | |
| return `(Grok API call failed: ${error.message})`; | |
| } | |
| } else if (openaiKey) { | |
| // Fallback to OpenAI because Grok key is missing | |
| showToast("Grok key missing/placeholder. Using OpenAI fallback.", "warning"); | |
| console.log("Fallback: Grok -> OpenAI"); | |
| return await callOpenAI(messages, temperature); // Use Grok's requested temp | |
| } else { | |
| // Both Grok (or placeholder) and OpenAI keys missing | |
| showToast("Grok & Fallback OpenAI keys missing. Skipping Grok step.", "error"); | |
| return "(Grok and Fallback OpenAI keys missing)"; | |
| } | |
| } | |
| async function callClaude(messages, max_tokens = 4000) { | |
| const claudeKey = appState.apiKeys.claude; | |
| const openaiKey = appState.apiKeys.openai; | |
| if (claudeKey) { | |
| // Use Claude | |
| const headers = { 'x-api-key': claudeKey, 'anthropic-version': '2023-06-01', 'Content-Type': 'application/json' }; | |
| const body = JSON.stringify({ model: MODELS.claude, max_tokens, messages }); | |
| const data = await makeApiRequest(API_ENDPOINTS.claude, { method: 'POST', headers, body }, 'Claude', claudeKey); | |
| if (data.content?.[0]?.text) return data.content[0].text.trim(); | |
| else if (data.type === 'error') throw new Error(`Claude err: ${data.error.type}`); | |
| else throw new Error("Bad Claude fmt."); | |
| } else if (openaiKey) { | |
| // Fallback to OpenAI | |
| showToast("Claude key missing. Using OpenAI fallback.", "warning"); | |
| console.log("Fallback: Claude -> OpenAI"); | |
| // Assuming message format is compatible enough for this fallback | |
| return await callOpenAI(messages, 0.7); // Use default temp | |
| } else { | |
| // Both keys missing | |
| throw new Error("Claude key missing, and fallback OpenAI key also missing."); | |
| } | |
| } | |
| // --- Workflow Control --- | |
| function navigateToStage(stageName) { if (!STAGES.includes(stageName)) return; console.log(`Nav to: ${stageName}`); updateState({ currentStage: stageName, ui: { ...appState.ui, errorMessage: null } }); updateProgressBar(stageName); updateStagePanels(stageName); populatePanelContent(stageName); addHistoryEntry(stageName, `Entered: ${getStageFriendlyName(stageName)}`); updateHistorySidebar(); document.getElementById('exportButton').disabled = stageName !== 'latexDrafting' || Object.keys(appState.research.latexSections).length === 0; } | |
| function getStageFriendlyName(stageName) { const names = { ideaGeneration: "Idea Gen", ideaDevelopment: "Develop", firstCritique: "Critique 1", iterativeRefinement: "Refine", deepCritique: "Critique 2", consensus: "Consensus", paperOutline: "Outline", latexDrafting: "Draft" }; return names[stageName] || stageName; } | |
| async function handleAction(actionType, payload = null) { updateState({ ui: { ...appState.ui, isLoading: true, errorMessage: null } }); try { switch (actionType) { | |
| case 'generateIdeas': const idea=document.getElementById('initialIdeaInput').value.trim(); if(!idea){showToast("Enter idea.","warning");throw new Error("Input needed.");} updateState({research:{...appState.research,initialIdea:idea}}); const p1=`Suggest 7 novel math research directions for: "${idea}". Numbered list.`; const resp1=await callOpenAI([{role:'user',content:p1}]); const dirs=resp1.split('\n').map(l=>l.trim()).filter(l=>/^\d+\.\s/.test(l)).map(l=>l.replace(/^\d+\.\s*/,'')); if(dirs.length===0)dirs.push("Parse failed. Raw:", resp1); updateState({research:{...appState.research,potentialDirections:dirs}}); addHistoryEntry(appState.currentStage, `Generated ${dirs.length} ideas`); navigateToStage('ideaDevelopment'); break; | |
| case 'selectIdea': const idx=parseInt(payload,10); updateState({research:{...appState.research,selectedDirectionIndex:idx}}); populatePanelContent('ideaDevelopment'); document.getElementById('developIdeaBtn').disabled=false; break; | |
| case 'developIdea': if(appState.research.selectedDirectionIndex<0){showToast("Select direction.","warning");throw new Error("Select needed.");} const selIdea=appState.research.potentialDirections[appState.research.selectedDirectionIndex]; const p2=`Develop idea: "${selIdea}" (concepts, approaches, challenges).`; const devIdea=await callOpenAI([{role:'user',content:p2}]); updateState({research:{...appState.research,developedIdea:devIdea,currentIdeaForRefinement:devIdea}}); addHistoryEntry(appState.currentStage, `Developed idea.`); navigateToStage('firstCritique'); break; | |
| case 'sendToGeminiCritique1': const idea1=appState.research.developedIdea; if(!idea1)throw new Error("Dev idea missing."); const gp1=`Critique idea (errors, weak, suggestions):\n"${idea1}"`; const fb1=await callGemini(gp1); /* Uses fallback if needed */ const cr1={model:'gemini',iteration:1,feedback:fb1}; updateState({research:{...appState.research,critiques:[cr1]},ui:{...appState.ui,currentIteration:1}}); addHistoryEntry(appState.currentStage, `Got critique 1.`); navigateToStage('iterativeRefinement'); break; | |
| case 'sendToGeminiRefine': const ideaN=appState.research.currentIdeaForRefinement; if(!ideaN)throw new Error("Idea missing."); const iter=appState.ui.currentIteration+1; const gpN=`Critique refined idea (Iter ${iter}, remaining issues, suggestions):\n"${ideaN}"`; const fbN=await callGemini(gpN); /* Uses fallback */ const crN={model:'gemini',iteration:iter,feedback:fbN}; updateState({research:{...appState.research,critiques:[...appState.research.critiques, crN]},ui:{...appState.ui,currentIteration:iter}}); addHistoryEntry(appState.currentStage, `Got critique ${iter}.`); populatePanelContent('iterativeRefinement'); break; | |
| case 'updateRefinedIdea': updateState({research:{...appState.research,currentIdeaForRefinement:payload}}); break; | |
| case 'proceedToDeepCritique': const finalRef=document.getElementById('refinementIdeaInput').value.trim(); updateState({research:{...appState.research,currentIdeaForRefinement:finalRef}}); addHistoryEntry(appState.currentStage, `Finished refine iter ${appState.ui.currentIteration}.`); navigateToStage('deepCritique'); break; | |
| case 'sendToGrok': const ideaG=appState.research.currentIdeaForRefinement; if(!ideaG)throw new Error("Refined idea missing."); const gMsgs=[{role:'system',content:'Critical math researcher.'},{role:'user',content:`Analyze report, find fundamental errors/suggest diff directions:\n"${ideaG}"`}]; const gFb=await callGrok(gMsgs); /* Uses fallback */ const gCr={model:'grok',feedback:gFb}; updateState({research:{...appState.research,grokCritique:gCr}}); addHistoryEntry(appState.currentStage, `Got Grok critique.`); populatePanelContent('deepCritique'); break; | |
| case 'proceedToConsensus': if(!appState.research.grokCritique){showToast("Grok critique needed.","warning");throw new Error("Grok missing.");} addHistoryEntry(appState.currentStage, `Proceed to consensus.`); navigateToStage('consensus'); break; | |
| case 'saveConsensusAndProceedToOutline': const report=document.getElementById('consensusReportInput').value.trim(); if(!report){showToast("Finalize report.","warning");throw new Error("Report empty.");} updateState({research:{...appState.research,consensusReport:report}}); addHistoryEntry(appState.currentStage, `Consensus final.`); navigateToStage('paperOutline'); break; | |
| case 'generateOutline': const reportO=appState.research.consensusReport; if(!reportO)throw new Error("Consensus missing."); const clP1=`Generate detailed academic paper outline (standard sections) based on:\n"${reportO}"`; const outline=await callClaude([{role:'user',content:clP1}]); /* Uses fallback */ updateState({research:{...appState.research,paperOutline:outline}}); addHistoryEntry(appState.currentStage, `Got outline.`); populatePanelContent('paperOutline'); break; | |
| case 'proceedToDrafting': if(!appState.research.paperOutline){showToast("Outline needed.","warning");throw new Error("Outline missing.");} addHistoryEntry(appState.currentStage, `Proceed to draft.`); navigateToStage('latexDrafting'); break; | |
| case 'generateLatexSection': const title=appState.ui.selectedSectionForLatex; if(!title){showToast("Select section.","warning");throw new Error("Section missing.");} const outlineL=appState.research.paperOutline; const reportL=appState.research.consensusReport; if(!outlineL||!reportL)throw new Error("Outline/Report missing."); const clPL=`Generate ONLY LaTeX code for section "${title}". Based on Outline:\n${outlineL}\nReport:\n${reportL}\nAssume AMS. No preamble/doc tags.`; const latexCode=await callClaude([{role:'user',content:clPL}],4000); /* Uses fallback */ const newSections={...appState.research.latexSections,[title]:latexCode}; updateState({research:{...appState.research,latexSections:newSections}}); addHistoryEntry(appState.currentStage, `Gen LaTeX: "${title}".`); populatePanelContent('latexDrafting'); document.getElementById('exportButton').disabled=false; break; | |
| case 'restartCurrentStage': const stage=appState.currentStage; console.log(`Restarting: ${stage}`); let resUpd={}; let uiUpd={}; switch(stage){case 'iterativeRefinement': resUpd.critiques=appState.research.critiques.filter(c=>c.iteration<appState.ui.currentIteration); uiUpd.currentIteration=Math.max(0,appState.ui.currentIteration-1); showToast(`Feedback iter ${appState.ui.currentIteration+1} cleared.`,'info'); break; case 'deepCritique': resUpd.grokCritique=null; showToast(`Grok critique cleared.`,'info'); break; case 'consensus': resUpd.consensusReport=''; showToast(`Consensus report cleared.`,'info'); break; case 'paperOutline': resUpd.paperOutline=''; showToast(`Outline cleared.`,'info'); break; case 'latexDrafting': resUpd.latexSections={}; uiUpd.selectedSectionForLatex=null; document.getElementById('exportButton').disabled=true; showToast(`LaTeX cleared.`,'info'); break; default: showToast(`Restart N/A: ${getStageFriendlyName(stage)}`, 'warning'); throw new Error("Restart N/A.");} updateState({research:{...appState.research,...resUpd},ui:{...appState.ui,...uiUpd}}); populatePanelContent(stage); addHistoryEntry(stage, `Restarted stage.`); updateHistorySidebar(); break; | |
| case 'exportLatex': exportLatexFile(); break; | |
| default: console.warn(`Unknown action: ${actionType}`); throw new Error(`Unknown action: ${actionType}`); | |
| } } catch (error) { console.error(`Action ${actionType} failed:`, error); /* Error display handled by makeApiRequest/updateErrorDisplay */ } finally { updateState({ ui: { ...appState.ui, isLoading: false } }); } } | |
| // --- UI Update Functions --- | |
| // (updateProgressBar, updateStagePanels, populatePanelContent, updateHistorySidebar, showToast, updateLoadingState, updateErrorDisplay, populateLatexSectionSelector, renderGeneratedLatexSections - unchanged from previous version) | |
| function updateProgressBar(currentStageName) { const idx=STAGES.indexOf(currentStageName); const steps=document.querySelectorAll('.progress-step'); const total=STAGES.length; steps.forEach((step, i)=>{const icon=step.querySelector('.step-icon'); icon.classList.remove('bg-gradient-to-r','from-indigo-600','to-blue-500','text-white','border-transparent','from-green-500','to-teal-500'); icon.classList.add('bg-white','text-gray-400','border-gray-200'); if(i<idx){step.classList.remove('active');step.classList.add('completed'); icon.classList.remove('bg-white','text-gray-400','border-gray-200'); icon.classList.add('bg-gradient-to-r','from-green-500','to-teal-500','text-white','border-transparent');} else if(i===idx){step.classList.remove('completed');step.classList.add('active'); icon.classList.remove('bg-white','text-gray-400','border-gray-200'); icon.classList.add('bg-gradient-to-r','from-indigo-600','to-blue-500','text-white','border-transparent');} else{step.classList.remove('active','completed');}}); const fill=document.getElementById('progressBarFill'); const perc=idx>0?(idx/(total-1))*100:0; fill.style.width=`${perc}%`; document.getElementById('currentStageLabel').textContent=getStageFriendlyName(currentStageName); document.getElementById('currentStepIndicator').textContent=`Step ${idx+1} of ${total}`; } | |
| function updateStagePanels(stageName) { document.querySelectorAll('.stage-panel').forEach(p=>p.classList.add('hidden')); const panel=document.getElementById(`${stageName}Stage`); if(panel){panel.classList.remove('hidden'); panel.classList.remove('fade-in'); void panel.offsetWidth; panel.classList.add('fade-in');} else {console.error(`Panel missing: ${stageName}Stage`);} } | |
| function populatePanelContent(stageName) { console.log(`Populating: ${stageName}`); const R=appState.research; const UI=appState.ui; const marked=window.marked||((t)=>`<pre>${t}</pre>`); switch(stageName){ case 'ideaGeneration':document.getElementById('initialIdeaInput').value=R.initialIdea||''; break; case 'ideaDevelopment': const list=document.getElementById('ideaListContainer'); list.innerHTML=''; if(R.potentialDirections.length>0){R.potentialDirections.forEach((idea,i)=>{const item=document.createElement('div'); item.className=`idea-item p-3 border rounded-lg cursor-pointer hover:bg-gray-100 t-c text-sm ${i===R.selectedDirectionIndex?'selected':''}`; item.textContent=idea; item.dataset.index=i; item.onclick=()=>handleAction('selectIdea',i); list.appendChild(item);});} else {list.innerHTML='<p class="t-s t-g-500">No ideas.</p>';} const selD=document.getElementById('selectedIdeaDisplay'); if(R.selectedDirectionIndex>=0){selD.textContent=R.potentialDirections[R.selectedDirectionIndex]; document.getElementById('developIdeaBtn').disabled=false;} else {selD.innerHTML='<span class="italic t-g-500">Select.</span>'; document.getElementById('developIdeaBtn').disabled=true;} break; case 'firstCritique':document.getElementById('critique1-developedIdea').innerHTML=R.developedIdea?marked.parse(R.developedIdea):'No idea.'; break; case 'iterativeRefinement': document.getElementById('refinementIdeaInput').value=R.currentIdeaForRefinement||''; const lastC=R.critiques.length>0?R.critiques[R.critiques.length-1]:null; document.getElementById('refinementFeedbackDisplay').innerHTML=lastC?marked.parse(lastC.feedback):'<p class="t-s t-g-500">No feedback.</p>'; document.getElementById('iterationCount').textContent=UI.currentIteration; const convP=Math.min(10+UI.currentIteration*15,90); document.getElementById('convergenceBar').style.width=`${convP}%`; document.getElementById('convergenceText').textContent=`${convP}% (Iter ${UI.currentIteration})`; break; case 'deepCritique': document.getElementById('critique2-refinedIdea').innerHTML=R.currentIdeaForRefinement?marked.parse(R.currentIdeaForRefinement):'No idea.'; const grokC=document.getElementById('grokFeedbackDisplayContainer'); const grokD=document.getElementById('grokFeedbackDisplay'); if(R.grokCritique){grokD.innerHTML=marked.parse(R.grokCritique.feedback); grokC.classList.remove('hidden');} else {grokC.classList.add('hidden'); grokD.innerHTML='<p class="t-s t-g-500">Grok cleared/N/A.</p>';} break; case 'consensus': document.getElementById('consensus-refinedIdea').innerHTML=R.currentIdeaForRefinement?marked.parse(R.currentIdeaForRefinement):'N/A'; document.getElementById('consensus-grokCritique').innerHTML=R.grokCritique?marked.parse(R.grokCritique.feedback):'N/A'; const consI=document.getElementById('consensusReportInput'); if(!R.consensusReport&&R.currentIdeaForRefinement){let pf=`Consensus:\n\n-- Refined --\n${R.currentIdeaForRefinement}\n\n-- Grok --\n${R.grokCritique?.feedback||'N/A'}`; consI.value=pf; updateState({research:{...R,consensusReport:pf}});} else {consI.value=R.consensusReport;} break; case 'paperOutline': document.getElementById('outline-consensusReport').innerHTML=R.consensusReport?marked.parse(R.consensusReport):'N/A'; const outC=document.getElementById('outlineDisplayContainer'); const outD=document.getElementById('outlineDisplay'); if(R.paperOutline){outD.innerHTML=marked.parse(R.paperOutline); outC.classList.remove('hidden');} else {outC.classList.add('hidden'); outD.innerHTML='<p class="t-s t-g-500">Outline cleared/N/A.</p>';} break; case 'latexDrafting': document.getElementById('latex-paperOutline').innerHTML=R.paperOutline?marked.parse(R.paperOutline):'N/A'; populateLatexSectionSelector(); renderGeneratedLatexSections(); document.getElementById('exportButton').disabled=Object.keys(R.latexSections).length===0; break; } } | |
| function updateHistorySidebar() { const sb=document.getElementById('historySidebar'); sb.innerHTML=''; if(appState.history.length===0){sb.innerHTML='<p class="t-xs t-g-400">Steps here.</p>'; return;} const icons={ideaGeneration:'lightbulb',ideaDevelopment:'seedling',firstCritique:'comment-medical',iterativeRefinement:'sync-alt',deepCritique:'search',consensus:'handshake',paperOutline:'list-ol',latexDrafting:'file-alt'}; const colors={ideaGeneration:'indigo',ideaDevelopment:'green',firstCritique:'amber',iterativeRefinement:'blue',deepCritique:'pink',consensus:'purple',paperOutline:'teal',latexDrafting:'orange'}; appState.history.forEach((item,idx)=>{const s=item.stage; const icon=icons[s]||'question-circle'; const clr=colors[s]||'gray'; const active=s===appState.currentStage&&idx===appState.history.length-1; const el=document.createElement('div'); el.className=`sidebar-item p-3 t-s rounded-xl ${active?'active':''}`; el.innerHTML=`<div class="flex items-center space-x-3"><div class="w-8 h-8 rounded-lg bg-${clr}-100 text-${clr}-600 flex items-center justify-center shrink-0"><i class="fas fa-${icon} t-s"></i></div><div><div class="font-medium text-gray-700 text-xs">${item.summary}</div><div class="text-xs text-gray-400">${item.timestamp}</div></div></div>`; sb.appendChild(el);}); sb.scrollTop=sb.scrollHeight; } | |
| function addHistoryEntry(stage, summary) { const ts = new Date().toLocaleTimeString([], { hour:'2-digit', minute:'2-digit' }); const entry={stage,summary,timestamp:ts}; const last=appState.history[appState.history.length-1]; if(!last||!(last.stage===stage&&last.summary===summary)){updateState({history:[...appState.history,entry]});} } | |
| function showToast(message, type='info') { const cont=document.getElementById('toastContainer'); const toast=document.createElement('div'); toast.className=`px-6 py-3 rounded-xl shadow-hard text-white flex items-center space-x-2 z-50 transform transition-all duration-300 translate-y-2 opacity-0`; let bg='bg-indigo-600'; let icon='info-circle'; if(type==='success'){bg='bg-green-600';icon='check-circle';} if(type==='error'){bg='bg-red-600';icon='exclamation-circle';} if(type==='warning'){bg='bg-amber-600';icon='exclamation-triangle';} toast.classList.add(bg); toast.innerHTML=`<i class="fas fa-${icon} shrink-0"></i><span>${message}</span>`; cont.appendChild(toast); setTimeout(()=>{toast.classList.remove('translate-y-2','opacity-0'); toast.classList.add('translate-y-0','opacity-100');},10); setTimeout(()=>{toast.classList.remove('translate-y-0','opacity-100'); toast.classList.add('translate-y-2','opacity-0'); setTimeout(()=>{toast.remove();},300);},4000); } | |
| function updateLoadingState(isLoading) { const spinner=document.getElementById('loadingSpinner'); const btns=document.querySelectorAll('button[data-action]'); if(isLoading){spinner.classList.remove('hidden');btns.forEach(b=>b.disabled=true);} else {spinner.classList.add('hidden'); btns.forEach(b=>b.disabled=false); const devBtn=document.getElementById('developIdeaBtn'); if(devBtn)devBtn.disabled=appState.research.selectedDirectionIndex<0; const genSecBtn=document.getElementById('generateSectionBtn'); if(genSecBtn)genSecBtn.disabled=!appState.ui.selectedSectionForLatex;} document.getElementById('exportButton').disabled=isLoading||appState.currentStage!=='latexDrafting'||Object.keys(appState.research.latexSections).length===0; } | |
| function updateErrorDisplay(errorMessage) { const display=document.getElementById('errorDisplay'); const text=document.getElementById('errorMessageText'); if(errorMessage){text.textContent=errorMessage; display.classList.remove('hidden');} else {text.textContent=''; display.classList.add('hidden');} } | |
| function populateLatexSectionSelector() { const select=document.getElementById('sectionSelect'); const btn=document.getElementById('generateSectionBtn'); select.innerHTML='<option value="">-- Select Section --</option>'; select.value=""; updateState({ui:{...appState.ui,selectedSectionForLatex:null}}); btn.disabled=true; if(!appState.research.paperOutline)return; const lines=appState.research.paperOutline.split('\n'); const titles=lines.map(l=>l.trim()).filter(l=>l.match(/^#*\s*\d*\.?\s*[A-Za-z]/)).map(l=>l.replace(/^#*\s*\d*\.?\s*/,'').trim()); if(titles.length>0){titles.forEach(title=>{if(!appState.research.latexSections[title]){const opt=document.createElement('option'); opt.value=title; opt.textContent=title; select.appendChild(opt);}}); select.onchange=(e)=>{updateState({ui:{...appState.ui,selectedSectionForLatex:e.target.value}}); btn.disabled=!e.target.value;};} else {select.innerHTML+='<option value="" disabled>Parse failed</option>';} } | |
| function renderGeneratedLatexSections() { const container=document.getElementById('latexSectionsContainer'); container.innerHTML=''; if(Object.keys(appState.research.latexSections).length===0){container.innerHTML='<p class="t-s t-g-500">No LaTeX generated.</p>'; return;} const outlineSects=appState.research.paperOutline.split('\n').map(l=>l.trim()).filter(l=>l.match(/^#*\s*\d*\.?\s*[A-Za-z]/)).map(l=>l.replace(/^#*\s*\d*\.?\s*/,'').trim()); const sortedTitles=Object.keys(appState.research.latexSections).sort((a,b)=>{const iA=outlineSects.indexOf(a); const iB=outlineSects.indexOf(b); if(iA===-1&&iB===-1)return 0; if(iA===-1)return 1; if(iB===-1)return -1; return iA-iB;}); sortedTitles.forEach(title=>{const latexCode=appState.research.latexSections[title]; const div=document.createElement('div'); div.className='latex-section'; div.innerHTML=`<div class="latex-section-header">${title}</div><div class="latex-section-content"><div class="latex-code-view"><h5 class="t-xs font-semibold t-g-500 mb-2 uppercase">LaTeX Code</h5><pre class="t-xs"><code class="language-latex hljs"></code></pre></div><div class="latex-preview-view"><h5 class="t-xs font-semibold t-g-500 mb-2 uppercase">Preview</h5><div class="latex-render-target t-s"></div></div></div>`; const codeEl=div.querySelector('code'); const rendEl=div.querySelector('.latex-render-target'); codeEl.textContent=latexCode; try{hljs.highlightElement(codeEl);}catch(e){console.error("hljs err:",e);} try{katex.render(latexCode,rendEl,{displayMode:true,throwOnError:false,macros:{"\\R":"\\mathbb{R}"}});} catch(e){console.error(`KaTeX err "${title}":`,e); rendEl.textContent=`Render Error: ${e.message}`; rendEl.classList.add('text-red-600','t-xs');} container.appendChild(div);}); } | |
| // --- Export Functionality --- | |
| function exportLatexFile() { if(Object.keys(appState.research.latexSections).length===0){showToast("No LaTeX to export.","warning");return;} let fullLatex=`\\documentclass{article}\n\\usepackage{amsmath,amssymb,amsthm,graphicx}\n\\usepackage[utf8]{inputenc}\n\\usepackage[margin=1in]{geometry}\n\\title{Research Paper}\n\\author{AI User}\n\\date{\\today}\n\\newtheorem{theorem}{Theorem}[section]\n\\newtheorem{lemma}[theorem]{Lemma}\n\\newtheorem{proposition}[theorem]{Proposition}\n\\newtheorem{corollary}[theorem]{Corollary}\n\\theoremstyle{definition}\n\\newtheorem{definition}[theorem]{Definition}\n\\newtheorem{example}[theorem]{Example}\n\\theoremstyle{remark}\n\\newtheorem*{remark}{Remark}\n\\begin{document}\n\\maketitle\n\n`; const outlineSects=appState.research.paperOutline.split('\n').map(l=>l.trim()).filter(l=>l.match(/^#*\s*\d*\.?\s*[A-Za-z]/)).map(l=>l.replace(/^#*\s*\d*\.?\s*/,'').trim()); const sortedTitles=Object.keys(appState.research.latexSections).sort((a,b)=>{const iA=outlineSects.indexOf(a);const iB=outlineSects.indexOf(b); if(iA===-1&&iB===-1)return 0; if(iA===-1)return 1; if(iB===-1)return -1; return iA-iB;}); sortedTitles.forEach(title=>{let cmd='\\section'; const lowerT=title.toLowerCase(); if(lowerT.includes('conclusion')||lowerT.includes('references')||lowerT.includes('bibliography'))cmd='\\section*'; else if(lowerT.includes('appendix'))cmd='\\appendix\n\\section'; fullLatex+=`${cmd}{${title}}\n\n${appState.research.latexSections[title]}\n\n`;}); fullLatex+=`\n\\end{document}\n`; const blob=new Blob([fullLatex],{type:'application/x-latex'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download='math_research_paper.tex'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast("LaTeX export initiated.","success"); } | |
| // --- Utility Functions --- | |
| function debounce(func,wait){let timeout;return function executedFunction(...args){const later=()=>{clearTimeout(timeout);func(...args);};clearTimeout(timeout);timeout=setTimeout(later,wait);};} | |
| // --- Event Listener Setup --- | |
| function initializeEventListeners() { document.getElementById('apiKeysBtn').addEventListener('click', openApiKeyModal); document.getElementById('saveApiKeys').addEventListener('click', saveApiKeys); document.getElementById('cancelApiKeys').addEventListener('click', closeApiKeyModal); document.getElementById('apiKeysModal').addEventListener('click',(e)=>{if(e.target===document.getElementById('apiKeysModal'))closeApiKeyModal();}); document.getElementById('apiKeysModal').addEventListener('click', togglePasswordVisibility); const stageContent=document.getElementById('stageContent'); stageContent.addEventListener('click',(e)=>{const btn=e.target.closest('button[data-action]'); if(btn&&!btn.disabled){const action=btn.dataset.action; const payload=btn.dataset.payload; handleAction(action,payload);}}); const refInput=document.getElementById('refinementIdeaInput'); if(refInput){refInput.addEventListener('input',debounce((e)=>{if(appState.currentStage==='iterativeRefinement'){handleAction('updateRefinedIdea',e.target.value);}},500));} document.getElementById('exportButton').addEventListener('click', exportLatexFile); console.log("Main App Listeners initialized."); /* TODO (Deferred): View Previous */ } | |
| // --- Initialization Function --- | |
| function checkApiKeysAndProceed() { const required=['openai','gemini','claude']; /* Grok is optional/placeholder */ const missing=required.filter(key=>!appState.apiKeys[key]); if(missing.length > 0){showToast(`Missing keys for core functionality: ${missing.join(', ')}. Fallbacks may occur.`,"warning"); return false; } else if (!appState.apiKeys.grok) { showToast("Grok key missing (placeholder OK). OpenAI fallback will be used.", "info"); } return true; } | |
| function initializeAppLogic() { loadApiKeys(); initializeEventListeners(); navigateToStage(appState.currentStage); console.log("App Initialized."); checkApiKeysAndProceed(); } | |
| function initApp() { console.log("Initializing App..."); const script=document.createElement('script'); script.src='https://cdn.jsdelivr.net/npm/marked/marked.min.js'; const setupIntroListeners = () => { document.getElementById('closeIntroModalBtn').addEventListener('click', () => { closeModal('introModal'); localStorage.setItem(INTRO_SEEN_FLAG, 'true'); initializeAppLogic(); }); document.getElementById('introModal').addEventListener('click', (e)=>{ if(e.target===document.getElementById('introModal')) { closeModal('introModal'); localStorage.setItem(INTRO_SEEN_FLAG, 'true'); initializeAppLogic(); } }); console.log("Intro listeners attached."); }; script.onload=() => { console.log("Marked.js loaded."); setupIntroListeners(); const introSeen = localStorage.getItem(INTRO_SEEN_FLAG); if (!introSeen) { openModal('introModal'); } else { initializeAppLogic(); } }; script.onerror=() => { console.error("Failed Marked.js load"); showToast("Markdown parser failed.","error"); setupIntroListeners(); const introSeen = localStorage.getItem(INTRO_SEEN_FLAG); if (!introSeen) { openModal('introModal'); } else { initializeAppLogic(); } }; document.head.appendChild(script); } | |
| </script> | |
| </body> | |
| </html> |