arnaucube
arnaucube/study-labPublic Self-hosted repository
study-lab/index.html
index.htmlRaw
  1<!doctype html>
  2<html lang="en">
  3<head>
  4  <meta charset="utf-8">
  5  <meta name="viewport" content="width=device-width, initial-scale=1">
  6  <meta name="color-scheme" content="light dark">
  7  <title>Study Pal — Branch your thinking</title>
  8  <script>
  9    try {
 10      document.documentElement.dataset.theme = localStorage.getItem('study-pal:theme') ||
 11        (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
 12    } catch { document.documentElement.dataset.theme = 'light'; }
 13  </script>
 14  <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
 15  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">
 16  <link rel="stylesheet" href="styles.css">
 17</head>
 18<body>
 19  <header class="topbar">
 20    <div class="brand-group">
 21      <button id="toggle-history" class="history-toggle" type="button" aria-label="Toggle conversations sidebar" aria-expanded="true">☰</button>
 22      <a id="study-pal-home" class="brand" href="#" aria-label="Open a new map">
 23        <span class="brand-mark" aria-hidden="true">S</span>
 24        <span>Study Pal</span>
 25      </a>
 26    </div>
 27    <div class="top-actions">
 28      <button id="open-usage" class="usage-button" type="button" aria-haspopup="dialog" aria-label="Open API usage details">
 29        <span id="month-tokens">0</span> <span class="usage-caption">tokens</span>
 30      </button>
 31      <button id="theme-toggle" class="theme-toggle" type="button" aria-label="Switch to dark theme" title="Switch theme">
 32        <span aria-hidden="true">☾</span>
 33      </button>
 34      <button id="open-settings" class="button button-dark" type="button" aria-haspopup="dialog">
 35        <span class="status-dot" id="key-status" aria-hidden="true"></span>
 36        <span class="settings-label">Settings</span>
 37      </button>
 38    </div>
 39  </header>
 40
 41  <main class="app-shell">
 42    <aside class="history-panel" aria-label="Past conversations">
 43      <div class="history-heading">
 44        <div>
 45          <span class="eyebrow">Your library</span>
 46          <h2>Conversations</h2>
 47        </div>
 48      </div>
 49      <button id="new-map-sidebar" class="new-map-sidebar" type="button"><span aria-hidden="true">+</span> New map</button>
 50      <nav id="history-list" class="history-list" aria-label="Saved conversations"></nav>
 51      <p id="history-empty" class="history-empty" hidden>No saved conversations yet.</p>
 52      <p class="history-storage"><span aria-hidden="true">●</span> Stored in this browser</p>
 53    </aside>
 54
 55    <section class="workspace" aria-label="Conversation">
 56      <div id="welcome" class="welcome">
 57        <div class="eyebrow">A thinking space that branches</div>
 58        <h1>Follow every<br><em>line of thought.</em></h1>
 59        <p>Ask a question, explore the answer, then branch from any earlier idea without losing your way.</p>
 60        <div class="starter-grid" aria-label="Example questions">
 61          <button class="starter" data-prompt="Explain and prove the Tower Law for finite field extensions K ⊆ F ⊆ L. Show how bases compose, then work through the example Q ⊆ Q(√2) ⊆ Q(√2, √3) and relate it to Galois theory.">
 62            <span class="starter-icon">⊆</span>
 63            <strong>Explain the Tower Law from Galois Theory</strong>
 64            <small>Degrees of extensions in Galois theory</small>
 65          </button>
 66          <button class="starter" data-prompt="Over an algebraically closed field k, explain and prove the affine algebraic set–radical ideal correspondence from Hilbert's Nullstellensatz, carefully showing how the algebra and geometry reverse inclusions.">
 67            <span class="starter-icon">V</span>
 68            <strong>Connect ideals and geometry</strong>
 69            <small>Understand the Nullstellensatz correspondence</small>
 70          </button>
 71          <button class="starter" data-prompt="Let f: X → Y be a dominant morphism of irreducible algebraic varieties. Prove the fiber dimension theorem, explain why the generic fiber has dimension dim(X) − dim(Y), and give an example where special fibers have larger dimension.">
 72            <span class="starter-icon">f</span>
 73            <strong>Study fibers of algebraic varieties</strong>
 74            <small>Generic and special fibers of varieties</small>
 75          </button>
 76        </div>
 77      </div>
 78
 79      <div id="conversation" class="conversation" aria-live="polite"></div>
 80
 81      <div class="composer-wrap">
 82        <div id="branch-context" class="branch-context" hidden>
 83          <span>Branching from <strong id="branch-label"></strong></span>
 84          <button id="clear-branch" type="button" aria-label="Return to latest node">×</button>
 85        </div>
 86        <form id="composer" class="composer">
 87          <div id="attachments" class="attachments" aria-label="PDFs in this map" hidden></div>
 88          <textarea id="prompt" rows="1" placeholder="Ask anything…" aria-label="Message" maxlength="30000"></textarea>
 89          <div class="composer-footer">
 90            <div class="composer-tools">
 91              <button id="upload-pdf" class="upload-pdf" type="button" title="Add PDFs to this map" aria-label="Add PDFs to this map">
 92                <span aria-hidden="true">+</span> PDF
 93              </button>
 94              <span class="hint"><kbd>Enter</kbd> send · <kbd>Shift</kbd> + <kbd>Enter</kbd> new line</span>
 95            </div>
 96            <button id="send" class="send" type="submit" aria-label="Send message">↑</button>
 97          </div>
 98          <input id="pdf-input" type="file" accept="application/pdf,.pdf" multiple hidden>
 99        </form>
100        <p class="privacy-note">Your map stays in this browser. Added PDFs are uploaded to the configured API.</p>
101      </div>
102    </section>
103
104    <aside class="map-panel" aria-label="Concept map">
105      <div class="map-heading">
106        <div>
107          <span class="eyebrow">Your exploration</span>
108          <h2>Concept map</h2>
109        </div>
110        <button id="collapse-map" class="icon-button" type="button" aria-label="Collapse concept map">→</button>
111      </div>
112      <p id="map-help" class="map-help">Select a node to revisit it and start a new branch.</p>
113      <nav id="tree" class="tree" aria-label="Question branches"></nav>
114      <div id="map-empty" class="map-empty">
115        <span class="empty-orbit">✦</span>
116        <p>Your questions will grow into a map here.</p>
117      </div>
118      <div class="map-stats">
119        <span><strong id="node-count">0</strong> nodes</span>
120        <span><strong id="branch-count">0</strong> branches</span>
121      </div>
122    </aside>
123    <button id="expand-map" class="expand-map" type="button" hidden>← Concept map</button>
124  </main>
125
126  <div id="selection-menu" class="selection-menu" role="dialog" aria-label="Ask about selected text" hidden>
127    <div class="selection-topline">
128      <span>Ask about selection</span>
129      <button id="close-selection" type="button" aria-label="Close">×</button>
130    </div>
131    <blockquote id="selection-preview"></blockquote>
132    <select id="selection-action" aria-label="Question type">
133      <option value="Explain this passage in simpler terms.">Explain simply</option>
134      <option value="Go deeper into this idea and its implications.">Go deeper</option>
135      <option value="Give me a concrete example of this.">Give an example</option>
136      <option value="What assumptions does this passage make?">Challenge assumptions</option>
137      <option value="custom">Write my own…</option>
138    </select>
139    <textarea id="selection-custom" rows="2" placeholder="What would you like to ask?" hidden></textarea>
140    <button id="ask-selection" class="button button-dark" type="button">Ask</button>
141  </div>
142
143  <dialog id="settings-dialog" class="settings-dialog">
144    <form id="settings-form" method="dialog">
145      <div class="dialog-heading">
146        <div>
147          <span class="eyebrow">Connection</span>
148          <h2>API settings</h2>
149        </div>
150        <button id="close-settings" class="icon-button" type="button" aria-label="Close settings">×</button>
151      </div>
152      <p class="settings-copy">Use an OpenAI API key (not a ChatGPT password). It is stored only in this browser and sent directly to the configured API.</p>
153      <label>
154        API key
155        <div class="secret-field">
156          <input id="api-key" type="password" placeholder="sk-…" autocomplete="off" spellcheck="false">
157          <button id="toggle-key" type="button" aria-label="Show API key">Show</button>
158        </div>
159      </label>
160      <label>
161        Model
162        <input id="model" type="text" value="gpt-5-mini" placeholder="gpt-5-mini" spellcheck="false">
163      </label>
164      <details>
165        <summary>Advanced</summary>
166        <label>
167          Responses API endpoint
168          <input id="api-url" type="url" value="https://api.openai.com/v1/responses" spellcheck="false">
169        </label>
170        <label>
171          System instructions
172          <textarea id="system-prompt" rows="3">You are a patient technical tutor. Use clear Markdown and LaTeX where useful. Use mathematical language, assume graduate level knowledge. Build on the conversation context and explain assumptions.</textarea>
173        </label>
174      </details>
175      <div class="security-callout"><strong>Local-use note</strong> — any script running on this origin can access local storage. Use a restricted project key and never deploy this frontend publicly with a saved key.</div>
176      <div class="dialog-actions">
177        <button id="clear-history" class="button button-ghost danger" type="button">Clear chat history</button>
178        <button id="forget-key" class="button button-ghost danger" type="button">Forget key</button>
179        <button class="button button-dark" value="save" type="submit">Save settings</button>
180      </div>
181    </form>
182  </dialog>
183
184  <dialog id="usage-dialog" class="settings-dialog usage-dialog">
185    <form method="dialog">
186      <div class="dialog-heading">
187        <div>
188          <span class="eyebrow">Local estimate</span>
189          <h2>API usage</h2>
190        </div>
191        <button id="close-usage" class="icon-button" type="button" aria-label="Close API usage">×</button>
192      </div>
193      <p class="settings-copy"><span id="usage-month"></span> · Requests made from this browser</p>
194
195      <div class="usage-summary">
196        <div class="usage-stat primary">
197          <span>Tokens</span>
198          <strong id="usage-total">0</strong>
199        </div>
200        <div class="usage-stat">
201          <span>Estimated cost</span>
202          <strong id="usage-cost">$0.00</strong>
203        </div>
204        <div class="usage-stat">
205          <span>Requests</span>
206          <strong id="usage-requests">0</strong>
207        </div>
208      </div>
209
210      <section class="usage-section" aria-labelledby="token-breakdown-title">
211        <h3 id="token-breakdown-title">Token breakdown</h3>
212        <div class="token-breakdown">
213          <span>Input <strong id="usage-input">0</strong></span>
214          <span>Cached input <strong id="usage-cached">0</strong></span>
215          <span>Output <strong id="usage-output">0</strong></span>
216        </div>
217      </section>
218
219      <section class="usage-section" aria-labelledby="models-title">
220        <h3 id="models-title">By model</h3>
221        <div id="usage-models" class="usage-models"></div>
222      </section>
223
224      <section class="usage-section" aria-labelledby="recent-title">
225        <h3 id="recent-title">Recent requests</h3>
226        <div id="usage-recent" class="usage-recent"></div>
227      </section>
228
229      <p id="usage-note" class="usage-note"></p>
230      <p class="usage-disclaimer">This is not your OpenAI invoice. It excludes requests from other browsers and applications, incomplete requests without returned usage, taxes, credits, tools, special processing, and usage recorded before this tracker was added. Prices can change.</p>
231      <div class="dialog-actions">
232        <button id="reset-usage" class="button button-ghost danger" type="button">Reset local history</button>
233        <a class="button button-ghost usage-dashboard" href="https://platform.openai.com/usage" target="_blank" rel="noopener noreferrer">Official dashboard ↗</a>
234        <button class="button button-dark" value="close" type="submit">Done</button>
235      </div>
236    </form>
237  </dialog>
238
239  <div id="toast" class="toast" role="status" aria-live="polite"></div>
240
241  <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
242  <script defer src="app.js"></script>
243</body>
244</html>