Spaces:
Running
Running
File size: 69,640 Bytes
644f26c 6b7f862 0dc4583 644f26c 6b7f862 644f26c 6b7f862 644f26c 0dc4583 644f26c 6b7f862 0dc4583 6b7f862 644f26c 6b7f862 644f26c 6b7f862 0dc4583 644f26c 6b7f862 644f26c 6b7f862 644f26c 6b7f862 644f26c 6b7f862 644f26c 6b7f862 644f26c 6b7f862 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
<!DOCTYPE html>
<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> |