# StyloBot 系列发布@:行为 @-}Aware typeScript UI

*此文章显示StyloBot'}分类结果如何在 Script}:}服务器中继器为快件和快件中的应用逻辑@,}模板辅助器*

[<img src="/articleimages/stylobot-logo.svg" alt="StyloBot" width="120" />](https://www.stylobot.net)

> **缩略 `@stylobot/core` 和 `@stylobot/node` npm 软件包不久将发布@. @%** 下面的片段描述它们将暴露的表面@. @

> **StyloBot 释放系列**
> 
> 1. [**行为@, @% 不是身份**](/blog/stylobot-fingerprint)为何StyloBot以行为方式模拟客户?
> 2. [**ASP=.@NET 用户界面**](/blog/behaviour-aware-ux)@ : 服务器@ MS K1 {. NET 应用程序的表面=% 1}
> 3. [**在LongM-Running .NET服务中查找和固定无约束增长**](/blog/stylobot-release-reliability)使引擎在生产过程中无趣的可靠性纪律
> 4. **檔案型態Script UI**此篇文章 :
> 5. [**侧车建筑**](/blog/sidecar-architecture)@ : 检测引擎如何连接到非@ MS K1NET 堆栈
> 6. [**学习加快速度**](/blog/stylobot-release-learning)“:”适应性学习系统“, 4 -”记忆和裁决缓存
> 7. [**静坐不动的测试**](/blog/stylobot-release-nondeterministic-testing): 校验纪律 @: 一个 BDF 文件驱动回溯@,load @MS K3和校准
> 8. [**StyloExptracte {-} 本地学习 HTML 到 标记下转换器**](/blog/stylobot-release-styloextract): HTML→ Markdown 层与探测器对齐 MSSK2 行尸虫清晰识别捕捉了\, 和狗食环让它诚实

StyloBot'}StyloBotMSMSK@.NET CoreMSMK2 高端- 性能框架,其优异的原始元素=, supp*-milli second holk}-Patrent latencyMS K7和生产20年pedigree 后面的StyolobotoMSKOT+.STymbript SDK是将引擎\'s输出引入NodeMSOK10js应用程序和浏览器的表面,无需了解任何有关在“.” {(热-path lantenge来自涵盖的适应学习系统。 [学习加快速度](/blog/stylobot-release-learning)@: 一旦一个指纹被足够自信地看到 \ ,\ 裁决缓存跳过整个探测器管道和答案 在微秒@.)

<!--category-- TypeScript, Node.js, StyloBot, Bot Detection, Security -->
<datetime class="hidden">2026-06-01T10:30</datetime>

# 一. 导言导言 导言 导言 导言

在 TypeScript 生态系统中,大多数的机器人检测都给了你两样东西之一: 一个浏览器指纹可以告诉你是否 *客户* 在用户@- @Agent 字符串上看起来是自动的\ ,}或正则, 只捕捉宣布自己为{.}的机器人字符串

应用层也无用 @.

[立方体](https://github.com/fingerprintjs/BotD) 在浏览器中运行, 且无法查看请求信头@, TLS 指纹 @,会话行为@MS K2或IP名声@, 或IP 名声 @MS K3 无头的铬和 Googlebot 看上去与它完全一样.纯 UA 匹配的软件包 @ (}`is-bot`, `isbot`) 捕捉到的只是被抓住的选择 @. 网络@- 游戏产品 @MS K3Cloudflare Bot 管理}, DataDome) 區塊,但無法在應用層有個性化 {.你得知一個請求被封,從來沒有到達您的應用程式有「0.87boat概率」 和中度風險波段MS K9,所以你的檢查頁本可以顯示摩擦步骤,而你的解析器可能已經排除了它,所有沒有錯誤代碼的請求 *.}

StyloBot 赋予您 TypeScript 应用程序的是一个判决@ : @ 输入结果, 一个连续的 Bot 概率评分@ MPK1_ 分类 @ MS K2}`AiBot`, `Scraper`, `GoodBot`, `MaliciousBot`@, ...), 风险带, 推荐的行动\, 和威胁评分.

此文章通过一个抽样商店前端, 并显示该裁决如何成为每个层的应用程序行为@ : @ server middware @ MS K1}# 模板助手@ MPK2 和浏览器组件@ I.}

[TOC]

# 判决

万事如意,这就是我们到达的地方

