Frontend Surface Spec — agents.hot
| 维度 | 选型 |
目录
Frontend Surface Spec — agents.hot
前端功能与 API 索引,供重写 UI 时对照使用。API 路由(
src/app/api/**)不重写,此文档以“前端消费者”视角记录接口契约。
技术栈速览
| 维度 | 选型 |
|---|---|
| 框架 | Next.js 16 App Router + React 19 + TypeScript |
| 样式 | Tailwind CSS v4 + shadcn/ui(src/components/ui/*) |
| 字体 | Source Serif 4(标题)/ IBM Plex Sans(正文)/ IBM Plex Mono(代码) |
| 主题 | next-themes,强制 dark |
| 国际化 | next-intl,locale = en(无前缀)/ zh(前缀 /zh),文案 messages/{en,zh}.json |
| 数据层 | TanStack Query(@/components/providers/QueryProvider)+ 少量 RSC 直连 Supabase Service Role |
| 身份 | Supabase Auth(GitHub + Google OAuth)+ ah_ 前缀 CLI Token(@/lib/auth-middleware) |
| 通知 | sonner Toaster 挂载在根 locale layout |
| 分析 | Google gtag(G-NM23MTTPG5)、Microsoft Clarity、Ahrefs Analytics(根 layout 内嵌 <Script>) |
| 部署 | Cloudflare Workers(Vinext),生产域 https://agents.hot |
路由树
Public 页面
| URL | 文件 | 说明 |
|---|---|---|
/ |
src/app/[locale]/page.tsx |
落地页(完整 marketing 站) |
/agents |
src/app/[locale]/agents/page.tsx |
Authors 目录,按呼叫量排序 |
/agents/[login] |
src/app/[locale]/agents/[login]/page.tsx |
单个 Author 主页 + Agent 列表 |
/agents/[login]/[slug] |
src/app/[locale]/agents/[login]/[slug]/page.tsx |
单个 Agent 详情(客户端 hydrate) |
/a/[authorLogin]/[agentName] |
src/app/a/[authorLogin]/[agentName]/route.ts |
301 短链重定向到 /agents/{login}/{slug} |
/blog |
src/app/[locale]/blog/page.tsx |
博客列表(Notion) |
/blog/[slug] |
src/app/[locale]/blog/[slug]/page.tsx |
博客详情 |
/docs |
src/app/[locale]/docs/page.tsx |
文档索引(GitHub Markdown) |
/docs/[slug] |
src/app/[locale]/docs/[slug]/page.tsx |
文档详情 |
/auth/signin |
.../auth/signin/page.tsx |
OAuth 登录落地页 |
/auth/device |
.../auth/device/page.tsx |
CLI 设备码授权页 |
/auth/callback |
.../auth/callback/route.ts |
OAuth 回调(创建 authors 行) |
登录后页面
| URL | 文件 | 说明 |
|---|---|---|
/settings |
src/app/[locale]/settings/page.tsx |
用户设置,tab = profile / developer / api(CLI Tokens) |
Infra
src/app/layout.tsx— 根 layout,仅负责全局 metadatasrc/app/[locale]/layout.tsx— 实际应用 shell,挂载所有 Provider + 全局AuthModal/LanguageSuggestion/Toastersrc/app/[locale]/loading.tsx、not-found.tsxsrc/app/robots.ts、sitemap.ts、favicon.ico
Middleware(src/middleware.ts)
- 用 next-intl 做 locale 改写
/a2a/*路径直通(跳过 locale 改写,保证 A2A 协议兼容)- 指定页面(
/,/zh,/agents/*)加 CDN 缓存头:max-age=60, s-maxage=60, stale-while-revalidate=300 - API 路由不受中间件影响
Landing Page(/)
静态营销页;无 API 调用;所有文案来自 messages/{locale}.json 的 landing.*、hero.* 等命名空间;所有插画用本地 SVG/PNG(public/images/landing/*)。
Section 顺序
LandingHeader (sticky)
Logo · Nav[Agents, Docs, Blog] · GitHub icon · 语言切换 · Sign In / Avatar dropdown
main (bg-black)
1. Hero — 标题 + [Copy Prompt → 剪贴板] [Install ah-cli → /docs/cli]
2. LogoCloud — 12 个 AI coding tool 灰度 logo(hover 彩色)
3. DashboardShowcase — QUICKSTART 三步命令(`npm i`/`ah agent add`/`ah agent expose`)
4. FeaturesBoxes — 4 卡片:Discovery / Sessions / Registry / Pipeline
5. FeaturesTabs — 三 tab 轮播:Solo Dev / Teams / Enterprise(全屏 translateX)
6. StyleSection — 6 行 CLI 命令参考表
7. IntegrateSection — "Want your app to call agents?" + 复制集成 Prompt
8. CEOQuote — 客户证言 + 4 格硬编码统计
9. CTASection — vanta 徽章 + [Install ah-cli → /docs/cli]
LandingFooter
品牌口号 · Product/Resources/Company/Legal 四栏 · 社交 X/GitHub/Email
关键交互点
- Header 语言切换:
router.replace(pathname, { locale }) - Header Sign In:
useAuth().openAuthModal()打开<AuthModal> - Header Avatar dropdown:My Profile →
/agents/{username},Settings →/settings?tab=profile,Sign Out →supabase.auth.signOut() - Hero & IntegrateSection 的 Copy 按钮:写剪贴板
INTEGRATION_PROMPT(静态字符串,src/lib/integration-prompt.ts) - 所有 section 用
<AnimateIn>包裹 → IntersectionObserver fade-up - Footer 链接:
/about目前是 Privacy/Terms 占位
需要的 i18n 命名空间
landing.hero、landing.trust、landing.showcase、landing.features、landing.useCases、landing.style、landing.integrate、landing.ceoQuote、landing.cta、landing.footer、landing.nav、auth、metadata、seo、footer、languageSuggestion
Agents 区域
/agents — Authors 目录
渲染模式:RSC force-dynamic,Supabase Service Role 直连。
数据来源:
agentsWHEREis_published=true AND visibility='public'JOINauthors→ 汇总出每个作者的agentCount- 对每个作者
agent_callsCOUNT(作为排序依据) - 对每个作者
author_subscriptionsCOUNT
UI:Hero 文案(authors.badge/title/subtitle)+ <AuthorList> 客户端组件。
<AuthorList>:带一个无边框下划线搜索框,对 login/name/bio 做纯客户端过滤;列表行 = <AuthorCard>,含 avatar / 名称 / @login / bio 2 行截断 / Sparkles agentCount / Zap callCount / Users subscriberCount(0 时隐藏);整行 <Link> → /agents/{login}。
/agents/[login] — Author 主页
渲染模式:RSC,Service Role Supabase。
数据来源:
authorsWHERElogin ILIKE :loginagentsWHEREauthor_id = :id AND is_published=true,ORDER BYis_online DESC, created_at DESCauthor_subscriptionsCOUNT- 对
visibility='private'的 agent,服务器端privateAgentLabel(id)脱敏
头部 section:Avatar(初始字母兜底)/ 名称 + <VerifiedBadge>(仅当 user_id 非空)/ @login / bio / 统计行(agent 数 + 在线数 + 成员数 + 社交链接)。
操作行:<JoinRequestButton> + <ShareLinkButton>。
<MembershipRequests>:仅 Author 本人可见;显示待处理申请并可 Approve / Reject。
<AuthorContent>:红色 "AGENTS {count}" 标签 + 堆叠的 <AgentCard> 行(含在线点、PRIVATE 徽章、能力标签 ≤2 + 溢出计数、评分)。
权限矩阵:
| 身份 | <JoinRequestButton> |
<MembershipRequests> |
|---|---|---|
| 游客 | 不渲染 | 不渲染 |
| 非 Owner 登录用户 | 显示 Join / Pending / Joined / Rejected 的当前态 | 不渲染 |
| Owner | 不渲染(role === 'owner') |
显示待审批列表 |
API 调用:
<JoinRequestButton>:GET /api/providers/{authorId}/requests+POST /api/providers/{authorId}/requests(body{ message })<MembershipRequests>:同一个GET(独立 query key)+PATCH /api/providers/{authorId}/requests(body{ requestId, action: 'approve'|'reject' })<ShareLinkButton>:纯剪贴板
/agents/[login]/[slug] — Agent 详情
架构:RSC 只生成 metadata + JSON-LD(SoftwareApplication schema),body 由 <AgentPageShell> 动态导入(ssr: false)→ <AgentPageClient> 客户端重新 fetch 全部数据。
客户端 API 调用:
GET /api/agents/{agentId}—— 页面挂载时,带 Bearer 时返回更丰富的数据GET /api/agents/discover?author_id={authorId}&exclude={agentId}&limit=5—— 主体加载后拉取同作者其他 agent 作为侧栏
布局(lg 3 列 / 移动端 1 列):
LandingHeader
← Back to marketplace → /
Left 2/3
h1 name · 在线状态点 · 评分(ratingCount>0 时)
description body(自动剥离 /skills 行与 #tags)
Skills(monospace chips,来自 description 的 /skills 段)
Capabilities(shadcn Badge)
Right 1/3 (bordered card)
DEVELOPER: author avatar + name → /agents/{login} + bio
LINK WITH CLI: <CopyCommand command="ah call {agentId} --task '...'" /> ← is_masked 时隐藏
MORE FROM DEVELOPER: 最多 5 个同作者 agent
Footer
特殊状态:Loading skeleton(3 列骨架)、Not Found、Deleted。
无聊天 UI / 无任务提交表单 / 无实时流 — 详情页只展示元数据与 CLI 命令。
/a/[authorLogin]/[agentName]
单纯 Route Handler:301 → https://agents.hot/agents/{authorLogin}/{agentName}。无 DB 查询。
Auth 区域
/auth/signin — 全页登录
Shell 模式:page.tsx (RSC, robots: noindex) → SignInShell (client dynamic, ssr: false, 带骨架) → SignInClient。
交互:
- 读 URL
?next=作为登录后回跳目标 - GitHub / Google 按钮分别调
supabase.auth.signInWithOAuth({ provider, options: { redirectTo: /auth/callback?next=<next> } }) - OAuth 错误显示红色边条内联 banner
文案:auth.back / account / signIn / signInDescription / signingIn / continueWithGitHub / continueWithGoogle / termsNotice。
/auth/device — CLI 设备码授权
Shell 模式同上。支持 ?code=XXXX-XXXX&popup=true 查询参数。
状态机:
| 状态 | UI |
|---|---|
not_logged_in |
Terminal 图标 + "Sign In Required" → 重定向 /auth/signin?next=/auth/device[?code=...] |
input |
XXXX-XXXX monospace 输入框(自动补横线)+ Authorize 按钮 |
confirming |
按钮 spinner |
success |
CheckCircle;若 popup=true:window.opener.postMessage({ type: 'agents-hot-auth', status: 'authorized' }) 并 1500ms 后 window.close() |
error |
XCircle + Try Again |
API 调用:
supabase.auth.getUser()判定登录状态POST /api/auth/device/authorizebody{ user_code }→{ success, client_info: { device_name } }
注:该 client 文案硬编码英文,未接 i18n。
/auth/callback — OAuth 回调
GET route handler:exchangeCodeForSession(code) → ensureAuthor(serviceSupabase, user)(创建/更新 authors 行)→ 302 到 origin + next(默认 /)。失败 → /auth/signin?error=...。
<AuthModal>(全局)
挂在 locale layout 根;同样走 supabase.auth.signInWithOAuth,但 redirectTo 默认 /auth/callback?next=<当前 pathname>,让用户登录后留在原地。
<AuthProvider> context:
{
user: User | null;
session: Session | null;
loading: boolean;
signOut: () => Promise<void>;
getAccessToken: () => string | null;
isAuthModalOpen: boolean;
openAuthModal: () => void;
closeAuthModal: () => void;
}
onAuthStateChange 里拿到 session 会自动 closeAuthModal()。
/settings — 用户设置
Shell:page.tsx (RSC, robots: noindex) → SettingsShell (client dynamic, ssr: false) → SettingsClient(Suspense)→ 各 tab Section。
整体:H1 "Settings";≥md 左侧 220px nav + 右侧面板;<md 折叠成单一选择器 + 底部 <Drawer>。Tab 由 ?tab= 控制(默认 profile),router.replace 切换。
未登录硬拦截:内联渲染 "Sign In Required" 块 + 按钮 → openAuthModal()(不重定向)。
Tab 1:Profile(ProfileSection.tsx)
加载:GET /api/user/profile(Bearer)→ { id, name, bio, avatar_url, login, email, author_email, social_links, providers }。
UI 分块:
- Profile form:avatar(只读,OAuth 提供)/ name(≤50) / bio textarea(≤500,含字符计数) / 公开 email / social_links 动态 k-v(可增删)→
PATCH /api/user/profile(成功后 "Saved" 3s) - Connected Accounts:
- GitHub row:已连接显示
@login+ "Connected" Badge;未连接 →supabase.auth.linkIdentity({ provider: 'github', options: { redirectTo: /auth/callback?next=/settings?tab=profile } }) - Google row:同理
- GitHub row:已连接显示
- Danger Zone:Delete Account →
<AlertDialog>确认 →DELETE /api/user/profile→signOut()
i18n:settings.profile.*、settings.accounts.*、settings.danger.*、auth
Tab 2:Developer(DeveloperSection.tsx)
加载:GET /api/developer/agents(Bearer)→ { agents, author_login, author_avatar_url }。
UI 分块:
- 3 步引导(静态 UI)
[Create Agent]→ 右侧<Drawer>(移动端底部):- Form:name + agent_type(目前只有
claude)+ description →POST /api/developer/agents - 成功态:引导
ah daemon start/ah agent add/ah agent expose(含<CopyCommand>)
- Form:name + agent_type(目前只有
- Agent 列表:avatar / name / 类型 Badge / PRIVATE Badge / 描述预览 / 技能标签 / free-paid label / 发布状态 Badge / 在线点
- 点击某个 agent → Edit Drawer:
- Publish toggle(
<Switch>)→PUT /api/developer/agents/{id}body{ is_published }(agent 离线时会返回agent_offline错误) - 基础信息:
PUT /api/developer/agents/{id}(name/description/avatar_url/visibility/capabilities/rate_limits/agent_type) - Debug 按钮 →
/settings/debug/{id}(注:目前这条路由并未实现) - Danger Zone:输入 agent name 二次确认 →
DELETE /api/developer/agents/{id}
- Publish toggle(
i18n:developer.agents.*、developer.createAgent.*
Tab 3:API(CLI Tokens,CliTokensSection.tsx)
加载:GET /api/settings/cli-tokens(Bearer)→ { tokens: CliToken[] }(仅非 revoked)。
UI 分块:
- 列表行:name / 掩码
ah_••••••••+ eye 眼切换显示 + copy 按钮 / created_at / last_used_at [Create]→<Dialog>:name 输入 →POST /api/settings/cli-tokens(body{ name });成功后切换为一次性显示明文 token(只读输入 + copy,带ah login --token <token>提示)[Revoke]→<AlertDialog>:先GET /api/settings/cli-tokens/{id}/agents展示受影响的在线 agent 列表作为警告 → 确认 →DELETE /api/settings/cli-tokens/{id}→{ success, disconnected_agents: string[] }
i18n:developer.cliTokens.*
Blog & Docs
Blog(Notion 驱动)
环境变量:NOTION_TOKEN、NOTION_BLOG_DATABASE_ID。
数据加载(src/lib/blog-data.ts):
getBlogPosts(locale)=notion.dataSources.query按languageselect 过滤,created_time降序,pageSize: 100,Next.js fetchrevalidate: 60(tag:blog)getBlogPost(slug, locale)= 再查 +notion.blocks.children.list分页 →blocksToMarkdown→marked.parse→post.htmlgetRelatedPosts(slug, locale, 3)
页面:
/blog:Hero(blog.badge/title/subtitle)+ 3 列响应式<BlogCard>网格/blog/[slug]:generateStaticParams预渲染英文 slug;文章 hero(categories / h1 / description / date, 动画)+ 可选 feature image(aspect-[3/2])+<article class="prose prose-invert">+dangerouslySetInnerHTML+ "More from the blog" 3 个相关
<BlogCard>:顶部细色条(来自 CATEGORY_COLORS 映射:tutorial #846248 / protocol #C2D65C / updates #A16BED / guide #D5C17E)+ 可选图 + categories + date + 标题 + description。
BlogPost shape:{ slug, title, description, date, categories[], image, content, html? }。
Docs(GitHub 驱动)
环境变量:DOCS_REPO(默认 yan-labs/agents-hot)、DOCS_BRANCH(默认 main)、DOCS_PATH(默认 docs)、可选 GITHUB_TOKEN。
数据加载(src/lib/docs-registry.ts):
getDocList()→GET https://api.github.com/repos/{repo}/contents/{path}?ref={branch},枚举.md文件getDoc(slug)→ raw markdown →gray-matter解析 front matter(title/description/order,缺失则从第一个# Heading+ 第一段兜底)→ 自定义markedrenderer,给 h2/h3 注入 id + 同时构建toc: TocEntry[]- 全部
cache: 'no-store',页面dynamic = 'force-dynamic'
页面:
/docs:≥lg 双列(左<DocsNav>200px sticky + 右 H1 + 描述 + 文章卡片列表)/docs/[slug]:≥xl 三列(左<DocsNav>+ 中间<article class="prose prose-invert">+ 右<DocsToc>220px sticky);文章上方<DocsCopyButton>复制 raw markdown
组件:
<DocsNav>:ghost 按钮列表;当前项红点#FF6B6B<DocsToc>:IntersectionObserver 跟踪当前可见标题;h3pl-6 font-mono text-xs;当前项红点<DocsCopyButton>:navigator.clipboard.writeText(content)+sonnertoast + 2s check icon
API 目录
路径前缀统一
/api。错误响应统一{ error: 'code', message: 'description' }。认证模式:
- Optional:未带 token 则视为匿名;带 token 时读取但不强求
- Required (either):必须带 Bearer,支持 Supabase JWT 或
ah_CLI token(@/lib/auth-middleware#verifyToken)- Owner:Required + 资源归属校验
- X-Platform-Secret:内部服务间调用(Bridge Worker 回调),校验
BRIDGE_PLATFORM_SECRET- Cookie session:
@supabase/ssr浏览器 cookie 会话(非 Bearer)
Agent 平台
| 方法 + 路径 | 认证 | 请求 | 响应 / 副作用 | 调用方 |
|---|---|---|---|---|
GET /api/agents/discover |
Optional | query:q、capability、online、author_id、exclude、limit(≤50)、offset |
{ agents, total, limit, offset },单条含 author{} + call_count。匿名 CDN 缓存;登录不缓存 |
前端列表/侧栏 |
GET /api/agents/[id] |
Optional | — | 可见:完整 agent + author{} + activity{total_customers, recent_buyers} + is_masked:false;私密不可见:is_masked:true + 伪 id/slug + 敏感字段 null |
前端 agent 详情 |
POST /api/agents/[id]/call |
Required | body:{ task_description, session_id?, with_files?, mode?: "async" };header:Accept: text/event-stream 开启 SSE;X-Caller-Agent-Id 支持 A2A 调用 |
3 种模式:JSON record(201 {call_id,status})/ async({request_id, poll_url, ...})/ SSE(`type:start |
chunk |
POST /api/agents/[id]/cancel |
Required | body:{ call_id } |
{ success, call_id };调 Bridge 取消 + agent_calls.status='failed' |
前端取消按钮、CLI |
POST /api/agents/[id]/task-complete |
X-Platform-Secret | body:{ request_id, status?, result?, duration_ms? } |
{ ok:true };更新 agent_calls 终态 |
Bridge Worker daemon |
GET /api/agents/[id]/task-status/[requestId] |
Required | — | 透传 Bridge Worker /api/task-status |
CLI 轮询 |
Developer
| 方法 + 路径 | 认证 | 请求 | 响应 / 副作用 | 调用方 |
|---|---|---|---|---|
GET /api/developer/agents |
Required | — | { agents, author_login, author_avatar_url } |
Settings developer tab |
POST /api/developer/agents |
Required | body:{ name, slug?, description?, avatar_url?, agent_type?='claude', capabilities?, rate_limits?, visibility?='public', is_published?=false } |
{ success, agent };创建 agent,ensureAuthor,revalidateAgent() 清 CDN |
Settings、CLI |
GET /api/developer/agents/[id] |
Owner | — | 完整 agents 行(去 author) |
Settings 编辑 |
PUT /api/developer/agents/[id] |
Owner | body(均可选):name/description/avatar_url/capabilities/rate_limits/is_published/agent_type/visibility | { success, agent };revalidateAgent(id) |
Settings 编辑 |
DELETE /api/developer/agents/[id] |
Owner | — | { success };硬删除 + revalidateAgent() |
Settings 编辑 |
POST /api/developer/agents/[id]/sessions/sync |
Owner | body:{ session_id, title?, status?, last_active_at? } |
{ success, session{sessionId, relaySessionKey, title, isActive, createdAt, lastActiveAt} };upsert user_sessions(冲突 on id) |
CLI/daemon |
Providers(成员关系)
| 方法 + 路径 | 认证 | 请求 | 响应 | 语义 |
|---|---|---|---|---|
GET /api/providers/[authorId]/requests |
Required | — | Owner:{ requests[], role:'owner' };访客:{ request|null, role:'visitor' } |
双角色:Owner 看全部申请、访客看自己 |
POST /api/providers/[authorId]/requests |
Required | body:{ message? ≤500 } |
{ id, status:'pending', created_at }(201);upsert membership_requests(requester_id+author_id),重置为 pending |
申请/重新申请 |
PATCH /api/providers/[authorId]/requests |
Required (Owner) | body:{ requestId, action:'approve'|'reject' } |
由 <MembershipRequests> 调用 |
Owner 批准/拒绝 |
用户 & 设置 & Auth
| 方法 + 路径 | 认证 | 请求 | 响应 / 副作用 | 调用方 |
|---|---|---|---|---|
GET /api/user/profile |
Required | — | { id, name, bio, avatar_url, login, email, author_email, social_links, providers } |
Settings、CLI ah whoami |
PATCH /api/user/profile |
Required | body(可选):{ name(≤100), bio(≤500), email, social_links } |
{ success };ensureAuthor + revalidateAuthor(login) |
Settings |
DELETE /api/user/profile |
Required | — | { success };匿名化 authors 行 + supabase.auth.admin.deleteUser + revalidateAuthor |
Settings danger zone |
GET /api/settings/cli-tokens |
Required | — | { tokens: CliToken[] } |
Settings API tab |
POST /api/settings/cli-tokens |
Required | body:{ name } |
{ token, id, name, created_at }(明文仅一次);写 cli_tokens |
Settings API tab |
DELETE /api/settings/cli-tokens/[id] |
Owner | — | { success, disconnected_agents: string[] };设 revoked_at 并调 Bridge 断开 |
Settings API tab |
GET /api/settings/cli-tokens/[id]/agents |
Owner | — | { agents: [{id, name, agent_type, is_online:true}] } |
Revoke 对话框警告 |
POST /api/auth/device |
Public | body:{ client_info? } |
{ device_code, user_code, verification_uri, verification_uri_complete, expires_in:900, interval:5 };写 device_codes 15min TTL |
CLI 发起设备码 |
POST /api/auth/device/authorize |
Cookie session | body:{ user_code } |
{ success, client_info };绑定 user_id 到 device_code |
/auth/device 浏览器授权 |
POST /api/auth/device/token |
Public | body:{ device_code } |
授权:{ access_token(ah_), token_type:'Bearer', user };未授权:{ error:'authorization_pending' }(400);过期/已用:{ error:'expired_token'|'invalid_grant' } |
CLI 轮询 |
A2A
| 方法 + 路径 | 认证 | 说明 |
|---|---|---|
POST /api/a2a/[author]/[slug]/jsonrpc |
由 resolveAgentForA2A 判定 |
JSON-RPC 2.0 ingress(tasks/send 等),受 A2A_COMPAT_ENABLED feature flag 控制;响应头带 A2A-Version |
POST /api/a2a/tasks/callback?task_id=&agent_id= |
X-Platform-Secret | Bridge Worker 异步 A2A 任务完成回调:{ request_id, status?, result?, attachments?, file_transfer_offer?, duration_ms? } |
GET /api/well-known-a2a/[author]/[slug]/card |
Optional | Agent Card 发现端点;Cache-Control: public, max-age=60, s-maxage=300;A2A-Version 头;A2A_COMPAT_ENABLED 控制 |
前端消费 API 映射(快速查表)
| 前端页面 | 端点 |
|---|---|
/ |
— |
/agents |
服务端直查 Supabase(agents/authors/agent_calls/author_subscriptions) |
/agents/[login] |
服务端直查 + GET /api/providers/{authorId}/requests + POST /api/providers/{authorId}/requests + PATCH /api/providers/{authorId}/requests |
/agents/[login]/[slug] |
GET /api/agents/{id} + GET /api/agents/discover?author_id=...&exclude=...&limit=5 |
/auth/device |
POST /api/auth/device/authorize |
/settings?tab=profile |
GET/PATCH/DELETE /api/user/profile + Supabase linkIdentity |
/settings?tab=developer |
GET/POST/PUT/DELETE /api/developer/agents[/*] |
/settings?tab=api |
GET/POST/DELETE /api/settings/cli-tokens[/*] + GET /api/settings/cli-tokens/{id}/agents |
/blog、/blog/[slug] |
服务端 Notion SDK |
/docs、/docs/[slug] |
服务端 GitHub Contents API |
共享组件 & 机制
| 关注点 | 位置 | 说明 |
|---|---|---|
| 身份 | AuthProvider (src/components/AuthProvider.tsx) |
Supabase session + AuthModal 控制 |
| 登录弹窗 | AuthModal (src/components/AuthModal.tsx) |
全局单例,由 layout 挂载 |
| React Query | QueryProvider (src/components/providers/QueryProvider.tsx) |
所有客户端数据层通过 TanStack Query(禁止裸 fetch) |
| 认证中间件 | src/lib/auth-middleware.ts |
verifyToken() 解析 Supabase JWT 或 ah_ CLI token |
| 内容脱敏 | src/lib/privacy-mask.ts |
私密 agent 名/id 掩码 |
| 响应脱敏 | src/lib/response-sanitizer.ts |
Agent 返回内容安全过滤 |
| Agent URL | src/lib/agent-url.ts |
getAgentUrl({slug, name, author_login}) |
| 会话服务 | src/lib/agent-session-service.ts |
把外部 session_id 映射到内部 relay session_key |
| Mesh client | src/lib/mesh-client.ts |
与 mesh.agents.hot Bridge Worker 通信 |
| 缓存预设 | src/lib/cache.ts |
CACHE_PRESETS.LIST 等 CDN 缓存策略 |
| SEO | src/lib/seo-metadata.ts |
buildCommonPageMetadata、getLocalizedUrl、getLanguageAlternates、getSeoKeywords |
| 集成 Prompt | src/lib/integration-prompt.ts |
静态字符串,Hero 与 Integrate 复用 |
| 语言建议 | LanguageSuggestion |
基于 navigator.language,依赖 localStorage['agents-hot-lang-pref'] 与 sessionStorage['agents-hot-lang-dismissed'] |
| 主题 | ThemeProvider |
强制 dark |
| 通知 | <Toaster> (sonner) |
挂在 locale layout |
| 滚动动画 | useScrollReveal(threshold) + <AnimateIn> |
IntersectionObserver fade-up |
| 媒体查询 | useMediaQuery |
src/hooks/use-media-query.ts |
| shadcn 基础组件 | src/components/ui/* |
alert-dialog / avatar / badge / button / card / dialog / drawer / dropdown-menu / input / select / separator / skeleton / sonner / switch / textarea / tooltip |
外部集成
| 服务 | 用途 | 关键 env |
|---|---|---|
| Supabase | Postgres + Auth + Storage;服务端直查 + SDK 客户端 | NEXT_PUBLIC_SUPABASE_URL、NEXT_PUBLIC_SUPABASE_ANON_KEY、SUPABASE_SERVICE_ROLE_KEY |
| Notion | 博客数据源 | NOTION_TOKEN、NOTION_BLOG_DATABASE_ID |
| GitHub Contents API | 文档数据源 | DOCS_REPO、DOCS_BRANCH、DOCS_PATH、GITHUB_TOKEN? |
| Bridge Worker(Mesh) | Agent 运行时中继 | BRIDGE_PLATFORM_SECRET、MESH_URL 等(查 src/lib/mesh-client.ts) |
| Google Analytics | gtag | G-NM23MTTPG5(硬编码在 layout) |
| Microsoft Clarity | 行为回放 | vlcvyxkwa1(硬编码) |
| Ahrefs Analytics | — | 2Hy0GZSI6JFaV06BKklp7Q(硬编码) |
重写时值得一并修正的瑕疵
/about占位:Footer Privacy/Terms 都指向/about;AuthModal 里又引用/legal/terms、/legal/privacy。这两套链接目前都没有真实页面,需要统一。/settings/debug/{id}:Developer tab Edit Drawer 里的 Debug 按钮指向这条路由,但该路由并未实现。/auth/device未接 i18n:DeviceAuthClient.tsx的文案全部硬编码英文。JoinRequestButton与MembershipRequests重复请求:同一GET /api/providers/{authorId}/requests用了两套 query key。重写时合并为一次查询。CEOQuote的统计数字硬编码:12/46/13/100%在组件里是字符串,若要接真实数据需要新增 API。force-dynamic的/agents与/docs/*:相对昂贵;前者有 CDN 缓存头补偿,后者每请求都打 GitHub API。重写时可评估用 ISR + webhook 重新校验。- Agent 详情页 SSR=false:RSC 只吐骨架,所有内容客户端 fetch,对 SEO 依赖 JSON-LD。若需要让用户看到首屏内容,可以在 RSC 直接渲染静态字段再 hydrate 动态部分。
generateStaticParams只生成英文博客 slug:中文详情页永远 fallback 动态渲染。- Settings tab id 与标签不符:CLI Tokens tab 在 URL 中叫
api,在导航里写成 "API"。 robots: noindex:/auth/signin与/settings已 noindex,重写时保留。