You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by gs...@apache.org on 2026/10/12 03:33:38 UTC

(tooling-llmao) 07/08: home: welcome newcomers with the steps to a first request

This is an automated email from the ASF dual-hosted git repository.

gstein pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-llmao.git

commit c33bf5006c9646976c62ddf9d9ad431e6e8f6bf1
Author: Greg Stein <gs...@gmail.com>
AuthorDate: Sun Oct 11 22:26:28 2026 -0500

    home: welcome newcomers with the steps to a first request
    
    The signed-in home page was a card saying "Signed in" with one button.
    It is now a welcome and four numbered steps: sign in, create a key,
    pick a model, connect a tool. Sign in shows as done once you have. The
    abbreviation "PAT" is gone; the help pages never used it.
    
    The model card's button said "Request a key" but created one directly,
    while My Keys uses that phrase for the approval flow. It now says
    "Create a key".
    
    The create-key form preselects your project when you have only one.
    
    Co-authored-by: Claude Opus 5.5 <no...@anthropic.com>
---
 pages.py                   |  3 ++
 static/css/llmao.css       | 39 ++++++++++++++++++++++++++
 templates/home.ezt         | 69 ++++++++++++++++++++++++++++------------------
 templates/key_create.ezt   |  2 +-
 templates/models.ezt       |  4 +--
 tests/test_help.py         | 53 +++++++++++++++++++++++++++++++++++
 tests/test_pages_models.py |  5 ++--
 7 files changed, 143 insertions(+), 32 deletions(-)

diff --git a/pages.py b/pages.py
index 8699bda..2124eed 100644
--- a/pages.py
+++ b/pages.py
@@ -1555,6 +1555,9 @@ async def keys_new_form(result):
     model = (quart.request.args.get("model") or "").strip()
     result.for_model = model
     result.has_for_model = ezt.boolean(bool(model))
+    # Nothing to choose between, so do not make them choose.
+    if not result.form_project and len(result.projects) == 1:
+        result.form_project = result.projects[0].name
     return result
 
 
diff --git a/static/css/llmao.css b/static/css/llmao.css
index fab1fb6..3703ea2 100644
--- a/static/css/llmao.css
+++ b/static/css/llmao.css
@@ -6,6 +6,45 @@ body {
   max-width: 40rem;
 }
 
