Move the course advisor into a floating panel and link courses in its prose
The advisor was a card inside the home page, so reaching it meant navigating
back to #/ and it was rebuilt on every render of that view. It is now a bubble
in the bottom-right corner of every screen bar the player and a quiz, opening
a chat panel that stays put as you move around — ask what to take next, follow
a recommendation, keep asking. It is built once into #overlay-root, outside
the view that gets replaced, and syncAdvisor() called from renderChrome() is
the single path that decides whether it is on screen; that covers every route
change and every loadMeta(), and makes the Settings toggle work with no change
to its handler.
Being permanent removes the reason for the routeSeq guards in ask(). An answer
that arrived after the user navigated was written to sessionStorage and then
dropped unrendered; now it lands, and the bubble carries an unread dot if the
panel is closed. showAuth() wipes the transcript and hides the dock, both of
which are new requirements: the dock is a sibling of #app-shell, so hiding the
shell no longer hides the advisor, and the widget now outlives a sign-out.
Courses named in the answer itself are links. advisorResolveCourses() is
lifted out of advisorValidateRecommendations() so an id written inline goes
through the same existence check and the same redirect to whichever duplicate
copy the learner has progress on — a sentence and the card under it can never
point at different copies. The map is keyed by the id as the model wrote it,
since that is what the browser has to find in the text. The client splits the
raw line before escaping rather than linkifying escaped output: esc() writes
an apostrophe as ', and the course-id pattern matches the #39 inside it.
Under 900px the panel is a full-height sheet, and the mobile toast strip lifts
clear of the bubble only while the bubble is actually on screen. The z-index
sits on the dock rather than #overlay-root, which would otherwise become a
stacking context and trap the confirm dialog behind the panel.
README's advisor section is brought back in line, including the parts left
stale by 05f0abc about sections and the whole library going out per question.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPZRrwVj6L9ckoRtK1yHAN
This commit is contained in:
@@ -41,9 +41,10 @@ plain HTTP resources.
|
||||
- **Course details** (difficulty, tags, a blurb, the learning paths a course
|
||||
belongs to) from the vendor's catalogue or the downloader's sidecar file —
|
||||
see *Course catalogue* below.
|
||||
- An optional **AI course advisor** on the home page: "I want to learn SD-WAN,
|
||||
where do I start?" answered with courses from your own library, in order,
|
||||
with links. Off unless you switch it on — see *Course advisor* below.
|
||||
- An optional **AI course advisor**, a bubble in the corner of every screen:
|
||||
"I want to learn SD-WAN, where do I start?" answered with courses from your
|
||||
own library, in order, with links. Off unless you switch it on — see
|
||||
*Course advisor* below.
|
||||
- **Reset progress** for one video, one quiz, a whole course/section, or the
|
||||
entire account.
|
||||
- Course and section listings switch between **grid and list** layout, and the
|
||||
@@ -267,13 +268,18 @@ lesson) is ignored.
|
||||
|
||||
## Course advisor
|
||||
|
||||
An optional box on the home page that answers questions like *"I want to
|
||||
learn SD-WAN, where do I start?"* or *"what should I know before Core MPLS?"*
|
||||
with courses from your own library, in a suggested order, each linking to its
|
||||
course page with your progress on it. When something you need is not on the
|
||||
A bubble in the bottom-right corner of every screen — bar the player and a
|
||||
quiz, where it would be in the way — opening a chat panel that answers
|
||||
questions like *"I want to learn SD-WAN, where do I start?"* or *"what should
|
||||
I know before Core MPLS?"* with courses from your own library, in a suggested
|
||||
order, each linking to its course page with your progress on it. Courses named
|
||||
in the answer itself are links too. When something you need is not on the
|
||||
server but is in the seeded catalogue, it says so and links to the vendor's
|
||||
page; topics covered nowhere are listed too.
|
||||
|
||||
The panel stays open as you move around, so you can follow a recommendation
|
||||
and keep asking. The conversation lives in the browser tab and ends with it.
|
||||
|
||||
It is **off by default** and the app never depends on it. To turn it on, set
|
||||
in `config.php`:
|
||||
|
||||
@@ -285,22 +291,26 @@ define('AI_MODEL', 'deepseek-v4-flash');
|
||||
define('AI_DAILY_LIMIT', 50); // questions per user per day
|
||||
```
|
||||
|
||||
With `AI_ENABLED` false (or no key) the box does not appear, `api/advisor.php`
|
||||
answers 503, and the app makes **no outbound requests at all**. The one extra
|
||||
requirement when it is on is PHP's curl extension. Each user can also hide the
|
||||
box for their browser from Settings.
|
||||
With `AI_ENABLED` false (or no key) the bubble does not appear,
|
||||
`api/advisor.php` answers 503, and the app makes **no outbound requests at
|
||||
all**. The one extra requirement when it is on is PHP's curl extension. Each
|
||||
user can also hide the button for their browser from Settings.
|
||||
|
||||
What leaves the server when it is on: the names of your courses and sections
|
||||
with their sizes, the catalogue details (difficulty, tags, blurb, learning
|
||||
paths), the catalogue titles of courses you have access to but have not
|
||||
downloaded, your progress percentage per course, and the conversation you
|
||||
type. Never video files, subtitles or quiz contents.
|
||||
What leaves the server when it is on: the name, category, difficulty, vendor
|
||||
and size of every course, a short blurb, tags and learning paths for the
|
||||
handful judged relevant to the question, the catalogue titles of courses you
|
||||
have access to but have not downloaded, your progress percentage per course,
|
||||
and the conversation you type. Never video files, section or lesson names,
|
||||
subtitles, or quiz contents.
|
||||
|
||||
How it works: the whole library is rendered as one stable block of text and
|
||||
sent with every question, and the provider caches that prefix, so a question
|
||||
costs a fraction of a cent. The model replies in JSON; the server drops any
|
||||
course id it made up before the browser sees it, and links are built only from
|
||||
ids that exist. Each question (including one that fails) counts toward the
|
||||
How it works: every course goes out as a one-line index, which is the same
|
||||
bytes for every user and every question, so the provider's prefix cache covers
|
||||
it and a question costs a fraction of a cent; only the courses that match the
|
||||
question are described in any depth. The model replies in JSON; the server
|
||||
drops any course id it made up before the browser sees it, and links are built
|
||||
only from ids that exist. That applies to the prose as well — an id the model
|
||||
writes inline as `#412` is resolved against the same check and becomes a link
|
||||
to that course, and anything that does not resolve is left as plain text. Each question (including one that fails) counts toward the
|
||||
per-user daily limit, kept in `advisor_requests` together with the token
|
||||
counts, so you can see what it costs. A conversation lives in the browser tab
|
||||
and is resent with each turn, so follow-ups ("I already know MPLS") work.
|
||||
|
||||
Reference in New Issue
Block a user