study-lab/README.md
README.mdRaw
1# Study Lab
2
3Study Lab is a local-first, branching AI chat for exploring technical concepts. Every question becomes a node in a concept map. Select any earlier node to continue from that point, or select text inside an answer and ask a focused follow-up.
4
5The app is intentionally frontend-only: there is no build step, package manager, backend, or database. Conversations and settings persist in the browser's `localStorage`, while responses stream directly from either OpenAI or OpenRouter's Responses API.
6
7## Features
8
9- can branch off any previous answer
10- select a chunk of text from an answer and ask about it
11- vim-like shortcuts
12- plain js + html + css
13- provider API keys and history stay local in your browser; questions go directly to the selected provider
14- the composer shows the active model and provider at a glance
15- each generated answer records and displays the model and provider that produced it
16- you see token usage and cost estimations
17
18other:
19- branching conversation tree with clickable question nodes
20- root-to-current-branch conversation view
21- ask about selected answer text with suggested or custom follow-ups
22- streaming responses
23- markdown, fenced code blocks, and LaTeX rendering through KaTeX
24- persistent sessions and API settings in `localStorage`
25- lightweight vanilla HTML, CSS, and JavaScript
26- responsive concept map for desktop and small screens
27- persistent light/dark theme with system-theme detection
28- monthly token tracking with per-model details and a local billing estimate
29- month-to-date actual spend retrieved from the OpenAI organization Costs API and the configured OpenRouter key
30- live input and output token pricing beside model names in Settings
31- independent chat-history clearing and per-conversation deletion
32- multiple persistent maps with a browsable conversation sidebar
33- per-map PDF and Markdown context, uploaded once through the Files API
34- debounced search across map titles, questions, answers, and attachment names
35- collapsible, zoomable concept trees with active-path highlighting
36- vim-style keyboard navigation for nodes, maps, search, and the composer
37- large-answer optimizations through throttled stream painting
38
39
40
41## Run locally
42
43You need a modern browser, an internet connection, and either an [OpenAI API key](https://platform.openai.com/api-keys) or an [OpenRouter API key](https://openrouter.ai/settings/keys). A ChatGPT subscription/password is not an OpenAI API key.
44
45From this directory, start any static file server. Python is commonly preinstalled:
46
47```bash
48python3 -m http.server 8080
49```
50
51Then open <http://localhost:8080>.
52
53### Install on Android
54
55Serve Study Lab over HTTPS (or use `localhost` while testing), open it in Chrome on Android, and choose **Install app** when the button appears. You can also use Chrome's menu and choose **Install app** or **Add to Home screen**. The installed app opens in its own window and caches its local interface for offline startup; sending AI requests still requires an internet connection.
56
57Alternatively, with Node.js:
58
59```bash
60npx --yes serve .
61```
62
63Open the local URL printed by `serve`.
64
651. Choose **Settings**.
662. Paste the API key for each provider you plan to use.
673. Add model cards, choose OpenAI or OpenRouter on each card, and enter any model ID available to that account. OpenRouter model IDs use the `provider/model` form, such as `openai/gpt-5-mini`.
684. Select the radio button on the model card that should be used by default.
695. Save and ask a question.
70
71Settings stores the OpenAI and OpenRouter API keys once at the top. Model cards below can independently choose a provider, model ID, and endpoint; one card is selected as the default connection. A fresh installation starts with one OpenAI model card, and **+ Add model** creates more.
72
73The usage dialog can retrieve actual month-to-date provider spend. OpenRouter uses the configured API key and reports spend for that key. OpenAI's organization Costs API requires a separate Admin API key created by an organization owner; because that key has elevated privileges, Study Lab keeps it only in `sessionStorage` for the current browser tab rather than in persistent local storage. Provider-reported totals may lag recent requests and can differ from invoices because of taxes, credit-purchase fees, or other adjustments.
74
75To add reference material, choose **+ PDF / MD** beside the composer and select one or more PDF or Markdown (`.md`) files. The files are attached to the current map and included with questions sent through the same provider, including questions asked from older branches. Each file must be under 50 MB, and files attached through one provider in a map must total under 50 MB.
76
77The endpoint on each model card is configurable under **Endpoint**. OpenAI defaults to `https://api.openai.com/v1/responses`; OpenRouter defaults to `https://openrouter.ai/api/v1/responses`.
78
79## How branching works
80
81Each node stores its parent ID. Clicking a node makes it the active context and displays only the path from the root to that node. The next question becomes its child. Selecting text within an answer and choosing **Ask** creates a child of the answer's node, even if another branch was active.
82
83The complete parent-path conversation is sent with each request. This makes every branch self-contained and avoids coupling local history to provider-side response IDs.
84
85
86
87## Multiple maps
88
89Choose **New map** in the header or conversation sidebar to open a blank map. The current map is retained automatically. Use the left **Conversations** sidebar to switch between saved maps; each entry shows its title, node count, and last activity date. Use ✎ to rename a map or × to delete its entire conversation. A custom title remains unchanged as new questions are added; submitting a blank title restores automatic naming. Existing sessions from the original single-map storage format are migrated automatically.
90
91**Settings → Clear chat history** is the explicit destructive action that removes all saved maps. It does not remove the API key, other settings, or the separate API-usage ledger.
92
93## Search and navigation
94
95Use the search field in the conversation sidebar—or press `/` outside an input—to search all saved map titles, questions, answers, and attachment names. Results link directly to the matching map or question node. Search is debounced and capped at 60 displayed results to keep large workspaces responsive.
96
97The concept-map toolbar can expand or collapse all branches and zoom the tree from 80% to 140%. Individual branches have disclosure arrows, and their collapsed state persists with the map. The current root-to-node path remains highlighted.
98
99
100
101Vim-style shortcuts work whenever a text field or dialog control is not focused:
102
103- `j` / `k` — next / previous visible concept node
104- `h` / `l` — parent / first child; `l` expands a collapsed active node first
105- `gg` / `G` — first / last visible node
106- `J` / `K` — next / previous conversation
107- `Space` — collapse or expand the active node
108- `/` — focus cross-conversation search
109- `i` — focus the question composer
110- `Esc` — leave input or search mode
111- `?` — open the in-app shortcut reference
112
113
114
115## File context
116
117PDF and Markdown files are uploaded directly from the browser to the selected provider's Files endpoint, derived from its configured Responses API URL. Only their file IDs, types, providers, names, and sizes are stored in `localStorage`; the file contents are not copied into browser storage. PDF page images use low visual detail to reduce token use, while extracted PDF text and Markdown text remain available to the model. Use a model that supports the selected file type.
118
119File IDs belong to the provider that received the upload. If you switch providers, files uploaded to the other provider remain visible but are dimmed and are not sent. Upload the file again while the new provider is selected to use it there.
120
121PDF parsing adds extracted text and page images to the prompt, so large books can consume substantial input tokens on every question. The header usage tracker includes tokens reported for these requests. For very large collections or frequent querying, OpenAI recommends retrieval with File Search; this lightweight local app intentionally uses direct PDF input and does not create vector stores.
122
123Removing a file chip or clearing local chat history detaches the file locally but does **not** delete the uploaded copy from the provider. Delete retained files separately through your provider account if needed. Custom API providers must expose a compatible `/files` endpoint next to their configured `/responses` endpoint.
124
125## Storage and security
126
127The browser stores these local keys:
128
129- `study-lab:v1` — multi-map conversation workspace, attachment references, collapsed branches, and active-map selection
130- `study-lab:settings:v1` — provider API keys, model cards, the default model, endpoints, and system instructions
131- `study-lab:usage:v1` — token usage and estimated request costs recorded by this browser
132- `study-lab:openrouter-prices:v1` — a bounded cache of recently used OpenRouter model rates
133- `study-lab:theme` — light/dark theme preference
134- `study-lab:tree-zoom` — concept-map zoom preference
135
136Data saved by releases before the rename is copied into the new storage namespace automatically on first load.
137
138The usage button in the header counts tokens reported by completed Responses API requests made from this browser. OpenAI requests use a local pricing table. For OpenRouter requests, Study Lab looks up the model returned by the response through OpenRouter's single-model API and caches its prompt, completion, cache-read, and request rates for 24 hours. Each usage record retains the exact rate snapshot used for its USD estimate.
139
140These values are estimates, not invoices. OpenRouter's model endpoint reports its current model price structure, which can differ from the final routed-provider charge or charges for non-token features. The tracker also cannot see other browsers or applications, older untracked requests, credits, taxes, or special pricing. Use the linked dashboard for the selected provider as the source of truth for billing.
141
142
143
144This design is convenient for a private tool on your own laptop, but browser storage is not a secure secret vault. Any JavaScript executing on the same origin can read it. Therefore:
145
146- Run the app only from files you trust.
147- Do not publish or host this version as a public multi-user site.
148- Prefer restricted provider keys with appropriate spend limits.
149- Use **Settings → Forget key** before sharing the browser profile or local origin.
150- Using a different port creates a different browser origin and therefore a separate session.
151
152For a public deployment, put API calls behind an authenticated backend and keep the key in a server environment variable.
153
154## Performance notes
155
156Only the active branch is added to the main document; sibling branches remain compact data in `localStorage`. Streaming UI updates are batched roughly every 70 ms instead of rerendering for every token, and the concept map is built with a single document-fragment update.
157
158Very large sessions are ultimately limited by the browser's `localStorage` quota (usually a few megabytes). Start a new map when the saved tree becomes unusually large.
159
160## Browser support
161
162Use a current version of Chrome, Edge, Firefox, or Safari. LaTeX loads from the jsDelivr CDN; if it cannot load, equations remain visible as plain source text. The OpenAI API must allow requests from your local browser origin. If a corporate browser policy or custom endpoint blocks CORS, a small local proxy backend would be required.
163
164## API reference
165
166The app uses the selected provider's Responses endpoint with `stream: true` and reads Server-Sent Events. See the official [OpenAI streaming Responses documentation](https://developers.openai.com/api/docs/guides/streaming-responses) and [OpenRouter Responses documentation](https://openrouter.ai/docs/api_reference/responses/overview).