```ts
// @stylobot/core
interface Verdict {
  isBot: boolean;
  botProbability: number;        // 0.0–1.0 - not binary, a continuous score
  confidence: number;            // certainty in that score
  botType: BotType | null;       // 'AiBot' | 'Scraper' | 'GoodBot' | 'MaliciousBot' | ...
  botName: string | null;        // 'GPTBot', 'Googlebot', 'curl/8.9.1', ...
  riskBand: RiskBand;            // 'VeryLow' | 'Low' | 'Elevated' | 'Medium' | 'High' | 'VeryHigh'
  recommendedAction: RecommendedAction;  // 'Allow' | 'Throttle' | 'Challenge' | 'Block'
  threatScore: number;           // elevated when CVE probes or credential stuffing detected
  threatBand: ThreatBand;        // 'None' | 'Low' | 'Elevated' | 'High' | 'Critical'
}
```

检出终点可加速 `Medium` API可以排除风险 `AiBot` 登录表可以添加证明@- @of_-}工作 `Elevated` 风险,而不会触及 `Low` 如果检测结果为网络层的布丁,则无法做到这一点 .

风险波段按顺序排列 *:* `VeryLow`, `Low`, `Elevated`, `Medium`, `High`, `VeryHigh`, `Verified`.

# 安装装置

```bash
npm install @stylobot/core @stylobot/node

# only needed for grpc mode
npm install @grpc/grpc-js @grpc/proto-loader
```

`@stylobot/core` 它包含定义类型@, REST client_, 标题派, 和 @( optionally}) gRPC clidient. 它在Node, DenoMS K8 与 Bun 中工作,没有修改\.

`@stylobot/node` 提供快速快捷插件@,}模板辅助工具 *,和判决注射器=. 它取决于 `@stylobot/core` 在运行时没有别的东西... .

# 服务器表面

## 判决如何达到你的中器

中间器件有三种模式\.}所有三个产品都一样 `req.stylobot` 形状@.}您使用的哪个取决于基础设施检测运行中的位置 @:

```mermaid
graph LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px

    subgraph H ["headers mode - zero added latency"]
        GW[Gateway / Caddy / YARP]:::input -->|"X-StyloBot-* headers"| A[Express / Fastify]:::good
    end

    subgraph G ["grpc mode - HTTP/2, no JSON overhead"]
        B[Express / Fastify]:::good -->|"gRPC Detect RPC"| SC[StyloBot Sidecar :5090]:::async
    end

    subgraph R ["api mode - REST, full signal set"]
        C[Express / Fastify]:::good -->|"POST /api/v1/detect"| SC2[StyloBot Sidecar :5091]:::async
    end
```

**`headers` 模式模式**@: 检测在网关上向上游跑 *;中器读到注射头,没有网络呼叫*. 这是Caddy或YARP门坐在前面时的生产模式*MS K3

**`grpc` 模式模式**中继器直接呼叫侧车 在HTTP/2.}低档Lowest lantency*, 不需要JSON高架机 *,* `@grpc/grpc-js`GRPC接口和侧车部署细节载于 [侧面车架建筑文章](/blog/sidecar-architecture).

**`api` 模式模式**# :呼叫# `POST /api/v1/detect` HTTP\/1.1.}返回 `reasons` (per-检测器捐款})和 `signals` @ ( @ 整个黑板状态@ MS K1} 其他模式忽略了 @ MPK2 @

中间软件总是不能打开@ . @ 如果侧车无法到达或乘数超过@,} `req.stylobot` 设定为允许的空判结果,请求继续@. @%

## 装配起来

**特快讯号0**

```ts
import express from 'express';
import { styloBotMiddleware } from '@stylobot/node';

const app = express();
app.use(styloBotMiddleware({ mode: 'headers' }));
// or:
app.use(styloBotMiddleware({ mode: 'grpc', endpoint: 'localhost:5090', timeout: 100 }));
```

**快速化@ : @%**

```ts
import Fastify from 'fastify';
import { styloBotPlugin } from '@stylobot/node';

const fastify = Fastify();
await fastify.register(styloBotPlugin, { mode: 'headers' });
```

添加类型声明, 如果您想要在快速化请求中键入访问权限@: @%

```ts
// types.d.ts
import type { StyloBotResult } from '@stylobot/node';
declare module 'fastify' {
  interface FastifyRequest { stylobot: StyloBotResult; }
}
```

# 店铺

