This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
The following commit(s) were added to refs/heads/master by this push:
new 8841d80 feat(agent): add a getting-started checklist to the home page
8841d80 is described below
commit 8841d803823f9f9cc18b230289cf0b8ffb45a897
Author: Yang Luo <[email protected]>
AuthorDate: Thu Sep 3 22:43:21 2026 +0800
feat(agent): add a getting-started checklist to the home page
---
web/src/components/OnboardingChecklist.tsx | 144 +++++++++++++++++++++++++++++
web/src/locales/en/data.json | 8 ++
web/src/locales/zh/data.json | 8 ++
web/src/pages/HomePage.tsx | 3 +
4 files changed, 163 insertions(+)
diff --git a/web/src/components/OnboardingChecklist.tsx
b/web/src/components/OnboardingChecklist.tsx
new file mode 100644
index 0000000..ebb403c
--- /dev/null
+++ b/web/src/components/OnboardingChecklist.tsx
@@ -0,0 +1,144 @@
+// Copyright 2026 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import * as React from "react";
+import {Link} from "react-router-dom";
+import {CheckCircle2, ChevronRight, Circle, Sparkles, X} from "lucide-react";
+import i18next from "i18next";
+
+import {Button} from "@/components/ui/button";
+import {Card, CardContent} from "@/components/ui/card";
+import {Progress} from "@/components/ui/progress";
+import {cn} from "@/lib/utils";
+import type {Agent, LlmAgentStat, Provider} from "@/types";
+
+const dismissedStorageKey = "onboardingChecklistDismissed";
+
+function readDismissed(): boolean {
+ try {
+ return localStorage.getItem(dismissedStorageKey) === "true";
+ } catch {
+ return false;
+ }
+}
+
+function persistDismissed() {
+ try {
+ localStorage.setItem(dismissedStorageKey, "true");
+ } catch {
+ // Private-mode storage failures must not take the page down.
+ }
+}
+
+interface Step {
+ done: boolean;
+ title: string;
+ description: string;
+ to: string;
+}
+
+/**
+ * The three-step path from an empty machine to seeing traffic flow, taken
+ * from the README's "Send an agent's traffic through Gateway" section. It
+ * disappears once every step is done, and stays gone across reloads once
+ * dismissed by hand.
+ */
+export function OnboardingChecklist({
+ providers,
+ agents,
+ stats,
+}: {
+ providers: Provider[];
+ agents: Agent[];
+ stats: LlmAgentStat[];
+}) {
+ const [dismissed, setDismissed] = React.useState(readDismissed);
+
+ const steps: Step[] = [
+ {
+ done: providers.length > 0,
+ title: i18next.t("agent:Onboarding add provider"),
+ description: i18next.t("agent:Onboarding add provider detail"),
+ to: "/providers",
+ },
+ {
+ done: agents.some(agent => agent.provider !== ""),
+ title: i18next.t("agent:Onboarding connect agent"),
+ description: i18next.t("agent:Onboarding connect agent detail"),
+ to: "/",
+ },
+ {
+ done: stats.some(stat => stat.requests > 0),
+ title: i18next.t("agent:Onboarding see it work"),
+ description: i18next.t("agent:Onboarding see it work detail"),
+ to: "/llm-records",
+ },
+ ];
+ const doneCount = steps.filter(step => step.done).length;
+
+ if (dismissed || doneCount === steps.length) {
+ return null;
+ }
+
+ const dismiss = () => {
+ persistDismissed();
+ setDismissed(true);
+ };
+
+ return (
+ <Card className="gap-0 py-0 shadow-xs">
+ <CardContent className="flex flex-col gap-3 p-4">
+ <div className="flex items-center gap-3">
+ <Sparkles className="text-primary size-4 shrink-0" />
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
+ <div className="flex items-center justify-between gap-2">
+ <span className="text-sm font-medium">{i18next.t("agent:Getting
started")}</span>
+ <span className="text-muted-foreground text-xs">
+ {doneCount}/{steps.length}
+ </span>
+ </div>
+ <Progress value={(doneCount / steps.length) * 100} tone="success"
className="h-1.5" />
+ </div>
+ <Button variant="ghost" size="icon-sm" onClick={dismiss}
aria-label={i18next.t("general:Dismiss")}>
+ <X />
+ </Button>
+ </div>
+
+ <div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
+ {steps.map(step => (
+ <Link
+ key={step.title}
+ to={step.to}
+ className={cn(
+ "hover:border-foreground/20 flex items-start gap-2 rounded-lg
border px-3 py-2 transition-colors",
+ step.done && "opacity-60",
+ )}
+ >
+ {step.done ? (
+ <CheckCircle2 className="text-success mt-0.5 size-4 shrink-0"
/>
+ ) : (
+ <Circle className="text-muted-foreground mt-0.5 size-4
shrink-0" />
+ )}
+ <div className="flex min-w-0 flex-col">
+ <span className="text-xs font-medium">{step.title}</span>
+ <span className="text-muted-foreground
text-[11px]">{step.description}</span>
+ </div>
+ {!step.done ? <ChevronRight className="text-muted-foreground
ml-auto size-3.5 shrink-0" /> : null}
+ </Link>
+ ))}
+ </div>
+ </CardContent>
+ </Card>
+ );
+}
diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json
index 36e5290..b0973de 100644
--- a/web/src/locales/en/data.json
+++ b/web/src/locales/en/data.json
@@ -83,6 +83,7 @@
"Gateway only hint": "Codex speaks only the OpenAI Responses API. Gateway
serves it by translating to the chat completions this provider answers, so
Codex cannot be pointed at it directly.",
"Gemini CLI config hint": "The Gemini CLI reads no base URL from a
settings file: the endpoint is GOOGLE_GEMINI_BASE_URL in ~/.gemini/.env, with
GEMINI_API_KEY beside it and \"security.auth.selectedType\": \"gemini-api-key\"
in ~/.gemini/settings.json. A project of its own .env is read first, and that
one wins.",
"Gemini gateway only hint": "The Gemini CLI speaks only Google's
generateContent API, which no provider here serves. Gateway serves it by
translating to the API the provider answers, so the CLI cannot be pointed at a
provider directly.",
+ "Getting started": "Getting started",
"Image": "Image",
"In use": "In use",
"Install": "Install",
@@ -118,6 +119,12 @@
"Not signed in hint": "Start this instance and sign in; the account shows
up here afterwards.",
"Not supported": "Not supported yet",
"Not written": "Not written",
+ "Onboarding add provider": "Add a provider",
+ "Onboarding add provider detail": "Point Gateway at a model vendor with a
base URL and API key.",
+ "Onboarding connect agent": "Connect an agent",
+ "Onboarding connect agent detail": "Pick that provider on one of the agent
cards above.",
+ "Onboarding see it work": "See it work",
+ "Onboarding see it work detail": "Watch requests land in LLM Records once
traffic flows through.",
"Patch": "Turn on monitoring",
"Patch Status": "Monitoring",
"Patched": "Monitoring",
@@ -555,6 +562,7 @@
"Dark": "Dark",
"Delete": "Delete",
"Deleted successfully": "Deleted successfully",
+ "Dismiss": "Dismiss",
"Display name": "Display name",
"Downloading": "Downloading",
"Edit": "Edit",
diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json
index efa582c..2f2c47a 100644
--- a/web/src/locales/zh/data.json
+++ b/web/src/locales/zh/data.json
@@ -83,6 +83,7 @@
"Gateway only hint": "Codex 只会说 OpenAI Responses API。Gateway 会把它翻译成该供应商应答的
chat completions,所以 Codex 无法直连这个供应商。",
"Gemini CLI config hint": "Gemini CLI 不从配置文件读取 Base URL:端点是 ~/.gemini/.env
中的 GOOGLE_GEMINI_BASE_URL,旁边是 GEMINI_API_KEY,同时 ~/.gemini/settings.json 中写入
\"security.auth.selectedType\": \"gemini-api-key\"。项目自带的 .env 会被优先读取并覆盖它。",
"Gemini gateway only hint": "Gemini CLI 只会说 Google 的 generateContent
API,这里没有供应商提供该 API。Gateway 会把它翻译成供应商应答的 API,所以 Gemini CLI 无法直连供应商。",
+ "Getting started": "快速上手",
"Image": "图片",
"In use": "使用中",
"Install": "安装",
@@ -118,6 +119,12 @@
"Not signed in hint": "启动该实例并登录,之后账号会显示在这里。",
"Not supported": "暂不支持",
"Not written": "未写入",
+ "Onboarding add provider": "添加一个 Provider",
+ "Onboarding add provider detail": "填入模型厂商的地址和 API Key,让 Gateway 接管它。",
+ "Onboarding connect agent": "绑定一个 Agent",
+ "Onboarding connect agent detail": "在上方的 Agent 卡片中选择该 Provider。",
+ "Onboarding see it work": "查看效果",
+ "Onboarding see it work detail": "流量接入后,可在 LLM Records 中看到请求记录。",
"Patch": "开启监控",
"Patch Status": "监控状态",
"Patched": "监控中",
@@ -576,6 +583,7 @@
"Dark": "深色",
"Delete": "删除",
"Deleted successfully": "删除成功",
+ "Dismiss": "关闭",
"Display name": "显示名称",
"Downloading": "正在下载",
"Edit": "编辑",
diff --git a/web/src/pages/HomePage.tsx b/web/src/pages/HomePage.tsx
index 34ef077..cbe9b14 100644
--- a/web/src/pages/HomePage.tsx
+++ b/web/src/pages/HomePage.tsx
@@ -24,6 +24,7 @@ import * as Setting from "@/Setting";
import {AgentCatalog} from "@/components/AgentCatalog";
import {AuthenticityOverview} from "@/components/AuthenticityOverview";
import {AgentGridCard} from "@/components/AgentGridCard";
+import {OnboardingChecklist} from "@/components/OnboardingChecklist";
import {EmptyState, ErrorState} from "@/components/shared/empty-state";
import {Loading} from "@/components/shared/loading";
import {UnauthorizedResult} from "@/components/shared/misc";
@@ -217,6 +218,8 @@ export default function HomePage({account}: {account:
Account}) {
}
/>
+ {loaded ? <OnboardingChecklist providers={providers} agents={agents}
stats={stats} /> : null}
+
{/* With no agents to show, the failure is the whole page and is rendered
below instead; here it sits above the cards that are still on
screen. */}
{error !== "" && agents.length > 0 ? <MessageAlert title={error} /> :
null}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]