+/* Home: the steps, numbered down a rail. */
+.home-page .home-steps {
+  counter-reset: step;
+  max-width: 40rem;
+}
+.home-page .home-step {
+  counter-increment: step;
+  position: relative;
+  padding: 0 0 1.75rem 3rem;
+}
+.home-page .home-step::before {
+  content: counter(step);
+  position: absolute;
+  left: 0;
+  top: -0.2rem;
+  width: 2rem;
+  height: 2rem;
+  line-height: 2rem;
+  border-radius: 50%;
+  text-align: center;
+  font-weight: 600;
+  background: var(--bs-secondary-bg);
+  color: var(--bs-body-color);
+}
+/* The rail joining one number to the next. */
+.home-page .home-step:not(:last-child)::after {
+  content: "";
+  position: absolute;
+  left: 1rem;
+  top: 2rem;
+  bottom: 0.2rem;
+  border-left: 2px solid var(--bs-border-color);
+}
+.home-page .home-step-done::before {
+  content: "\2713";
+  background: var(--bs-success);
+  color: #fff;
+}
+
 /* Dark grey bar (not pure black) so the logo disc edge stays visible. */
 .navbar.llmao-navbar {
   background-color: #343a40;
diff --git a/templates/home.ezt b/templates/home.ezt
index 38bf947..c3c551e 100644
--- a/templates/home.ezt
+++ b/templates/home.ezt
@@ -1,38 +1,53 @@
 [include "header.ezt"]
-<div class="container py-4">
+<div class="container py-4 home-page">
     <div class="mt-2">
         [include "flashes.ezt"]
     </div>
 
-    <h1 class="h3 mb-3">Apache LLMAO</h1>
+    <h1 class="h3 mb-3">Welcome to Apache LLMAO</h1>
     <p class="llmao-lede text-muted">
-        The ASF's gateway to AI models. Sign in with your ASF account,
-        create a personal access token (PAT), and use Foundation-sanctioned
-        models from any OpenAI&nbsp;API-compatible tool, with your
-        usage attributed to you.
-    </p>
-    <p class="text-muted">
-        Point your tool at <span class="font-monospace">https://llm.apache.org</span>
-        and use your PAT as the key. The endpoint follows the OpenAI API specification.
+        The ASF's gateway to AI models. Use Foundation-sanctioned models from
+        any OpenAI&nbsp;API-compatible tool, with your usage attributed to you.
+        Four steps get you there.
     </p>
 
-    [if-any uid]
-    <div class="card mb-3" style="max-width: 28rem;">
-        <div class="card-body">
-            <h2 class="h6 card-title">Signed in</h2>
-            <p class="card-text mb-0">
-                <span class="font-monospace">[uid]</span>
-                [if-any projects_label]
-                <br><span class="text-muted small">projects: [projects_label]</span>
-                [end]
+    <ol class="home-steps list-unstyled mt-4">
+        <li class="home-step[if-any uid] home-step-done[end]">
+            <h2 class="h6 mb-1">Sign in</h2>
+            [if-any uid]
+            <p class="text-muted mb-0">
+                Signed in as <span class="font-monospace">[uid]</span>[if-any projects_label]
+                &middot; projects: [projects_label][end]
             </p>
-            <a class="btn btn-primary btn-sm mt-3" href="/keys">Manage my keys</a>
-        </div>
-    </div>
-    [else]
-    <p>
-        <a class="btn btn-primary" href="/auth?login=/">Sign in with ASF</a>
-    </p>
-    [end]
+            [else]
+            <p class="text-muted mb-2">Use your ASF account.</p>
+            <a class="btn btn-primary btn-sm" href="/auth?login=/">Sign in with ASF</a>
+            [end]
+        </li>
+        <li class="home-step">
+            <h2 class="h6 mb-1">Create a key</h2>
+            <p class="text-muted mb-2">
+                A personal access token ties your usage to you and one of your
+                projects. The secret is shown once.
+            </p>
+            [if-any uid]<a class="btn btn-primary btn-sm" href="/keys">My Keys</a>[end]
+        </li>
+        <li class="home-step">
+            <h2 class="h6 mb-1">Pick a model</h2>
+            <p class="text-muted mb-2">
+                Each model lists its ID, its context window, and who can see
+                your prompts.
+            </p>
+            [if-any uid]<a class="btn btn-outline-primary btn-sm" href="/models">Models</a>[end]
+        </li>
+        <li class="home-step">
+            <h2 class="h6 mb-1">Connect a tool</h2>
+            <p class="text-muted mb-2">
+                Point it at <span class="font-monospace">https://llm.apache.org</span>
+                and use that token as the key. The endpoint follows the OpenAI API.
+            </p>
+            <a class="btn btn-outline-primary btn-sm" href="/help">Help</a>
+        </li>
+    </ol>
 </div>
 [include "footer.ezt"]
diff --git a/templates/key_create.ezt b/templates/key_create.ezt
index 1ec89c8..cefdc90 100644
--- a/templates/key_create.ezt
+++ b/templates/key_create.ezt
@@ -22,7 +22,7 @@
       <select class="form-select" name="project" id="project" required>
         <option value="">Select project…</option>
         [for projects]
-        <option value="[projects.name]">[projects.name]</option>
+        <option value="[projects.name]"[is projects.name form_project] selected[end]>[projects.name]</option>
         [end]
       </select>
     </div>
diff --git a/templates/models.ezt b/templates/models.ezt
index 225db99..2aa609d 100644
--- a/templates/models.ezt
+++ b/templates/models.ezt
@@ -106,10 +106,10 @@
             <span class="badge text-bg-success">[models.rollup_label]</span>
             [end]
             <a class="btn btn-outline-primary btn-sm"
-               href="/keys/new?model=[models.model_name]">Request a key</a>
+               href="/keys/new?model=[models.model_name]">Create a key</a>
             [else]
             <span class="badge text-bg-secondary">Unavailable</span>
-            <button type="button" class="btn btn-outline-secondary btn-sm" disabled>Request a key</button>
+            <button type="button" class="btn btn-outline-secondary btn-sm" disabled>Create a key</button>
             [end]
             <button type="button" class="btn btn-link btn-sm p-0 ms-auto"
                     data-bs-toggle="modal" data-bs-target="#modelDetailModal"
diff --git a/tests/test_help.py b/tests/test_help.py
index 25a9351..66aa49d 100644
--- a/tests/test_help.py
+++ b/tests/test_help.py
@@ -137,3 +137,56 @@ def test_home_page_does_not_list_api_paths():
     assert "/healthz" not in text
     assert "LiteLLM" not in text
     assert "llm.apache.org" in text
+    assert "PAT" not in text
+
+
+def _frame(**extra):
+    data = dict(
+        title="t",
+        flashes=[],
+        uid=None,
+        name=None,
+        projects=[],
+        projects_label=None,
+        is_site_admin=ezt.boolean(False),
+        nav="",
+        login_back="/",
+        restarted_at="",
+        repo="https://example.test/llmao",
+        commit="abc",
+        can_create_automation=ezt.boolean(False),
+    )
+    data.update(extra)
+    return edict(data)
+
+
+def _generate(name: str, data) -> str:
+    template = ezt.Template(str(THIS_DIR / "templates" / name), base_format=ezt.FORMAT_HTML)
+    buf = io.StringIO()
+    template.generate(buf, data)
+    return buf.getvalue()
+
+
+def test_home_page_is_the_steps_to_a_first_request():
+    signed_out = _generate("home.ezt", _frame())
+    assert signed_out.count('<li class="home-step') == 4
+    assert "home-step-done" not in signed_out
+    assert 'href="/auth?login=/"' in signed_out
+    assert 'href="/help"' in signed_out
+
+    signed_in = _generate("home.ezt", _frame(uid="gstein", name="Greg", projects_label="infra"))
+    assert signed_in.count("home-step-done") == 1
+    assert "Signed in as" in signed_in
+    for href in ("/keys", "/models", "/help"):
+        assert f'btn-sm" href="{href}"' in signed_in
+
+
+def test_create_key_form_preselects_a_given_project():
+    common = dict(uid="gstein", name="Greg", form_purpose="", for_model="", has_for_model=ezt.boolean(False))
+    one = _generate("key_create.ezt", _frame(projects=[edict(name="infra")], form_project="infra", **common))
+    assert '<option value="infra" selected>infra</option>' in one
+    two = _generate(
+        "key_create.ezt",
+        _frame(projects=[edict(name="infra"), edict(name="tooling")], form_project="", **common),
+    )
+    assert " selected>" not in two
diff --git a/tests/test_pages_models.py b/tests/test_pages_models.py
index f5b07e4..0b4a240 100644
--- a/tests/test_pages_models.py
+++ b/tests/test_pages_models.py
@@ -230,7 +230,8 @@ def test_models_template_renders_the_catalog_row():
     assert "<strong>Private.</strong>" in out
     assert "4 long requests at once" in out
     assert 'title="Accepts images"' in out
-    assert "Request a key" in out
+    assert "Create a key" in out
+    assert "Request a key" not in out
 
 
 def test_admin_rows_carry_their_models_deployments_and_summary():
@@ -565,7 +566,7 @@ def test_mixed_card_and_unavailable_card():
     assert "you can't choose which" in out
     assert "model-privacy-mixed" in out
     # The unavailable card keeps its button, disabled, and says why once.
-    assert "disabled>Request a key</button>" in out
+    assert "disabled>Create a key</button>" in out
     assert out.count(">Unavailable<") == 1
     assert "Unavailable right now" not in out
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org