同一样品仓库 [ASP.NET 文章](/blog/behaviour-aware-ux)“,”产品页面@,Q 校验“, login}, 通讯 @, 但是在Script. Type Scrippt}检测结果是一样的#;什么变化是表面如何暴露它的吗?.

## 产品页面

他们想要产品名称 , 价格 MS K2 和库存水平 用搜索引擎为目录编制索引 对人类有用

*假想: 切价点击 `/products/:id`没有折扣代码 , 没有加号 MS K2 @to@-}卡特按钮 @, 也没有值得使用“.”的购买信号*

路由处理器:

```ts
app.get('/products/:id', (req, res) => {
  const { verdict } = req.stylobot;
  const product = getProduct(req.params.id);

  res.render('product', {
    product,
    sbVerdict: verdict,
    showDiscount:   verdict.riskBand === 'VeryLow' || verdict.riskBand === 'Low',
    showBuyButton:  !verdict.isBot,
    showCartWarning: verdict.recommendedAction === 'Challenge',
    isSearchBot:    verdict.botType === 'SearchEngine' || verdict.botType === 'VerifiedBot',
  });
});
```

模板 @(_Handlebars@):}#

```handlebars
{{! verified crawlers get structured metadata instead of commercial UI }}
{{#if isSearchBot}}
  <meta name="description" content="{{product.name}} -{{product.category}}." />
  <p>{{product.description}}</p>
{{else}}

  {{! discount only for low-risk human visitors }}
  {{#if showDiscount}}
    <div class="alert alert-success">
      Member price: use code <strong>LOYAL10</strong> for 10% off.
    </div>
  {{/if}}

  {{! add-to-cart only for non-bot traffic }}
  {{#if showBuyButton}}
    <form method="post" action="/cart/add">
      <input type="hidden" name="productId" value="{{product.id}}" />
      {{#if showCartWarning}}
        <p class="text-warning">Additional verification may be required at checkout.</p>
      {{/if}}
      <button type="submit" class="btn btn-primary">Add to Cart</button>
    </form>
  {{else}}
    <p class="text-muted">Purchase available to human visitors.</p>
  {{/if}}

{{/if}}
```

在此使用的三种模式 @ . @ Crawler create=: `SearchEngine` 和 `VerifiedBot` 获取没有商业信号的结构化元数据@, 这正是他们真正需要的 @. 贴现目标\: 一个只显示为低Q-}风险流量的忠诚代码 `Challenge` 是一个警告,不是一个街区MS K1可疑会话仍然可以买到.

## 页面@2:检出

欺诈性自动化@,卡测试器 @,券brute}-forrs,和脚本的重写在校验时都汇合\.。目标是深度防御,没有CAPTCHAMS K5每个人的第一体验_.#

*假想@: @ a passer *% 1} 测试机器人点击率 `/checkout`“. ” 它看到一条已死的消息“- end message , 提交一个蜂蜜罐 -填充的表格$, 并收到一份无声确认, 没有重试奖励@.*

```mermaid
flowchart TD
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px
    classDef problem fill:none,stroke:#ef4444,stroke-width:2px

    A([Visitor reaches /checkout]):::input --> B{bot?}
    B -->|yes| C[Dead-end message - no form]:::problem
    B -->|no| D{risk band}
    D -->|Low| E[Express checkout CTA]:::good
    D -->|Elevated| F[Standard checkout CTA]:::good
    D -->|High+| G[Phone number - no online path]:::problem
    E & F --> H[POST /checkout/order]
    H --> I{honeypot triggered?}
    I -->|yes| J[Silent fake confirmation]:::problem
    I -->|no| K[Process real order]:::good
```

路线@:

```ts
app.get('/checkout', (req, res) => {
  const { verdict } = req.stylobot;
  res.render('checkout', {
    sbVerdict: verdict,
    showForm:     !verdict.isBot,
    showExpress:  verdict.riskBand === 'VeryLow' || verdict.riskBand === 'Low',
    showStandard: ['VeryLow','Low','Elevated'].includes(verdict.riskBand),
    showPhone:    ['High','VeryHigh'].includes(verdict.riskBand),
  });
});

app.post('/checkout/order', (req, res) => {
  // honeypot fields are invisible to humans; bots fill everything
  if (req.body.hp_name || req.body.hp_email) {
    return res.redirect('/checkout/confirmed');  // silent fake - no retry incentive
  }
  return processRealOrder(req, res);
});
```

模板@:

```handlebars
{{#if showForm}}
  <form method="post" action="/checkout/order">

    {{! invisible honeypot trap - humans leave blank, bots fill everything }}
    <div style="position:absolute;left:-9999px;opacity:0" aria-hidden="true">
      <input type="text" name="hp_name" tabindex="-1" autocomplete="off" />
      <input type="email" name="hp_email" tabindex="-1" autocomplete="off" />
    </div>

    {{#if showExpress}}
      <button type="submit" name="express" value="true" class="btn btn-success btn-lg">
        Express Checkout
      </button>
    {{/if}}

    {{#if showStandard}}
      <button type="submit" class="btn btn-primary">Proceed to Payment</button>
    {{/if}}

    {{#if showPhone}}
      <p>Please call us to complete your order: <strong>0800 123 456</strong></p>
    {{/if}}

  </form>
{{else}}
  <p class="text-muted">Checkout is available to human visitors only.</p>
{{/if}}
```

蜜罐丢弃是这里的关键技术@. @ 错误反馈教育攻击者变换@ ;} 一个静默的假确认浪费了他们的时间\ ,_ 和确认页面看起来完全一样, 无论该命令是否真实或被抛弃@ I. Express check outp as a trust betweat @ MS K4 not a defair { ) 意指只有赢得短路的会话才能在生产过程中获得更短的路 #.}%,乘以蜂池 `<div>` 折叠成共享的处理栏部分@ ( @ {}`{{> honeypot}}`@)_ 而不是在每一个受保护的窗体上重复内嵌样式块@.}

## 页面=3:登入

登入为 credicialial@- @stuffing 领土\ .}交易=-off 不同于支票_:}校验时的假正数可能丢失一份销售; 错过了一次证书=MSSK5}填充攻击会失去一个账户 <. >

*假想@: @ a credicial=% - statusing 脚本点击 `/login`它看到了一个威慑信息 ,\}填满了蜂蜜罐 ~MS K2}然后被弹射到 `/login/denied` 在任何认证运行前@. @%*

```ts
app.get('/login', (req, res) => {
  const { verdict } = req.stylobot;
  res.render('login', {
    sbVerdict: verdict,
    showWarning: verdict.isBot,
    showRisk:    ['High','VeryHigh'].includes(verdict.riskBand),
  });
});

app.post('/login', async (req, res) => {
  if (req.body.hp_user || req.body.hp_pass) {
    return res.redirect('/login/denied');
  }
  if (req.stylobot.verdict.isBot) {
    return res.redirect('/login/denied');
  }
  return authenticate(req, res);
});
```

```handlebars
{{#if showRisk}}
  <div class="alert alert-danger">
    High-risk signals detected. Login attempts are logged.
  </div>
{{/if}}

{{#if showWarning}}
  <div class="alert alert-warning">
    Automated login attempts are detected and blocked.
  </div>
{{/if}}

<form method="post" action="/login">
  <div style="position:absolute;left:-9999px;opacity:0" aria-hidden="true">
    <input type="text" name="hp_user" tabindex="-1" autocomplete="off" />
    <input type="password" name="hp_pass" tabindex="-1" autocomplete="off" />
  </div>
  <div class="form-group">
    <label for="email">Email</label>
    <input type="email" id="email" name="email" autocomplete="email" />
  </div>
  <div class="form-group">
    <label for="password">Password</label>
    <input type="password" id="password" name="password" autocomplete="current-password" />
  </div>
  <button type="submit" class="btn btn-primary">Sign In</button>
</form>
```

机器人在提交“.”之前先看到威慑力量,如果它提出反正“MS K1” 它会抓住蜂蜜罐 如果它能以某种方式避免蜂蜜壶‘,’ `verdict.isBot` 是所有验证逻辑运行前的最后服务器@ - @ side check

## 《邮报》

AI爬行者是一个不同的问题 @.他们不是试图brete}-Force账户或收成价格@;它们正在摄入示范培训的内容#. 他们通常对身份很诚实$,,这意味着正确的回应是商业上的而不是对抗性的\.

*GPTBot 点击率 `/newsletter`@.#它看到的是数据许可信息, 而不是订阅声道@.}它的提交被静悄悄地丢弃\.#*

```ts
app.get('/newsletter', (req, res) => {
  const { verdict } = req.stylobot;
  res.render('newsletter', {
    sbVerdict:     verdict,
    isHuman:       !verdict.isBot,
    isAiBot:       verdict.botType === 'AiBot',
    isOtherBot:    verdict.isBot && verdict.botType !== 'AiBot',
  });
});

app.post('/newsletter/subscribe', (req, res) => {
  if (req.body.hp_email2 || req.stylobot.verdict.isBot) {
    return res.redirect('/newsletter/thanks');  // silent discard
  }
  mailingList.subscribe(req.body.email);
  return res.redirect('/newsletter/thanks');
});
```

```handlebars
{{#if isHuman}}
  <p>Get exclusive deals delivered to your inbox. Unsubscribe any time.</p>
{{/if}}

{{#if isAiBot}}
  <div class="alert alert-info">
    This subscription endpoint is for human readers.
    For data licensing enquiries, <a href="/contact">contact us directly</a>.
  </div>
{{/if}}

{{#if isOtherBot}}
  <div class="alert alert-warning">
    Automated subscription attempts are discarded.
  </div>
{{/if}}

<form method="post" action="/newsletter/subscribe">
  <div style="position:absolute;left:-9999px;opacity:0" aria-hidden="true">
    <input type="email" name="hp_email2" tabindex="-1" autocomplete="off" />
  </div>
  <div class="form-group">
    <input type="email" name="email" placeholder="your@email.com" autocomplete="email" />
  </div>
  <button type="submit" class="btn btn-success">Subscribe</button>
</form>
```

`AiBot` 不只是另一个敌对标签@; @ it is a classion 使得企业能够做出响应.}一个许可信息比它告诉操作员他们正在处理什么,并给予他们一项行动来采取\NSK3}

# 模板助手器

## 将判决输入模板

`sbVerdictInjector` 在探测中器之后运行 并做出判决 `res.locals`:

```ts
import { sbVerdictInjector } from '@stylobot/node';

app.use(styloBotMiddleware({ mode: 'headers' }));
app.use(sbVerdictInjector({ mode: 'gateway' }));
// or for direct sidecar calls (REST port, not the gRPC port):
app.use(sbVerdictInjector({ mode: 'sidecar', endpoint: 'http://localhost:5091' }));
```

`res.locals.sbVerdict` 每个模板中都可用@. @% `res.locals.sbVerdictScript` 是一个内嵌 `<script>` 标签设置 `window.__sb` 用于浏览器组件@. @%

## 处理栏

定义界定 `RISK_ORDER` 在以下模块范围 {(}Nunjucks 和 EJS 辅助器使用同一地图=):}

```ts
import type { Verdict, RiskBand } from '@stylobot/core';

const RISK_ORDER: Record<string, number> = {
  Unknown: 0, VeryLow: 1, Low: 2, Elevated: 3, Medium: 4, High: 5, VeryHigh: 6, Verified: 7,
};

// register on your Handlebars engine instance
hbs.engine({
  helpers: {
    // {{#sbGate sbVerdict "Low"}}...{{else}}...{{/sbGate}}
    sbGate(verdict: Verdict | null, maxRisk: string, options: any) {
      if (!verdict) return options.fn(this);
      return RISK_ORDER[verdict.riskBand] <= RISK_ORDER[maxRisk]
        ? options.fn(this)
        : options.inverse(this);
    },

    // {{#sbIsBot sbVerdict}}...{{else}}...{{/sbIsBot}}
    sbIsBot(verdict: Verdict | null, options: any) {
      return verdict?.isBot ? options.fn(this) : options.inverse(this);
    },

    // {{#sbBotType sbVerdict "AiBot"}}...{{/sbBotType}}
    sbBotType(verdict: Verdict | null, type: string, options: any) {
      return verdict?.botType === type ? options.fn(this) : options.inverse(this);
    },

    // {{#sbMinRisk sbVerdict "High"}}...{{/sbMinRisk}}
    sbMinRisk(verdict: Verdict | null, minRisk: string, options: any) {
      if (!verdict) return options.inverse(this);
      return RISK_ORDER[verdict.riskBand] >= RISK_ORDER[minRisk]
        ? options.fn(this)
        : options.inverse(this);
    },
  },
});
```

在模板中 @:

```handlebars
{{#sbGate sbVerdict "Low"}}
  <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
{{else}}
  <a href="/checkout" class="btn btn-secondary">Checkout</a>
{{/sbGate}}

{{#sbBotType sbVerdict "AiBot"}}
  <div class="alert alert-info">AI crawler detected. Data licensing info above.</div>
{{/sbBotType}}

{{#sbMinRisk sbVerdict "High"}}
  <div class="alert alert-danger">High-risk session. Some features restricted.</div>
{{/sbMinRisk}}
```

## 努纳克人

```ts
import nunjucks from 'nunjucks';

// RISK_ORDER - same map defined in the Handlebars section above

class SbGateExtension {
  tags = ['sbgate'];
  parse(parser: any, nodes: any, lexer: any) {
    const tok = parser.nextToken();
    const args = parser.parseSignature(null, true);
    parser.advanceAfterBlockEnd(tok.value);
    const body = parser.parseUntilBlocks('else', 'endsbgate');
    let elseBody = null;
    if (parser.skipSymbol('else')) {
      parser.skip(lexer.TOKEN_BLOCK_END);
      elseBody = parser.parseUntilBlocks('endsbgate');
    }
    parser.advanceAfterBlockEnd();
    return new nodes.CallExtension(this, 'run', args, [body, elseBody]);
  }
  run(context: any, maxRisk: string, body: any, elseBody: any) {
    const v = context.ctx.sbVerdict;
    const fits = !v || RISK_ORDER[v.riskBand] <= RISK_ORDER[maxRisk];
    return fits ? body() : (elseBody?.() ?? '');
  }
}
env.addExtension('SbGateExtension', new SbGateExtension());
```

```nunjucks
{% sbgate "Low" %}
  <a href="/checkout/express">Express checkout</a>
{% else %}
  <a href="/checkout">Checkout</a>
{% endsbgate %}
```

## EJS 电子日志

EJS 没有块状助推器,, 但本地功能覆盖大多数案例

```ts
// RISK_ORDER - same map defined in the Handlebars section above

app.use((req, res, next) => {
  const v = res.locals.sbVerdict ?? null;
  res.locals.sbBelowRisk = (max: string) =>
    !v || RISK_ORDER[v.riskBand] <= RISK_ORDER[max];
  res.locals.sbAboveRisk = (min: string) =>
    v && RISK_ORDER[v.riskBand] >= RISK_ORDER[min];
  res.locals.sbIsBot  = () => v?.isBot ?? false;
  res.locals.sbBotIs  = (type: string) => v?.botType === type;
  next();
});
```

```ejs
<% if (sbBelowRisk('Low')) { %>
  <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
<% } else { %>
  <a href="/checkout" class="btn btn-secondary">Checkout</a>
<% } %>

<% if (sbBotIs('AiBot')) { %>
  <div class="alert alert-info">AI crawler. Data licensing enquiries via /contact.</div>
<% } %>
```

# 浏览器表面

## 将判决输入浏览器

添加新增 `{{{sbVerdictScript}}}` 在您的布局头上@ . @ it makes as\ MS K1}

```html
<script>window.__sb = {"isBot":false,"botProbability":0.12,"riskBand":"Low","recommendedAction":"Allow",...}</script>
```

然后装入元素捆绑\ :

```handlebars
<head>
  {{{sbVerdictScript}}}
  <script type="module" src="/js/sb-elements.js"></script>
</head>
```

## `<sb-gate>`@:_ 按风险带显示或隐藏内容

```html
<!-- only shown when riskBand <= 'low' -->
<sb-gate max-risk="low">
  <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
</sb-gate>

<!-- only shown when riskBand >= 'elevated' -->
<sb-gate min-risk="elevated">
  <div class="alert alert-warning">Unusual activity detected on your session.</div>
</sb-gate>
```

## `<sb-adapt>`: 选择第一个匹配的大小写

```html
<sb-adapt>
  <sb-case max-risk="low">
    Standard checkout - enter your card details below.
  </sb-case>
  <sb-case max-risk="medium">
    Additional verification is required before we can process your order.
  </sb-case>
  <sb-case>
    Automated checkout access is not permitted.
  </sb-case>
</sb-adapt>
```

`<sb-adapt>` 选择第一个 `<sb-case>` 其 `max-risk` 并隐藏其余的 #.# `<sb-case>` 无无 `max-risk` 都显示 `<sb-gate>` 和 `<sb-adapt>` 收听 `sb:verdict` 并重新评价第一个涂料后是否改变判决@.}

## `<sb-widget>`:伺服器 @- 液态碎片

`<sb-gate>` 隐藏内容客户端@-side.}如果您需要机器人来接收不同的标记 @,}不是隐藏的标记{,} 您需要在服务器上以模板可用的检测环境提供碎片\.}

`<sb-widget>` 从内含读取液体模板 `<template>` 元素 , 将页面上的所有部件请求分解为单圆\ MS K1 trip,, 并用返回的 HTML:替换 。

```html
<sb-widget data-sb-widget="checkout-cta">
  <template>
    {% if recommendedAction == 'Block' %}
      <p class="text-danger">Automated checkout is blocked.</p>
    {% elsif botType == 'AiBot' %}
      <p>AI crawlers cannot complete purchases. <a href="/contact">Data licensing enquiries</a>.</p>
    {% elsif isBot %}
      <p class="text-muted">Checkout is available to human visitors.</p>
    {% else %}
      <a href="/checkout/express" class="btn btn-success">Express Checkout</a>
    {% endif %}
  </template>
</sb-widget>
```

模板变量@: @% `isBot`, `botProbability`, `confidence`, `botType`, `botName`, `riskBand`, `recommendedAction`, `threatScore`, `threatBand`.+任何 `vars` 您在调用时从服务器传出@ - @%side `RenderWidget` 直接的.

全部 `<sb-widget>` 连接同一微时针的元素会被分解成一个请求@. @% 1个有五个部件的页面, 一个可以生成一个回合@ -} trip_ :}

```mermaid
sequenceDiagram
    participant B as Browser
    participant C as Coordinator
    participant S as /_stylobot/partials/render
    participant L as Fluid / Liquid

    B->>C: sb-widget#checkout-cta registers
    B->>C: sb-widget#promo-banner registers
    B->>C: sb-widget#nav-links registers
    Note over C: queueMicrotask - one batch
    C->>S: POST { widgets: { checkout-cta: "...", promo-banner: "...", nav-links: "..." } }
    S->>L: render each template with verdict context
    L-->>S: rendered HTML fragments
    S-->>C: <div data-sb-widget="checkout-cta">...</div> ...
    C-->>B: each sb-widget replaced in DOM
```

# 总表

|#元件 @|_图层@|}它做了什么?
|---|---|---|
| `styloBotMiddleware` * M|}快讯 ∮ 速报 ∮ |∮ 随员 `req.stylobot` 通过标题 @ /# gRPC #/_ REST *|*
| `styloBotPlugin` | 快速化 @|Same@,作为快捷插件 `decorateRequest` |
| `sbVerdictInjector` |}快讯 ∮|∮ 弹出 `res.locals.sbVerdict` 和 `sbVerdictScript` |
|+Handlebars 帮手 @|+Server模板@| `sbGate`, `sbIsBot`, `sbBotType`, `sbMinRisk` 助推器 @ | @%
@|Nunjucks 扩展 {|} 服务器模板@| `{% sbgate "Low" %}...{% endsbgate %}` |
| EJS 本地用户 @|服务器模板@| `sbBelowRisk()`, `sbAboveRisk()`, `sbIsBot()`, `sbBotIs()` |
| `<sb-gate>` |}浏览器 @|}根据风险波段显示#/}hiide 内容@,}收听 `sb:verdict` |
| `<sb-adapt>` | 浏览器 @| 从列表中选择第一个匹配的大小写@|
| `<sb-widget>` |瀏覽器 @| 抓取伺服器@- 已產生液體碎片\, 批批量 {|}
| `StyloBotGrpcClient` | 節點 / bun MS K2 Deno | 直接 gRPC 客戶端, 用于非@- middleware 使用 @|

下一则发布序列中的“:” [**侧车建筑**](/blog/sidecar-architecture)“,” 检测引擎如何连接到非“-.NET Tatts,GO SDKMS K3Cddy插件{,}和将它连接在一起的 gRPC界面@.}

如果你从".NET"抵达这里 [ASP=.@NET 用户界面](/blog/behaviour-aware-ux) 使用标签辅助器和动作过滤器覆盖相同的商店前端模式@;}保持引擎内存的可靠性重新工作=-}稳定在持续流量下 [在LongM-Running .NET服务中查找和固定无约束增长](/blog/stylobot-release-reliability).+Live 引擎 @,_QTaptro@,#和商用控制 [stylobot\% . @ net](https://www.stylobot.net).