<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ekemini Samuel</title>
    <description>The latest articles on DEV Community by Ekemini Samuel (@envitab).</description>
    <link>https://dev.to/envitab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F823697%2F01838b0b-d454-4382-ad1b-75d036ea50dd.jpg</url>
      <title>DEV Community: Ekemini Samuel</title>
      <link>https://dev.to/envitab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/envitab"/>
    <language>en</language>
    <item>
      <title>Top 5 Enterprise AI Gateways</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Wed, 14 Oct 2026 06:57:35 +0000</pubDate>
      <link>https://dev.to/envitab/top-5-enterprise-ai-gateways-4e1d</link>
      <guid>https://dev.to/envitab/top-5-enterprise-ai-gateways-4e1d</guid>
      <description>&lt;p&gt;Most companies did not plan their AI infrastructure. It grew as usage across teams increased. Someone wired up the OpenAI API for a prototype, another added Anthropic because it was better at a specific task, and six months later there are a dozen applications, each with its own API keys, its own retry logic, and no shared view of who is spending what.&lt;/p&gt;

&lt;p&gt;An enterprise AI gateway is the thing that stops sprawl from becoming a liability: one layer, in front of every model call, that knows who is calling, what they are allowed to do, and what it costs.&lt;/p&gt;

&lt;p&gt;This list is not about which gateway has the most features. It is about which ones actually hold up once security, platform, and finance teams are all asking it questions at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bifrost is the enterprise AI gateway to use if you want enterprise controls (identity, budgets, guardrails, audit logs) without giving up the open-source core. It is written in Go, licensed Apache 2.0, and &lt;a href="https://docs.getbifrost.ai/benchmarking/getting-started" rel="noopener noreferrer"&gt;adds about 11 microseconds of overhead at 5,000 requests per second&lt;/a&gt;, the kind of number that matters once a gateway sits in front of every request your company makes. It also ships Bifrost Edge, an agent that extends the same governance to AI traffic on employee laptops, which none of the other four gateways in this list currently offer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://portkey.ai/" rel="noopener noreferrer"&gt;Portkey&lt;/a&gt; has the broadest adoption story in the open-source gateway space, a large community, and a deep enterprise tier. It is a reasonable pick for teams that want a mature product with a long feature list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://tyk.io/tyk-ai-studio/" rel="noopener noreferrer"&gt;Tyk AI Gateway&lt;/a&gt; is the choice for teams that are already running Tyk, or any API gateway, for everything else, and want LLM traffic to live under the same policy engine instead of a separate system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.f5.com/products/ai-gateway" rel="noopener noreferrer"&gt;F5 AI Gateway&lt;/a&gt; fits organizations that already run F5 for application delivery and security, and want AI traffic folded into that same console rather than standing up a new one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.cloudflare.com/products/ai-gateway/" rel="noopener noreferrer"&gt;Cloudflare AI Gateway&lt;/a&gt; is the natural fit for teams already running on Cloudflare, with caching, fallback routing, and usage analytics built into infrastructure they are already using.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What enterprises are actually looking for in an AI gateway
&lt;/h2&gt;

&lt;p&gt;The term "AI gateway" covers a wide range of products now, from simple request routers to full governance platforms. Before you compare any of the five below, it helps to know what questions each team in your organization is going to ask once the gateway is actually in production.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt; wants identity on every request. Not an API key shared across a team, but a way to know which application, which service account, or which person sent a given request, and to revoke that access without redeploying anything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Platform&lt;/strong&gt; wants the gateway to not be the thing that goes down. That means load balancing across providers and keys, automatic failover when a provider has an outage, and low enough overhead that adding a gateway does not show up as a latency regression in a dashboard somewhere.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finance&lt;/strong&gt; wants to know what a model is costing, broken down by team, by project, or by application, before the invoice arrives rather than after. Budgets that can be set in advance, not just logs you can grep through at the end of the month.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compliance&lt;/strong&gt; wants an audit trail that holds up. Every request, every response, every policy decision, recorded somewhere that cannot be quietly edited.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Everyone&lt;/strong&gt; wants deployment flexibility. A gateway that only runs as someone else's SaaS is a hard sell for a company with data residency requirements, a regulated industry, or an internal policy against sending production traffic through a third party's infrastructure. The ability to self-host, including fully air-gapped, is what separates a gateway you can put in front of all traffic from one you can only use for a side project.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how the five stack up at a glance, and where each one actually sits in your request path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnlfml4etvdsldtjz3rqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnlfml4etvdsldtjz3rqi.png" alt="AI Gateway" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A gateway is the one place security, platform, and finance all get to ask their questions, on every request, regardless of which team sent it or which model answered it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Comparison across the AI Gateways
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gateway&lt;/th&gt;
&lt;th&gt;Core&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Self-hosted / air-gapped&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bifrost&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;Apache 2.0 (enterprise tier)&lt;/td&gt;
&lt;td&gt;Yes, including air-gapped&lt;/td&gt;
&lt;td&gt;Teams that want full governance without losing the open-source core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portkey&lt;/td&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;MIT (enterprise tier)&lt;/td&gt;
&lt;td&gt;Yes, including hybrid and air-gapped&lt;/td&gt;
&lt;td&gt;Teams that want a mature, widely adopted gateway with a long feature list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tyk AI Gateway&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;Open source (enterprise tier)&lt;/td&gt;
&lt;td&gt;Yes, multi-cloud and on-prem&lt;/td&gt;
&lt;td&gt;Teams already standardized on Tyk or another API gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F5 AI Gateway&lt;/td&gt;
&lt;td&gt;Part of F5's platform&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;SaaS and hybrid now; air-gapped planned&lt;/td&gt;
&lt;td&gt;Teams already running F5 for application delivery and security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare AI Gateway&lt;/td&gt;
&lt;td&gt;Cloudflare's global network&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;SaaS only&lt;/td&gt;
&lt;td&gt;Teams already running on Cloudflare&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Bifrost
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.getbifrost.ai/" rel="noopener noreferrer"&gt;Bifrost&lt;/a&gt; is an open-source AI gateway built in Go, with an Apache 2.0 core and a paid enterprise tier for identity, high availability, and support. It gives you one OpenAI-compatible interface to over 10,000 models across more than 25 providers, including OpenAI, Anthropic, AWS Bedrock, Google Vertex, Azure, Gemini, Groq, Mistral, Cohere, and xAI, plus self-hosted options like vLLM, SGLang, and Ollama. Switching providers is a base URL change, not a rewrite.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ifq6j3xlf20kwcvxzye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ifq6j3xlf20kwcvxzye.png" alt="Bifrost image" width="799" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What makes Bifrost an enterprise gateway rather than just a fast router is how identity and governance are built in rather than bolted on. &lt;/p&gt;

&lt;p&gt;Every caller gets a &lt;a href="https://docs.getbifrost.ai/features/governance/virtual-keys" rel="noopener noreferrer"&gt;Virtual Key&lt;/a&gt;, and that key is where budgets, rate limits, and model permissions actually live, scoped by team, customer, or individual model, and enforced hierarchically so a sub-team's budget cannot quietly exceed its parent's. A key that runs out returns a clear 402 rather than silently failing or, worse, silently succeeding on someone else's bill.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqo7zdc8h47v7pi9rti47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqo7zdc8h47v7pi9rti47.png" alt="Virtual Keys" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bifrost also treats agent tooling as a first-class part of the gateway rather than a separate system. Its &lt;a href="https://docs.getbifrost.ai/mcp/overview" rel="noopener noreferrer"&gt;MCP Gateway&lt;/a&gt; sits in front of tool calls over STDIO, HTTP, and SSE, the same way the rest of Bifrost sits in front of model calls, with filtering at the client, request, and virtual-key level so an agent only ever sees the tools it is allowed to use. &lt;/p&gt;

&lt;p&gt;Its Code Mode feature has the model write a short script to orchestrate tools instead of loading every tool definition into context, and in a case study Maxim published, a 508-tool run across 16 MCP servers dropped from $377 to $29, a cut of about 92 percent, while keeping a 100 percent task pass rate. For any team running agents in production, that is the difference between a tool-calling bill that scales with usage and one that does not.&lt;/p&gt;

&lt;p&gt;On the operations side: adaptive load balancing with circuit breakers that recalculate provider weights from live latency and error rates, &lt;a href="https://docs.getbifrost.ai/features/fallbacks" rel="noopener noreferrer"&gt;automatic failover&lt;/a&gt; configured as a fallbacks array in the request payload rather than a separate piece of infrastructure, &lt;a href="https://docs.getbifrost.ai/features/semantic-caching" rel="noopener noreferrer"&gt;semantic caching&lt;/a&gt; backed by Redis, Qdrant, Weaviate, or Pinecone, and complexity-based routing that can send a simple query to a cheap model and a harder one to a stronger model automatically.&lt;/p&gt;

&lt;p&gt;For multi-region deployments, Bifrost's &lt;a href="https://docs.getbifrost.ai/enterprise/clustering" rel="noopener noreferrer"&gt;cluster mode&lt;/a&gt; runs as peer-to-peer nodes with no single leader, sharing traffic patterns, rate-limit state, and provider health over a gossip protocol, so there is no coordinator node to become a new single point of failure.&lt;/p&gt;

&lt;p&gt;On the compliance side: fine-grained RBAC with custom roles, SSO and SCIM 2.0 support for Okta, Microsoft Entra, Keycloak, Zitadel, Auth0, Google Workspace, and any generic OIDC provider, &lt;a href="https://docs.getbifrost.ai/enterprise/audit-logs" rel="noopener noreferrer"&gt;audit logs&lt;/a&gt; that are signed and tamper-resistant with export to S3 or GCS, and in-VPC or fully air-gapped deployment for teams that cannot send traffic, prompts, or telemetry anywhere outside their own network. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.getbifrost.ai/enterprise/guardrails" rel="noopener noreferrer"&gt;Guardrails&lt;/a&gt; come built in for content safety, PII, and secrets detection, and extend out to Presidio, Azure AI Language, AWS Bedrock Guardrails, Azure Content Safety, Google Model Armor, CrowdStrike AIDR, Check Point, Lakera Guard, and several others. Secrets themselves can live in AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault rather than sitting in Bifrost's own database, and budget or rate-limit breaches can alert Slack, Microsoft Teams, or PagerDuty. SOC 2 Type II, ISO 27001, HIPAA, and GDPR are all covered.&lt;/p&gt;

&lt;p&gt;One piece that is distinct from every other gateway on this list: Bifrost Edge, an agent that runs on employee laptops and routes AI traffic from desktop apps, browser AI, and coding agents through the same Gateway, so the governance does not stop at the server boundary. It is an add-on to the enterprise tier, priced per endpoint, but it addresses a real gap, the AI usage that never touches your backend at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unified OpenAI-compatible interface to 10,000+ models across 25+ providers, plus self-hosted vLLM, SGLang, and Ollama.&lt;/li&gt;
&lt;li&gt;Virtual Keys for identity, with hierarchical budgets, rate limits, and model scoping.&lt;/li&gt;
&lt;li&gt;MCP Gateway with tool filtering and Code Mode for agent tool calls.&lt;/li&gt;
&lt;li&gt;Adaptive load balancing with circuit breakers, automatic failover, and complexity-based routing&lt;/li&gt;
&lt;li&gt;Peer-to-peer cluster mode for multi-region high availability, with no single coordinator.
. Semantic caching and reasoning budget normalization across providers.&lt;/li&gt;
&lt;li&gt;RBAC, SSO and SCIM 2.0 (Okta, Entra, Keycloak, Zitadel, Auth0, Google Workspace, generic OIDC).&lt;/li&gt;
&lt;li&gt;Signed, tamper-resistant audit logs, exportable to S3 or GCS
Bifrost Edge for governing AI traffic on employee devices, not just the backend.&lt;/li&gt;
&lt;li&gt;In-VPC and fully air-gapped deployment, SOC 2 Type II, ISO 27001, HIPAA, GDPR.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Bifrost is built for enterprises running mission-critical AI workloads that require best-in-class performance, scalability, and reliability. It serves as a centralized AI gateway to route, govern, and secure all AI traffic across models and environments with ultra low latency. Bifrost unifies LLM gateway, MCP gateway, and Agents gateway capabilities into a single platform. Designed for regulated industries and strict enterprise requirements, it supports air-gapped deployments, VPC isolation, and on-prem infrastructure. It provides full control over data, access, and execution, along with robust security, policy enforcement, and governance capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Portkey
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://portkey.ai/features/ai-gateway" rel="noopener noreferrer"&gt;Portkey&lt;/a&gt; is an open-source gateway , with its core project sitting at close to 12,000 stars on GitHub. It started as a lightweight unified API across model providers and has large enterprise feature set: SSO through SAML 2.0, Okta, and Azure AD, role-based access control, PII anonymization, and certifications including SOC 2, ISO 27001, GDPR, and HIPAA.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgptnnqeqq3qdh9vd7zq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgptnnqeqq3qdh9vd7zq.png" alt="Portkey" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deployment is flexible across SaaS, hybrid, and fully air-gapped tiers, and the product line now includes its own MCP Gateway and an Agent Gateway aimed at the same agent-tooling problem Bifrost's MCP layer addresses.&lt;/p&gt;

&lt;p&gt;Portkey's recent "Gateway 2.0" release pushed more enterprise-grade functionality back into the open-source core rather than keeping it exclusive to the paid tier, which is a meaningful shift for teams that want to start on the free version and grow into it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unified API across a wide range of model providers, MIT-licensed core.&lt;/li&gt;
&lt;li&gt;SSO (SAML 2.0, Okta, Azure AD), RBAC, PII anonymization.&lt;/li&gt;
&lt;li&gt;SOC 2, ISO 27001, GDPR, and HIPAA certifications.&lt;/li&gt;
&lt;li&gt;MCP Gateway and Agent Gateway for agent tool traffic.&lt;/li&gt;
&lt;li&gt;Semantic caching.&lt;/li&gt;
&lt;li&gt;SaaS, hybrid, and air-gapped deployment tiers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that want a mature, broadly adopted gateway with a long enterprise feature list and an active community.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Tyk AI Gateway
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tyk.io/tyk-ai-studio/" rel="noopener noreferrer"&gt;Tyk&lt;/a&gt; is an API and AI gateway paired with Tyk AI Studio. It works with the same policy engine, access control, and multi-cloud deployment model that Tyk customers already use for REST, GraphQL, and gRPC APIs, extended to cover model providers too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8eo2rja4nuwt7d3to651.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8eo2rja4nuwt7d3to651.png" alt="Tyk AI Gateway" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A platform team that already runs Tyk for its API layer does not want a second, unrelated system just for LLM calls, with its own policy language and its own way of defining who can call what. Tyk's AI Gateway folds model traffic into the same Kubernetes-native deployment and policy engine, which is a real simplification for teams in that position. Enterprise users referenced in Tyk's own case studies include organizations like Zeiss, Domino's, and Deloitte, which gives some sense of the scale it is built to run at.&lt;/p&gt;

&lt;p&gt;Tyk supports multi-cloud, hybrid, and fully on-premises deployment, and because it grew out of a general-purpose API gateway, it handles a wider range of protocols than most AI-specific gateways, which matters if your LLM traffic is only one part of a broader API surface you need to govern consistently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unified policy engine shared with Tyk's broader API gateway product&lt;/li&gt;
&lt;li&gt;Kubernetes-native, with multi-cloud, hybrid, and on-premises deployment.&lt;/li&gt;
&lt;li&gt;REST, GraphQL, gRPC, and async protocol support alongside LLM traffic.&lt;/li&gt;
&lt;li&gt;Tyk AI Studio for building and managing AI-specific policies.&lt;/li&gt;
&lt;li&gt;Enterprise customers at meaningful scale (Zeiss, Domino's, Deloitte referenced in Tyk's own materials).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams already running Tyk, or evaluating a general-purpose API gateway, that want LLM traffic governed under the same system rather than a separate one.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. F5 AI Gateway
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.f5.com/products/ai-gateway" rel="noopener noreferrer"&gt;F5&lt;/a&gt; is best known for application delivery and security, the layer that sits in front of a company's web and API traffic generally. Its AI Gateway extends that same platform, now branded as F5's Application Delivery and Security Platform, to cover LLM traffic, combining a Model Gateway, an MCP Gateway, and AI-specific guardrails into one console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvkh9706iqt3yvtajjpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvkh9706iqt3yvtajjpo.png" alt="F5 AI Gateway" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The guardrails are where F5's existing security background shows up directly: detection for prompt injection and sensitive data leaks, built on the same inspection capabilities F5 already uses for web application traffic. On cost, F5 claims up to 60 percent reduction in token spend through a combination of routing, model tiering, and semantic caching, though as with any vendor-reported figure, it is worth asking what workload that number was measured against before treating it as a baseline for your own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model Gateway, MCP Gateway, and AI Guardrails unified in one console.&lt;/li&gt;
&lt;li&gt;Prompt injection and sensitive data leak detection, built on F5's existing security stack.&lt;/li&gt;
&lt;li&gt;Claimed up to 60 percent token spend reduction via routing, tiering, and semantic caching.&lt;/li&gt;
&lt;li&gt;SaaS, hybrid SaaS, and hybrid multicloud deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; organizations already running F5 for application delivery and security that want AI traffic governed in the same console rather than a new one.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Cloudflare AI Gateway
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.cloudflare.com/products/ai-gateway/" rel="noopener noreferrer"&gt;Cloudflare AI Gateway&lt;/a&gt; takes the same approach Cloudflare takes to everything else: put it on their global network and let teams configure it through a dashboard or API rather than infrastructure they have to run themselves. It sits in front of model providers as a control plane, with request caching to cut down on repeated calls, fallback routing so a request can move to a backup provider automatically, and unified billing that rolls every provider's cost into one bill instead of several.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkllf882re6f773lxo01n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkllf882re6f773lxo01n.png" alt="Cloudfare AI Gateway" width="799" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The appeal here is proximity to infrastructure a team already has. Routing rules, including which provider handles a request based on latency, cost, or availability, can be changed from the dashboard without a redeploy, and logs come with token counts, cost, duration, and request status attached, so usage analytics are available without wiring up a separate observability stack. Built-in guardrails aim to catch sensitive data before it reaches a provider, without additional setup. &lt;/p&gt;

&lt;p&gt;What it does not offer is a self-hosted or air-gapped deployment option. Cloudflare AI Gateway runs on Cloudflare's infrastructure, not yours, which is a real constraint for teams with strict data residency requirements, but less of one for teams already comfortable running production traffic through Cloudflare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request caching with configurable TTL to cut redundant provider calls.&lt;/li&gt;
&lt;li&gt;Fallback routing by latency, cost, or availability.&lt;/li&gt;
&lt;li&gt;Unified billing across every connected provider.&lt;/li&gt;
&lt;li&gt;Logs and usage analytics with token counts, cost, and duration per request.&lt;/li&gt;
&lt;li&gt;Built-in guardrails against sensitive data leakage, no extra setup required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams already running on Cloudflare who want an AI gateway that lives in the same dashboard as the rest of their infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one is right for you
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16ey4o97ols5uo1vsr6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16ey4o97ols5uo1vsr6g.png" alt="Enterprise AI Gateways" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are choosing based on what your team already runs, the decision mostly If you are choosing based on what your team already runs, the decision mostly makes itself: Tyk if you already run Tyk, F5 if you already run F5, Cloudflare if your traffic already sits behind Cloudflare. &lt;/p&gt;

&lt;p&gt;Those three gateways earn their place by not asking you to operate a second, unrelated system.&lt;/p&gt;

&lt;p&gt;If you are choosing without that constraint, the real question is what you are optimizing for. Portkey gives you the most mature, most broadly adopted option, with a long feature list and a large community behind it, though its pending acquisition is worth a direct conversation with the vendor. &lt;/p&gt;

&lt;p&gt;Bifrost gives you that same depth of governance (identity, budgets, guardrails, audit), fully self-hostable including air-gapped, without the lock-in that comes from a product tied to one company's broader platform. For a team that wants to own its enterprise AI gateway the way it owns the rest of its stack, rather than renting a slice of someone else's, that is the harder thing to find, and it is what Bifrost is built around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is an AI gateway, and how is it different from a model API?&lt;/strong&gt; &lt;br&gt;
A model API is how you send a request to one provider. An AI gateway sits in front of all of your model providers, handling identity, routing, budgets, and governance for every request, regardless of which model is actually serving it. You call the gateway once and it decides where the request actually goes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need an AI gateway if I only use one model provider?&lt;/strong&gt;&lt;br&gt;
Even with a single provider, a gateway gives you identity on every request, rate limits and budgets per team or application, and an audit trail, none of which a provider's own API gives you by default. It also means you are not rewriting application code the day you decide to add a second provider or a fallback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does "air-gapped deployment" actually mean for an AI gateway?&lt;/strong&gt; &lt;br&gt;
It means the gateway can run entirely inside your own network, with no outbound dependency on the vendor's infrastructure to function. For regulated industries or anyone with strict data residency requirements, this is often a hard requirement rather than a preference, which is why it is worth checking whether a vendor offers it today versus on a roadmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much latency does an AI gateway add?&lt;/strong&gt; &lt;br&gt;
It varies by implementation, but a well-built gateway should add a negligible amount. Bifrost, for example, is benchmarked at about 11 microseconds of overhead at 5,000 requests per second, in Maxim's own published testing. At that level, the gateway is not the thing slowing down your application; the model call itself is. Whatever gateway you evaluate, ask for the benchmark methodology, not just the headline number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can an AI gateway reduce my model costs?&lt;/strong&gt; &lt;br&gt;
Yes, through a few mechanisms: semantic caching avoids paying for a model call when a near-identical one was already answered, complexity-based routing sends simpler queries to cheaper models automatically, and having visibility into spend per team or application in the first place tends to surface waste that was previously invisible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do AI gateways work with agents and MCP tools, or just chat completions?&lt;/strong&gt; &lt;br&gt;
The better ones do. Agent tool calls are a growing share of LLM traffic, and treating them as ungoverned is the same mistake as treating early chat completions as ungoverned. Bifrost's MCP Gateway, Portkey's Agent Gateway, and F5's MCP Gateway are all explicit answers to that, filtering which tools an agent can reach and keeping tool traffic inside the same identity and audit model as everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Bifrost is open source and self-hostable, with a quickstart that gets an enterprise AI gateway running in front of your existing model traffic in minutes. Explore the &lt;a href="https://docs.getbifrost.ai/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; and the &lt;a href="https://github.com/maximhq/bifrost" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;, or &lt;a href="https://getmaxim.ai/bifrost/book-a-demo" rel="noopener noreferrer"&gt;book a Bifrost demo&lt;/a&gt; to see it running in front of your own model traffic.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>llm</category>
    </item>
    <item>
      <title>How SIMD works in Mojo</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Wed, 30 Sep 2026 18:50:31 +0000</pubDate>
      <link>https://dev.to/envitab/how-simd-works-in-mojo-2lfd</link>
      <guid>https://dev.to/envitab/how-simd-works-in-mojo-2lfd</guid>
      <description>&lt;p&gt;Let's say you want to know how many times the word &lt;strong&gt;research&lt;/strong&gt; appears in a document.&lt;/p&gt;

&lt;p&gt;You could upload the document to a program and read the file one byte at a time, compare each byte with the first letter of the word, and inspect the promising positions more closely. &lt;/p&gt;

&lt;p&gt;Now give the same program ten megabytes of text. Then a hundred megabytes. Then give it the papers, policy reports, interview transcripts, and field notes collected by an entire research team.&lt;/p&gt;

&lt;p&gt;The question has not changed. The computer is still asking, “Does the word start here?” The problem is how many times it has to ask.&lt;/p&gt;

&lt;p&gt;This is where SIMD starts to make sense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data" rel="noopener noreferrer"&gt;SIMD &lt;/a&gt;means single instruction, multiple data. Instead of comparing one byte and then the next one, the processor can compare several bytes together. The values sit in separate lanes of a vector, and the same operation runs across those lanes.&lt;/p&gt;

&lt;p&gt;So SIMD is not really a trick for making a loop move faster. It changes the shape of the work. A scalar loop moves through the data one value at a time. A SIMD operation looks across several values at once.&lt;/p&gt;

&lt;p&gt;That sounds simple, and the basic idea is simple. The interesting part begins when we try to use it in a real program.&lt;/p&gt;

&lt;p&gt;What happens when the file does not divide neatly into complete vectors? What happens when the first byte matches but the rest of the word does not? How do we distinguish research from researcher?&lt;/p&gt;

&lt;p&gt;And after making the code faster, how do we prove that it still returns the right answer?&lt;/p&gt;

&lt;p&gt;We will answer those questions by building &lt;a href="https://github.com/Tabintel/researchlens" rel="noopener noreferrer"&gt;ResearchLens&lt;/a&gt;, a &lt;a href="https://mojolang.org/" rel="noopener noreferrer"&gt;Mojo&lt;/a&gt; application that searches research text. It uses SIMD to find possible matches and a scalar verifier to decide which ones are real.&lt;/p&gt;

&lt;p&gt;Along the way, we will look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what a SIMD lane is&lt;/li&gt;
&lt;li&gt;why the vector width is part of a Mojo type&lt;/li&gt;
&lt;li&gt;how loads, comparisons, masks, and reductions fit together&lt;/li&gt;
&lt;li&gt;why a fast candidate is not automatically a correct match&lt;/li&gt;
&lt;li&gt;how to handle the final partial vector safely&lt;/li&gt;
&lt;li&gt;how to benchmark the result without making claims the data cannot support.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;You do not need a GPU for this tutorial. The SIMD hardware is already inside the CPU in a modern computer. We are going to show Mojo how to use it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The scalar loop
&lt;/h2&gt;

&lt;p&gt;Let us begin with the obvious version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_first_byte_scalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function is easy to trust. It checks one byte, updates the count, and moves on.&lt;/p&gt;

&lt;p&gt;If the input contains eight bytes, the loop asks the same small question eight times:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this byte equal to the first byte of the term?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important word is same. Every comparison is independent. The answer for byte 3 does not depend on the answer for byte 2.&lt;/p&gt;

&lt;p&gt;That is the kind of work SIMD is built for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj692tyxluipu9y83nnsl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj692tyxluipu9y83nnsl.png" alt="SIMD" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SIMD does not remove the work. It presents the work to the processor in a shape the hardware can execute more efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  A register with lanes
&lt;/h2&gt;

&lt;p&gt;A SIMD value is one fixed-width value divided into lanes. Every lane contains the same type of element, and an operation is applied element by element.&lt;/p&gt;

&lt;p&gt;For example, this value contains eight 16-bit integers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SIMD&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;squared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;squared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# [1, 4, 9, 16, 25, 36, 49, 64]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The multiplication looks ordinary, but its inputs are vector-shaped. Each lane is multiplied by the corresponding lane.&lt;/p&gt;

&lt;p&gt;The type tells us two things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SIMD&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DType.int16&lt;/code&gt; tells Mojo what each lane contains.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;8&lt;/code&gt; tells Mojo how many lanes the vector has.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this is one SIMD value containing eight 16-bit integers. Both values are compile-time parameters. By the time Mojo generates the machine code, it already knows the element type and the number of lanes.&lt;/p&gt;

&lt;p&gt;Mojo takes this model further than you might expect. Types such as &lt;code&gt;Float32&lt;/code&gt;, &lt;code&gt;Int64&lt;/code&gt;, and &lt;code&gt;UInt8&lt;/code&gt; are scalar aliases built on the same SIMD foundation. A scalar is a one-lane SIMD value.&lt;/p&gt;

&lt;p&gt;So Mojo does not give us one disconnected model for scalar numbers and another for vectors. It gives us one model that grows from one lane to many.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; One vector-shaped operation does not mean one clock cycle. The compiler may map a value to one hardware register, several registers, or another efficient representation. A wider type is not automatically faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The useful width depends on the element size, the target CPU, memory behaviour, and the rest of the kernel. Vector width is something to measure, not assume.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SIMD data path
&lt;/h2&gt;

&lt;p&gt;Most useful SIMD kernels are not one clever instruction. They are a short pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fii3pcuc46ogjdeg02jwu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fii3pcuc46ogjdeg02jwu.png" alt="SIMD" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The stages are separate for a reason:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Vector work&lt;/th&gt;
&lt;th&gt;Correctness rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load&lt;/td&gt;
&lt;td&gt;Contiguous bytes&lt;/td&gt;
&lt;td&gt;Read a complete vector&lt;/td&gt;
&lt;td&gt;Never read beyond the buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compare&lt;/td&gt;
&lt;td&gt;First target byte&lt;/td&gt;
&lt;td&gt;Produce one Boolean per lane&lt;/td&gt;
&lt;td&gt;Use an element-wise comparison&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;Boolean mask&lt;/td&gt;
&lt;td&gt;Map &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Keep the mask inspectable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reduce / Iterate&lt;/td&gt;
&lt;td&gt;Candidate lanes&lt;/td&gt;
&lt;td&gt;Combine lane values&lt;/td&gt;
&lt;td&gt;A candidate is not yet a complete match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify&lt;/td&gt;
&lt;td&gt;Candidate positions&lt;/td&gt;
&lt;td&gt;Check the full term&lt;/td&gt;
&lt;td&gt;Match the scalar reference&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the architecture we will use for the ResearchLens project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ResearchLens uses two algorithms
&lt;/h2&gt;

&lt;p&gt;ResearchLens has one job: count exact ASCII terms in UTF-8 research text.&lt;/p&gt;

&lt;p&gt;It uses two implementations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A scalar implementation that is intentionally obvious.&lt;/li&gt;
&lt;li&gt;A SIMD implementation that scans several possible starting positions at once.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The scalar version is not code that we keep around for nostalgia. It is the correctness oracle.&lt;/p&gt;

&lt;p&gt;If the SIMD version and the scalar version disagree, the SIMD version is wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jn76g7xk4cbcjuekv1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jn76g7xk4cbcjuekv1l.png" alt="The ResearchLens architecture" width="799" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The ResearchLens project has three areas:&lt;/p&gt;

&lt;h3&gt;
  
  
  Data layer
&lt;/h3&gt;

&lt;p&gt;Research papers, reports, transcripts, and field notes become a contiguous byte buffer.&lt;/p&gt;

&lt;p&gt;The first version is deliberately byte-oriented. UTF-8 text can contain multi-byte characters, but ASCII letters have the same byte representation inside UTF-8. That makes exact ASCII terms a useful, bounded starting point.&lt;/p&gt;

&lt;p&gt;It does not make ASCII boundary rules a complete Unicode tokenizer. We will say that plainly instead of hiding it in a footnote.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decision layer
&lt;/h3&gt;

&lt;p&gt;The SIMD scan checks the first byte of the target term across a complete vector.&lt;/p&gt;

&lt;p&gt;If the term is research, each byte equal to r becomes a candidate position.&lt;/p&gt;

&lt;p&gt;That narrows the search. It does not prove that the complete word exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification layer
&lt;/h3&gt;

&lt;p&gt;The verification layer includes a scalar function that checks every candidate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the complete term fit inside the buffer?&lt;/li&gt;
&lt;li&gt;Do all bytes match?&lt;/li&gt;
&lt;li&gt;Is the byte before the term a word character?&lt;/li&gt;
&lt;li&gt;Is the byte after the term a word character?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the program distinguishes research from researcher, or art from partial.&lt;/p&gt;

&lt;p&gt;SIMD narrows the search while the verifier owns the meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the verifier
&lt;/h2&gt;

&lt;p&gt;Before we write the vector loop, we need the function that defines a correct match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_ascii_word_byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;term_matches_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;is_ascii_word_byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;is_ascii_word_byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function is not vectorized, and that is fine because the SIMD scan should make the set of positions reaching this function much smaller than the full input. The verifier can then spend more work on the positions that deserve it.&lt;/p&gt;

&lt;p&gt;It's a common performance flow to use a cheap parallel filter, then apply a more expensive exact check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the scalar reference obvious
&lt;/h2&gt;

&lt;p&gt;Now we can write the reference implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_term_scalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;term_matches_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SIMD implementation has more moving parts, so we need a simple version to test it. &lt;/p&gt;

&lt;h3&gt;
  
  
  Load, compare, and inspect the mask
&lt;/h3&gt;

&lt;p&gt;Here is the centre of the SIMD path in the &lt;code&gt;src/analyzer.mojo&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;comptime&lt;/span&gt; &lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;

&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsafe_ptr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unsafe_load&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SIMD&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line in the code to look at is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unsafe_load&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It loads 32 adjacent bytes from &lt;code&gt;data&lt;/code&gt;, beginning at &lt;code&gt;offset&lt;/code&gt;, into one &lt;code&gt;SIMD[DType.uint8, 32]&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;The next line compares all 32 bytes with the first byte of the search term:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SIMD&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a SIMD mask. Each lane tells us whether the corresponding byte could be the beginning of a complete match.&lt;/p&gt;

&lt;p&gt;The name is deliberately honest. Mojo cannot prove that a raw pointer has 32 valid bytes available at this offset. &lt;/p&gt;

&lt;p&gt;The loop condition must establish that contract before the load happens.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;.eq()&lt;/code&gt; compares every lane with the first byte of the target.&lt;br&gt;
The explicit method matters. In Mojo, &lt;code&gt;==&lt;/code&gt; answers whether two complete SIMD values are equal and returns one Bool. The &lt;code&gt;element-wise .eq()&lt;/code&gt; method returns a Boolean SIMD mask, which is what we need here.&lt;/p&gt;

&lt;p&gt;Conceptually, the result looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;bytes:       m  o  j  o     r  e  s  e  a  r  c  h
first byte:  r  r  r  r  r  r  r  r  r  r  r  r  r
mask:        F  F  F  F  F  T  F  F  F  F  T  F  F
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The true lanes are candidate starting positions. We still have to verify the full term at those offsets.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete SIMD loop
&lt;/h2&gt;

&lt;p&gt;The complete function adds the verifier and the tail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;comptime&lt;/span&gt; &lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_term_simd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsafe_ptr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unsafe_load&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;SIMD&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;lane&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;term_matches_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lane&lt;/span&gt;
            &lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;term_matches_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop does not reduce the mask to one number because we need the actual candidate positions. It reads each Boolean lane and sends only the true positions to the verifier. &lt;/p&gt;

&lt;p&gt;Once the tests are stable and the benchmark tells us verification is the bottleneck, we have a precise place to improve.&lt;/p&gt;

&lt;h3&gt;
  
  
  The boundary is part of the algorithm
&lt;/h3&gt;

&lt;p&gt;A document length is rarely divisible by 32.&lt;/p&gt;

&lt;p&gt;Suppose 103 bytes are valid starting positions. The vector loop can safely process three complete 32-byte windows. That covers 96 positions. Seven remain.&lt;/p&gt;

&lt;p&gt;An unchecked load at offset 96 would try to read 32 bytes when only seven valid positions remain. That is not a performance issue. It is an invalid memory access.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fghdp1t8dqhbuikbmtvnu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fghdp1t8dqhbuikbmtvnu.png" alt="SIMD" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The loop guard makes the contract explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SIMD_WIDTH&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# A complete vector is safe here.
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# The remaining positions use the scalar path.
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above is because &lt;code&gt;limit&lt;/code&gt; is not greater than &lt;code&gt;len(data)&lt;/code&gt;, the first condition also ensures that the 32-byte &lt;code&gt;unsafe_load&lt;/code&gt; stays within the input buffer.&lt;/p&gt;

&lt;p&gt;The scalar tail is part of the algorithm. A vector can tell us that a lane contains r. It cannot, by itself, tell us that research is a complete word in the application’s tokenization model.&lt;br&gt;
The fast path and the correctness path need a clean boundary between them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Overview of the data shapes
&lt;/h2&gt;

&lt;p&gt;When you read a SIMD kernel, asking “which line is fast?” is usually less helpful than asking “what shape does the data have here?”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvu1cop8uey65056jhm8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvu1cop8uey65056jhm8f.png" alt="Data" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The ResearchLens path is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load:&lt;/strong&gt; 32 adjacent bytes become one typed vector.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare:&lt;/strong&gt; 32 bytes become 32 Boolean answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mask:&lt;/strong&gt; the answers become inspectable candidate lanes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce or iterate:&lt;/strong&gt; the vector result becomes a smaller scalar decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify:&lt;/strong&gt; a candidate position becomes a correct application-level match or a rejection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the mental model to keep. Each line changes the shape of the data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up the project in Mojo
&lt;/h2&gt;

&lt;p&gt;We have discussed how the SIMD path works. Now let us set up the complete project and run it.&lt;/p&gt;

&lt;p&gt;ResearchLens was built and tested with &lt;strong&gt;Mojo 1.0.0&lt;/strong&gt;. You can write Mojo in VS Code, Windsurf, or another editor that supports the Mojo extension. &lt;/p&gt;
&lt;h3&gt;
  
  
  Install Mojo with Pixi
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://mojolang.org/install/" rel="noopener noreferrer"&gt;official Mojo installation guide&lt;/a&gt; supports both &lt;code&gt;uv&lt;/code&gt; and Pixi for creating and managing a Mojo project. We will use Pixi because it creates a reproducible project environment and generates a lockfile for the dependencies.&lt;/p&gt;

&lt;p&gt;If Pixi is not already installed, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://pixi.sh/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart your terminal after the installation if the &lt;code&gt;pixi&lt;/code&gt; command is not immediately available.&lt;/p&gt;

&lt;p&gt;Now create the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pixi init researchlens &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; https://conda.modular.com/max/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; conda-forge

&lt;span class="nb"&gt;cd &lt;/span&gt;researchlens
pixi add &lt;span class="s2"&gt;"mojo==1.0.0"&lt;/span&gt;
pixi run mojo &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final command should print the installed Mojo version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mojo 1.0.0 (ed45d567)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The value in parentheses, &lt;code&gt;ed45d567&lt;/code&gt;, is the build identifier for this specific Mojo 1.0.0 build.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mojo may have a newer release by the time you read this. We are using version &lt;code&gt;1.0.0&lt;/code&gt; because that is the exact version used to build, test, and benchmark this project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the project files
&lt;/h3&gt;

&lt;p&gt;ResearchLens has a small structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;researchlens/
├── pixi.toml
├── pixi.lock
├── README.md
├── data/
│   └── sample.txt
├── src/
│   └── analyzer.mojo
└── tests/
    └── test_analyzer.mojo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the directories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; data src tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the source code to &lt;code&gt;src/analyzer.mojo&lt;/code&gt;, the tests to &lt;code&gt;tests/test_analyzer.mojo&lt;/code&gt;, and a small research-text sample to &lt;code&gt;data/sample.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The complete project is available in the &lt;a href="https://github.com/Tabintel/researchlens" rel="noopener noreferrer"&gt;ResearchLens repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure the Pixi tasks
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;pixi.toml&lt;/code&gt; and use the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[workspace]&lt;/span&gt;
&lt;span class="py"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Your Name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;channels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"https://conda.modular.com/max"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"conda-forge"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"researchlens"&lt;/span&gt;
&lt;span class="py"&gt;platforms&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"linux-64"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"linux-aarch64"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"osx-arm64"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.0"&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;mojo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"=&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="nn"&gt;[tasks]&lt;/span&gt;
&lt;span class="py"&gt;analyze&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mojo run src/analyzer.mojo"&lt;/span&gt;
&lt;span class="py"&gt;test&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mojo run -I src tests/test_analyzer.mojo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two tasks give us shorter commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pixi run &lt;span class="nb"&gt;test
&lt;/span&gt;pixi run analyze data/sample.txt research 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pinning the Mojo version matters. It means another engineer can clone the repository, install the same dependencies, and reproduce the environment we tested.&lt;/p&gt;

&lt;p&gt;Commit &lt;code&gt;pixi.lock&lt;/code&gt; to the repository as well. The configuration describes the environment, while the lockfile records the exact packages Pixi resolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the scalar and SIMD paths together
&lt;/h2&gt;

&lt;p&gt;A fast implementation is only useful if it returns the correct answer.&lt;/p&gt;

&lt;p&gt;Instead of testing the scalar and SIMD functions separately, we give both functions the same input and confirm that they return the same match count.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;tests/test_analyzer.mojo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;std.testing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;assert_equal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;analyzer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;count_term_scalar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count_term_simd&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;assert_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;count_term_scalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;assert_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;count_term_simd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;researcher research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A long prefix that crosses a vector tail: research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ResearchLens: all scalar/SIMD agreement tests passed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pixi run &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ResearchLens: all scalar/SIMD agreement tests passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;check&lt;/code&gt; is marked with &lt;code&gt;raises&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of the operations called inside &lt;code&gt;check&lt;/code&gt; may return an error. Mojo requires the function to make that possibility explicit.&lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;raises&lt;/code&gt;, the compiler reports that a function which may raise is being called from a context that cannot raise. We encountered this error while building ResearchLens, and adding &lt;code&gt;raises&lt;/code&gt; fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the analyzer
&lt;/h2&gt;

&lt;p&gt;With the tests passing, run ResearchLens against the sample corpus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pixi run analyze data/sample.txt research 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three arguments are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Argument&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data/sample.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The text corpus to search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;research&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The term to find&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The number of benchmark iterations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the machine used for this tutorial, the command produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bytes: 244
matches: 3
iterations: 100
scalar total ns: 64000
SIMD total ns: 19000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ResearchLens found three complete occurrences of &lt;code&gt;research&lt;/code&gt;. It then ran both implementations 100 times and measured their total execution time.&lt;/p&gt;

&lt;p&gt;For this run, the SIMD implementation was approximately &lt;strong&gt;3.37 times faster&lt;/strong&gt; than the scalar implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the executable
&lt;/h2&gt;

&lt;p&gt;So far, Pixi has been running the Mojo source file directly. We can also compile ResearchLens into a standalone executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pixi run mojo build src/analyzer.mojo &lt;span class="nt"&gt;-o&lt;/span&gt; researchlens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the compiled application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./researchlens data/sample.txt research 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the Apple Silicon machine used for this tutorial, Mojo produced an &lt;strong&gt;86 KB Mach-O arm64 file&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You do not need a GPU for this project. We are using CPU SIMD. The vector hardware is already inside a modern processor, and Mojo gives us a way to express work that can run across those vector units.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the complete application
&lt;/h2&gt;

&lt;p&gt;A vector width of 32 does not exactly mean a 32-times speedup.&lt;/p&gt;

&lt;p&gt;The program still has to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load the bytes from memory.&lt;/li&gt;
&lt;li&gt;Find candidate positions.&lt;/li&gt;
&lt;li&gt;Verify each candidate.&lt;/li&gt;
&lt;li&gt;Handle the final partial vector.&lt;/li&gt;
&lt;li&gt;Count the valid matches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The compiler may also divide a wide SIMD value into smaller hardware operations. The final performance depends on the processor, the data, the compiler, and the way the algorithm is written.&lt;/p&gt;

&lt;p&gt;That is why we benchmark the complete application instead of timing only the most impressive SIMD instruction.&lt;/p&gt;

&lt;p&gt;For a useful comparison:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the scalar and SIMD implementations on the same bytes.&lt;/li&gt;
&lt;li&gt;Confirm that they return the same match count.&lt;/li&gt;
&lt;li&gt;Keep compilation and one-time setup outside the timed section.&lt;/li&gt;
&lt;li&gt;Run enough iterations to reduce timing noise.&lt;/li&gt;
&lt;li&gt;Record the CPU, operating system, Mojo version, corpus size, search term, vector width, and iteration count.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here are the results from the completed ResearchLens project:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Corpus&lt;/th&gt;
&lt;th&gt;Iterations&lt;/th&gt;
&lt;th&gt;Matches&lt;/th&gt;
&lt;th&gt;Scalar total&lt;/th&gt;
&lt;th&gt;SIMD total&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;244 bytes&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;64,000 ns&lt;/td&gt;
&lt;td&gt;19,000 ns&lt;/td&gt;
&lt;td&gt;3.37×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,485,996 bytes&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;127,878&lt;/td&gt;
&lt;td&gt;2,745,454,000 ns&lt;/td&gt;
&lt;td&gt;856,933,000 ns&lt;/td&gt;
&lt;td&gt;3.20×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;104,857,746 bytes&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;1,278,753&lt;/td&gt;
&lt;td&gt;5,531,949,000 ns&lt;/td&gt;
&lt;td&gt;1,732,811,000 ns&lt;/td&gt;
&lt;td&gt;3.19×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 10 MB and 100 MB tests settled at almost the same ratio: approximately &lt;strong&gt;3.2 times faster&lt;/strong&gt; for this workload on this machine.&lt;/p&gt;

&lt;p&gt;That consistency is more useful than the result from the 244-byte sample. With such a small file, timer noise and fixed overhead account for a larger part of the measurement.&lt;/p&gt;

&lt;p&gt;The 100 MB result does not mean Mojo will make every program 3.19 times faster. It means this implementation measured 3.19 times faster with this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search term&lt;/li&gt;
&lt;li&gt;Corpus&lt;/li&gt;
&lt;li&gt;Vector width&lt;/li&gt;
&lt;li&gt;Mojo version&lt;/li&gt;
&lt;li&gt;Processor&lt;/li&gt;
&lt;li&gt;Number of iterations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Change any of those variables and the result may change.&lt;/p&gt;

&lt;h3&gt;
  
  
  The data also matters
&lt;/h3&gt;

&lt;p&gt;ResearchLens first uses SIMD to find bytes that could begin a match. It then sends those candidate positions to the scalar verifier.&lt;/p&gt;

&lt;p&gt;We can describe that relationship with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;candidate ratio =
    candidate starting bytes / valid starting positions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the search term begins with an uncommon byte, the SIMD filter can reject most positions cheaply.&lt;/p&gt;

&lt;p&gt;If it begins with a common byte, more positions reach the scalar verifier, and verification takes a larger share of the execution time.&lt;/p&gt;

&lt;p&gt;The value of the optimization therefore depends partly on the data being searched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;SIMD is useful when the same small operation must be applied to many independent values.&lt;/p&gt;

&lt;p&gt;Mojo makes the parts of that work visible in ordinary code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The element type&lt;/li&gt;
&lt;li&gt;The lane count&lt;/li&gt;
&lt;li&gt;The vector load&lt;/li&gt;
&lt;li&gt;The comparison mask&lt;/li&gt;
&lt;li&gt;The reduction&lt;/li&gt;
&lt;li&gt;The scalar verifier&lt;/li&gt;
&lt;li&gt;The tail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can inspect each part, test it, and measure it.&lt;/p&gt;

&lt;p&gt;ResearchLens is a small application, but the same pattern appears in delimiter scanning, byte classification, image processing, numerical transformations, filters, checksums, and many other workloads.&lt;/p&gt;

&lt;p&gt;The goal is not to vectorize everything.&lt;/p&gt;

&lt;p&gt;It is to notice when the computer is being asked the same small question millions of times.&lt;/p&gt;

&lt;p&gt;If you build your own version of ResearchLens, try changing the vector width or the distribution of the first byte and share in the comments.&lt;/p&gt;

&lt;p&gt;P.S: If you want to learn specifically about SIMD, read Mitchell Hashimoto's &lt;a href="https://mitchellh.com/writing/everyone-should-know-simd" rel="noopener noreferrer"&gt;article&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mojo</category>
      <category>ai</category>
      <category>devrel</category>
      <category>programming</category>
    </item>
    <item>
      <title>Mojo is an innovative, high-performance programming language designed for writing systems-level code for AI workloads. It also comes with a fire emoji file extension .🔥— literally.

Learn more about Mojo in this article : -)</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Wed, 30 Sep 2026 09:38:39 +0000</pubDate>
      <link>https://dev.to/envitab/mojo-is-an-innovative-high-performance-programming-language-designed-for-writing-systems-level-82b</link>
      <guid>https://dev.to/envitab/mojo-is-an-innovative-high-performance-programming-language-designed-for-writing-systems-level-82b</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/envitab/what-is-mojo-2oip" class="crayons-story__hidden-navigation-link"&gt;What is Mojo?&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/envitab" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F823697%2F01838b0b-d454-4382-ad1b-75d036ea50dd.jpg" alt="envitab profile" class="crayons-avatar__image" width="298" height="277"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/envitab" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ekemini Samuel
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ekemini Samuel
                
                
              
              &lt;div id="story-author-preview-content-4211924" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/envitab" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F823697%2F01838b0b-d454-4382-ad1b-75d036ea50dd.jpg" class="crayons-avatar__image" alt="" width="298" height="277"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ekemini Samuel&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/envitab/what-is-mojo-2oip" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 11&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/envitab/what-is-mojo-2oip" id="article-link-4211924"&gt;
          What is Mojo?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mojo"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mojo&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/software"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;software&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/envitab/what-is-mojo-2oip" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/envitab/what-is-mojo-2oip#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>performance</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>We Built a DevRel Knowledge Base</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Fri, 11 Sep 2026 11:28:29 +0000</pubDate>
      <link>https://dev.to/envitab/we-built-a-devrel-knowledge-base-2bjj</link>
      <guid>https://dev.to/envitab/we-built-a-devrel-knowledge-base-2bjj</guid>
      <description>&lt;p&gt;&lt;a href="https://advorel.thally.app/what-is-devrel" rel="noopener noreferrer"&gt;Developer Relations &amp;amp; Advocacy&lt;/a&gt; is often described as the work of helping developers understand and use a product.&lt;/p&gt;

&lt;p&gt;That description is correct, but there is more. In this blog, we will share what we learned while turning a community Developer Relations guide into a structured documentation site with Thally.&lt;/p&gt;

&lt;p&gt;A Developer Advocate might write a technical tutorial, deliver a conference talk, review an onboarding journey, support a community, build a sample application, or take recurring developer feedback back to a product team. The work connects people, products, education, and experience.&lt;/p&gt;

&lt;p&gt;This range is part of what makes Developer Relations exciting. It is also what makes the field difficult to learn.&lt;/p&gt;

&lt;p&gt;Useful knowledge exists across conference talks, community conversations, personal blogs, company playbooks, videos, and the experience of practitioners. Someone entering the field can find definitions easily, but finding a dependable path from understanding DevRel to doing the work is much harder.&lt;/p&gt;

&lt;p&gt;Those are some of the problems the DX Guide provides solutions for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we built the DX Guide
&lt;/h2&gt;

&lt;p&gt;The DX Guide is a community-built knowledge base for people learning, practising, or leading Developer Relations.&lt;/p&gt;

&lt;p&gt;We wanted to create a resource that did more than define common terms. It needed to help readers understand the work, apply practical frameworks, learn from real community examples, and find useful next steps.&lt;/p&gt;

&lt;p&gt;The DX Guide started in 2025 as a project within the &lt;a href="https://www.dxmentorship.com/" rel="noopener noreferrer"&gt;DXMentorship cohort&lt;/a&gt;. We built its &lt;a href="https://ekeminios.mintlify.app/" rel="noopener noreferrer"&gt;first version with Mintlify&lt;/a&gt;, and members of the DX community in that cohort contributed their knowledge and experience to the guide. That beginning shaped the project as a community resource. It was never meant to represent one person's view of Developer Relations.&lt;/p&gt;

&lt;p&gt;The first version gave us a valuable foundation. As the material grew, we also saw opportunities to improve how readers moved through it, how contributor work was credited, and how the documentation could be reviewed and maintained. That brought us to the next stage of the guide and, eventually, to Thally.&lt;/p&gt;

&lt;p&gt;The guide covers areas such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developer Advocacy&lt;/li&gt;
&lt;li&gt;Developer Experience&lt;/li&gt;
&lt;li&gt;technical content and documentation&lt;/li&gt;
&lt;li&gt;public speaking&lt;/li&gt;
&lt;li&gt;community building&lt;/li&gt;
&lt;li&gt;feedback collection&lt;/li&gt;
&lt;li&gt;measurement and reporting&lt;/li&gt;
&lt;li&gt;careers and professional development&lt;/li&gt;
&lt;li&gt;real examples and lessons from practitioners&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The aim is not to establish one permanent definition of DevRel. The field changes, and different organisations structure the work differently. The guide gives the community a shared place to document what people are learning and improve it through review and contribution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi4lny49d3mwvajabyd3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi4lny49d3mwvajabyd3s.png" alt="The DX Guide homepage, showing its navigation and welcome page" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we moved the documentation to Thally
&lt;/h2&gt;

&lt;p&gt;The original documentation contained useful material, but having content is not the same as having a dependable documentation experience.&lt;/p&gt;

&lt;p&gt;Some pages repeated their introductions. Navigation needed a clearer structure. Contributor examples needed context and attribution. Product references needed official links and recognisable identities. The site also needed a consistent visual system and a workflow the team could continue to maintain.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.linkedin.com/posts/ekeneeze_sync-hacks-build-ship-stay-in-sync-activity-7494082973487513600-d76v" rel="noopener noreferrer"&gt;SYNCHACK challenge&lt;/a&gt; gave us a practical reason to address those issues through Track 2, which focuses on launching or migrating a documentation project.&lt;/p&gt;

&lt;p&gt;We chose &lt;a href="https://thally.io/" rel="noopener noreferrer"&gt;Thally&lt;/a&gt; because we needed more than a place to display Markdown files. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fplt65ewgegz7fu42m0qa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fplt65ewgegz7fu42m0qa.png" alt="Thally" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We wanted a publishing foundation that could support structured navigation, reusable content, search, deployment, and agent-facing ways to discover the same source material.&lt;/p&gt;

&lt;p&gt;Our project now contains MDX pages organised into topic-based navigation groups. It includes practical guidance, contributor stories, event photographs, professional links, product resources, and a changelog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzzyc0hbgoesfhnx4d809.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzzyc0hbgoesfhnx4d809.png" alt="The Books page in the DX Guide" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Books page is one example of the broader goal. It is not an isolated list added to increase the number of pages. It gives readers a route from the guide into deeper learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration was only the beginning
&lt;/h2&gt;

&lt;p&gt;At first, documentation migration can look like a file-moving task.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Move the Markdown.&lt;br&gt;
Recreate the navigation. &lt;br&gt;
Add the logo. &lt;br&gt;
Deploy the site.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In practice, the more important work was editorial.&lt;/p&gt;

&lt;p&gt;We reviewed the guide page by page. We removed repeated introductions and duplicated sections. We replaced emoji headings with a consistent icon system. We revised indirect language so organisations and contributors were described clearly. We checked links, image context, headings, descriptions, and related-page paths.&lt;/p&gt;

&lt;p&gt;We also tried to make every example earn its place.&lt;/p&gt;

&lt;p&gt;A photograph is not useful simply because it looks good. It becomes useful when the surrounding text explains what a reader can learn from it without inventing details that were never verified.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5b6apx14ovtrzxizej3l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5b6apx14ovtrzxizej3l.jpg" alt="Joy Ndukwe speaking at DevFest" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Joy Ndukwe's DevFest photograph supports the guide's discussion of public speaking and community work. We use the information we can verify and avoid assigning an unconfirmed session title or outcome to the image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxnmmqwfwj960r3dersc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxnmmqwfwj960r3dersc.jpg" alt="Ekemini Samuel's presenting at a Mojo Africa Community event" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same principle applies to project and community examples. In the photo above, Ekemini Samuel is presenting a talk at Mojo Africa Community event. &lt;/p&gt;

&lt;p&gt;Real work makes the guide more concrete, but accurate context matters more than promotional language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing a technical knowledge base
&lt;/h2&gt;

&lt;p&gt;We wanted the DX Guide to feel technical without feeling cold.&lt;/p&gt;

&lt;p&gt;We selected JetBrains Mono as the primary typeface. It gives prose, headings, commands, and examples a consistent voice that feels familiar to developers. We paired it with a restrained colour system built around deep green, lime, and violet.&lt;/p&gt;

&lt;p&gt;The DX mark gives the guide a recognisable identity, while the interface keeps the documentation itself in focus.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The image below shows the &lt;a href="https://advorel.thally.app/linkedin-and-personal-branding" rel="noopener noreferrer"&gt;LinkedIn and Personal Branding&lt;/a&gt; chapter in the DX Guide&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2f9zdl4keu55qrq9s90p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2f9zdl4keu55qrq9s90p.png" alt="The LinkedIn and Personal Branding chapter in the DX Guide" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical challenge we did not expect
&lt;/h2&gt;

&lt;p&gt;The production deployment did not work perfectly on the first attempt.&lt;/p&gt;

&lt;p&gt;A build failed because the synchronized application expected runtime modules that were missing from the repository snapshot. The error appeared across documentation, search, API, and well-known routes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thally provides clear insights into the documentation deployment pipeline&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo0dhb8fwvxl13okgqybo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo0dhb8fwvxl13okgqybo.png" alt="Thally Deployment" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution was not to copy a quick runtime fix into the documentation project. &lt;a href="https://thally.io/" rel="noopener noreferrer"&gt;Thally's&lt;/a&gt; runtime and the authored documentation have different ownership. The repository received a runtime synchronization, and the related static-route tests were aligned with that version.&lt;/p&gt;

&lt;p&gt;After the update, the project checks, tests, and production build passed.&lt;/p&gt;

&lt;p&gt;This was one of our most useful lessons. Documentation teams need to understand where content responsibility ends and platform responsibility begins. A workaround in the wrong repository can solve today's build while creating a harder maintenance problem tomorrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Thally helped us do
&lt;/h2&gt;

&lt;p&gt;Thally gave the guide a structured route from authored content to published documentation.&lt;/p&gt;

&lt;p&gt;It helped us bring together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MDX content&lt;/li&gt;
&lt;li&gt;organised navigation&lt;/li&gt;
&lt;li&gt;reusable page components&lt;/li&gt;
&lt;li&gt;search and publishing surfaces&lt;/li&gt;
&lt;li&gt;deployment checks&lt;/li&gt;
&lt;li&gt;agent-facing documentation surfaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also gave us a practical environment for reviewing the documentation as a product rather than treating it as a folder of files.&lt;/p&gt;

&lt;p&gt;With the analytics, we have insights into the DX documentation, knowing what to improve as we grow&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fozzy9rvlebpv8qb3aaps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fozzy9rvlebpv8qb3aaps.png" alt="Analytics" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The workflow worked best when automation moved the work forward and people remained responsible for what was published.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we learned
&lt;/h2&gt;

&lt;p&gt;The biggest lesson was simple: documentation migration is also editorial work.&lt;/p&gt;

&lt;p&gt;A successful build does not automatically produce clear documentation. A large page count does not guarantee useful coverage. Search cannot repair an unclear information structure. A polished design cannot replace evidence and attribution.&lt;/p&gt;

&lt;p&gt;We learned to ask better questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is the reader trying to accomplish?&lt;/li&gt;
&lt;li&gt;Does this page provide a useful next action?&lt;/li&gt;
&lt;li&gt;Is this example supported by evidence?&lt;/li&gt;
&lt;li&gt;Have we distinguished verified facts from assumptions?&lt;/li&gt;
&lt;li&gt;Can another contributor understand and maintain this structure?&lt;/li&gt;
&lt;li&gt;Does the downloaded or deployed result behave like the preview?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also learned that a living guide needs visible maintenance. The changelog, contribution guide, repository history, and review process are part of the documentation experience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here is our &lt;a href="https://advorel.thally.app/changelog" rel="noopener noreferrer"&gt;Changelog&lt;/a&gt; in the DX documentation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faud4isrslszjq8dms5dn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faud4isrslszjq8dms5dn.png" alt="Changelog" width="799" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building with AI assistance
&lt;/h2&gt;

&lt;p&gt;We used AI assistance during parts of the research process. &lt;a href="//google.com"&gt;Google&lt;/a&gt;, &lt;a href="//chatgpt.com"&gt;ChatGPT&lt;/a&gt; and &lt;a href="//claude.com"&gt;Claude&lt;/a&gt; helped us inspect patterns, investigate errors, and support validation workflows as we worked through the documentation.&lt;/p&gt;

&lt;p&gt;We also used the &lt;a href="https://docs.thally.io/" rel="noopener noreferrer"&gt;Thally&lt;/a&gt; documentation to research the platform, confirm technical details, and understand the workflow required for the migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;The DX Guide is a living documentation.&lt;/p&gt;

&lt;p&gt;We want to continue improving the practical paths through the content, expanding contributor examples with permission, checking the guide's agent-facing surfaces, and making it easier for new community authors to participate.&lt;/p&gt;

&lt;p&gt;We would love to see people use the guide to understand a DevRel problem, complete meaningful work, and contribute a lesson that helps the next person.&lt;/p&gt;

&lt;p&gt;Explore the guide: &lt;a href="https://advorel.thally.app" rel="noopener noreferrer"&gt;advorel.thally.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a video guide of our DX documentation:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/FnK4Sk6oiNw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;What would make a community Developer Relations guide more useful in your day-to-day work? Please share in the comment!&lt;/p&gt;

&lt;p&gt;A big thank you to the SyncHack and Thally team! 🎉&lt;/p&gt;

&lt;p&gt;And thanks to &lt;a href="https://www.linkedin.com/in/joy-ndukwe?utm_source=share_via&amp;amp;utm_content=profile&amp;amp;utm_medium=member_ios" rel="noopener noreferrer"&gt;Joy&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/samuel-uzor98?utm_source=share_via&amp;amp;utm_content=profile&amp;amp;utm_medium=member_ios" rel="noopener noreferrer"&gt;Samuel&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/abimbola-adedotun?utm_source=share_via&amp;amp;utm_content=profile&amp;amp;utm_medium=member_ios" rel="noopener noreferrer"&gt;Adedotun&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/ekeminisamuel/" rel="noopener noreferrer"&gt;Ekemini&lt;/a&gt; for working together as we did the migration of the DX documentation to Thally.&lt;/p&gt;

&lt;p&gt;Thank you for reading! 💜&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>documentation</category>
      <category>thally</category>
      <category>devex</category>
    </item>
    <item>
      <title>Enterprise AI governance is not only about writing the rules. It is about enforcing them once AI traffic starts spreading across teams, applications, models, providers, and agents.

That is why an AI gateway matters, giving you a place to see the traffic,</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Tue, 01 Sep 2026 13:03:27 +0000</pubDate>
      <link>https://dev.to/envitab/enterprise-ai-governance-is-not-only-about-writing-the-rules-it-is-about-enforcing-them-once-ai-11f4</link>
      <guid>https://dev.to/envitab/enterprise-ai-governance-is-not-only-about-writing-the-rules-it-is-about-enforcing-them-once-ai-11f4</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/envitab/enterprise-ai-governance-governing-llm-traffic-at-scale-with-an-ai-gateway-564j" class="crayons-story__hidden-navigation-link"&gt;Enterprise AI Governance: Governing LLM Traffic at Scale with an AI Gateway&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/envitab" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F823697%2F01838b0b-d454-4382-ad1b-75d036ea50dd.jpg" alt="envitab profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/envitab" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ekemini Samuel
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ekemini Samuel
                
                
              
              &lt;div id="story-author-preview-content-4476543" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/envitab" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F823697%2F01838b0b-d454-4382-ad1b-75d036ea50dd.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ekemini Samuel&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/envitab/enterprise-ai-governance-governing-llm-traffic-at-scale-with-an-ai-gateway-564j" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/envitab/enterprise-ai-governance-governing-llm-traffic-at-scale-with-an-ai-gateway-564j" id="article-link-4476543"&gt;
          Enterprise AI Governance: Governing LLM Traffic at Scale with an AI Gateway
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aigateway"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aigateway&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/llm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;llm&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/envitab/enterprise-ai-governance-governing-llm-traffic-at-scale-with-an-ai-gateway-564j" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;10&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/envitab/enterprise-ai-governance-governing-llm-traffic-at-scale-with-an-ai-gateway-564j#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              4&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
      <category>security</category>
    </item>
    <item>
      <title>Enterprise AI Governance: Governing LLM Traffic at Scale with an AI Gateway</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Mon, 24 Aug 2026 14:58:04 +0000</pubDate>
      <link>https://dev.to/envitab/enterprise-ai-governance-governing-llm-traffic-at-scale-with-an-ai-gateway-564j</link>
      <guid>https://dev.to/envitab/enterprise-ai-governance-governing-llm-traffic-at-scale-with-an-ai-gateway-564j</guid>
      <description>&lt;p&gt;Your AI governance problem probably isn't a policy problem; it’s a routing one. &lt;/p&gt;

&lt;p&gt;Let’s paint a common scenario: Your team uses OpenAI, another adds Anthropic, then the product team connects another model. Then someone builds an agent with MCP tools. A few months later, the company has several AI applications, multiple providers, and a growing bill.&lt;/p&gt;

&lt;p&gt;The question is: who can use which model, how much can they spend, and what happens when a provider fails?&lt;/p&gt;

&lt;p&gt;That is when AI governance stops being a policy document and becomes an infrastructure problem.&lt;/p&gt;

&lt;p&gt;In this article, we'll look at why enterprise AI governance gets harder as LLM traffic grows, where an AI gateway fits into the architecture, and how &lt;a href="https://www.getmaxim.ai/bifrost" rel="noopener noreferrer"&gt;Bifrost&lt;/a&gt; can govern access, routing, budgets, rate limits, observability, and AI agent tools.&lt;/p&gt;

&lt;p&gt;Bifrost is an &lt;a href="https://github.com/maximhq/bifrost" rel="noopener noreferrer"&gt;open-source&lt;/a&gt; AI gateway built in Go by &lt;a href="https://www.getmaxim.ai/bifrost" rel="noopener noreferrer"&gt;Maxim AI&lt;/a&gt;. It sits between your applications and AI model providers, routing requests through a &lt;a href="https://docs.getbifrost.ai/overview" rel="noopener noreferrer"&gt;unified API&lt;/a&gt; instead of making each application manage every provider separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let’s get started&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why enterprise AI governance becomes an infrastructure problem
&lt;/h2&gt;

&lt;p&gt;The first generation of enterprise AI was mostly about choosing models. The problem is different now.&lt;/p&gt;

&lt;p&gt;LLM traffic is not like ordinary API traffic. Cost depends on tokens. Latency varies by model and provider. A single agent can make many model and tool calls. Providers have different limits and capabilities. Teams may want different models for different jobs.&lt;/p&gt;

&lt;p&gt;So the traffic itself becomes something the platform team needs to manage.&lt;/p&gt;

&lt;p&gt;At scale, the same request pattern repeats across applications, teams, providers, and agents. Governance gets difficult simply because the system grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem with governing AI in every application
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fupjx6lqy67qxti7dbgzx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fupjx6lqy67qxti7dbgzx.png" alt="AI gateway" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every AI request an enterprise makes needs an answer to questions like:  who is allowed to make it, which model or provider it's allowed to use, and whether anyone can find a record of it afterward. &lt;/p&gt;

&lt;p&gt;That's what "governing AI traffic" means in practice: not a policy document, but those questions getting answered correctly, every time, for every request.&lt;/p&gt;

&lt;p&gt;The first move might be to answer them inside each application. Give the app its own provider keys, its own retry logic, its own spending limits, its own logging. For one team, on one service, this works fine.&lt;/p&gt;

&lt;p&gt;It stops working once there are twenty teams doing it. Each one solves the same questions once, at different times, in a slightly different way, because nothing forces any two of them to do it the same way. &lt;/p&gt;

&lt;p&gt;So when a provider changes its API, or a new model gets approved, or security wants a new rule, that's not one change anymore. It's twenty changes, made by twenty people, on twenty schedules, and every application has quietly become part of the governance system whether it was built for that job or not.&lt;/p&gt;

&lt;p&gt;The limit is not the volume of traffic, but not having a single place where any of this policy lives. That's the gap an AI gateway is built to close.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI gateway as the control plane
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpkbahpwb9qbr2c526u8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpkbahpwb9qbr2c526u8v.png" alt="AI traffic" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A better way to go about handling different requests, as shown in the diagram above, is to route applications and agents through an AI gateway connected to the AI model providers.&lt;/p&gt;

&lt;p&gt;The gateway is the control plane where identity, access, routing, budgets, rate limits, guardrails, and observability can be applied consistently.&lt;/p&gt;

&lt;p&gt;It’s not simply having another proxy; rather, it’s having a place where policy meets the request while the request is happening.&lt;/p&gt;

&lt;p&gt;A typical request can move through:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9v133lj4moxcs3lg5pkm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9v133lj4moxcs3lg5pkm.png" alt="Request" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Identity:&lt;/strong&gt; who is making the request, usually a Virtual Key tied to an app, team, or agent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access policy:&lt;/strong&gt; which providers, models, and tools that identity is allowed to use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Budget and rate limit:&lt;/strong&gt; whether the request fits within the spending and volume limits set for it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Routing:&lt;/strong&gt; which provider and model should actually handle the request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guardrails:&lt;/strong&gt; a check for policy violations or sensitive data before anything reaches the user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Provider:&lt;/strong&gt; the model provider, like Groq, OpenAI, or Anthropic, that generates the response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;**Observability: **a record of the request, response, cost, and outcome for later audit.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a check fails, the request stops there, emphasizing the principle of one governed path, where the idea is to effectively manage traffic along its existing route.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should an AI gateway govern?
&lt;/h2&gt;

&lt;p&gt;When traffic passes through one layer, the governance questions become much easier to answer. You can start with something as simple as knowing who is making a request. That might be a user, an application, a team, or a &lt;a href="https://docs.getbifrost.ai/features/governance/virtual-keys?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Virtual Key&lt;/a&gt;. From there, you can decide what that caller is allowed to access and keep its usage separate from everyone else's.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3nkbs4klly6wocrcl9ta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3nkbs4klly6wocrcl9ta.png" alt="AI flow" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same applies to models and providers. Not every application needs access to everything. A support application might only need a few approved models, while an internal agent might need access to a different set.&lt;/p&gt;

&lt;p&gt;There is also the question of how much traffic each application should generate. Budgets can keep spending under control, while request and token limits prevent a single application from consuming more than its share. The &lt;a href="https://docs.getbifrost.ai/features/governance/budget-and-limits" rel="noopener noreferrer"&gt;Budget and Limits&lt;/a&gt; controls let those limits be applied at different levels.&lt;/p&gt;

&lt;p&gt;With several models and providers in the mix, the gateway also needs to decide where each request should go. &lt;a href="https://docs.getbifrost.ai/features/governance/routing" rel="noopener noreferrer"&gt;Routing&lt;/a&gt; can direct traffic to particular providers or models, distribute requests, and provide a fallback when something goes wrong.&lt;/p&gt;

&lt;p&gt;Some requests should never make it that far. Guardrails can inspect what is going into and coming out of a model and block requests that violate the rules.&lt;/p&gt;

&lt;p&gt;And after the request has been handled, you still need to understand what happened. How much traffic did an application generate? Which model handled it? How long did it take? Did a provider fail? Good observability turns those questions from guesses into things you can actually answer.&lt;/p&gt;

&lt;p&gt;That is what governance looks like at the traffic layer: knowing who is making the request, what they can do, how much they can use, where the request goes, and what happened along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents make the governance problem bigger
&lt;/h2&gt;

&lt;p&gt;As AI agents get more development and adoption, the governance challenge gets bigger. At first, the question was simple: "Can this application call GPT?" But for agents, that's just the start.&lt;/p&gt;

&lt;p&gt;These agents can do a lot more than just call a model. They can read files, query databases, search internal systems, use Git, and call other tools through the Multi-Channel Protocol. Because of this, governance needs to include control over tool access as well.&lt;/p&gt;

&lt;p&gt;For example, a research agent might only need to read files but shouldn’t be able to delete them. On the other hand, an engineering agent might need access to Git.&lt;/p&gt;

&lt;p&gt;Bifrost’s &lt;a href="https://docs.getbifrost.ai/features/governance/mcp-tools" rel="noopener noreferrer"&gt;MCP Tool Filtering&lt;/a&gt; lets you set up a Virtual Key. This key can explicitly allow certain clients and tools to be accessed. If you don’t set up any MCP configuration, the default is to deny access to everything; this keeps things secure.&lt;/p&gt;

&lt;p&gt;So instead of asking, "Which model can this application use?" we need to ask, "What can this AI system actually do?" This shift means we have to think more about how to govern these agents and what permissions they really need to work safely and effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost and reliability belong in the same layer
&lt;/h3&gt;

&lt;p&gt;The gateway is also where governance meets business economics. If one team's agent suddenly makes ten times as many requests. With gateway-level budgets and usage data, you can see which key, team, provider, or workflow caused the increase.&lt;/p&gt;

&lt;p&gt;The same layer can handle reliability. Bifrost's routing controls can distribute requests across configured provider or model paths, which also caters to any fallback behavior when a provider path is no longer available.&lt;/p&gt;

&lt;p&gt;Governance is not only about saying no. It also keeps approved traffic reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The governed AI traffic layer
&lt;/h2&gt;

&lt;p&gt;This is where &lt;a href="https://www.getmaxim.ai/bifrost" rel="noopener noreferrer"&gt;Bifrost&lt;/a&gt; comes in as an open-source, high-performance AI gateway. It gives applications a unified API and interface to connect across the AI model providers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fimye0gfg79hdl6lu33dv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fimye0gfg79hdl6lu33dv.png" alt="Bifrost website" width="799" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To learn more about the engineering story behind Bifrost, watch the &lt;a href="https://youtu.be/x7SNhZKKsRs" rel="noopener noreferrer"&gt;Runtime episode on YouTube&lt;/a&gt;. It covers why the team built Bifrost in Go, the unified API, routing and failover, observability, MCP, and the infrastructure decisions behind a high-throughput AI gateway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's put this into practice
&lt;/h3&gt;

&lt;p&gt;We built a &lt;strong&gt;Customer Support Assistant&lt;/strong&gt; using &lt;a href="http://Next.js" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; and &lt;a href="https://groq.com/" rel="noopener noreferrer"&gt;Groq&lt;/a&gt; for AI inference. The application does not call a provider directly. Its &lt;code&gt;baseURL&lt;/code&gt; points to Bifrost, and the Bifrost Virtual Key identifies the workload, with the API key credential entered in the Bifrost dashboard.&lt;/p&gt;

&lt;p&gt;The Customer Support application only sends chat requests through the AI gateway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi6l33vn1kkvpzagjjc1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi6l33vn1kkvpzagjjc1x.png" alt="Customer Support Assistant" width="799" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, the governance policy includes a request and token limit, a monthly budget, and an approved model list configured in a &lt;a href="https://docs.getbifrost.ai/deployment-guides/config-json" rel="noopener noreferrer"&gt;config.json&lt;/a&gt; file and also managed in the Bifrost dashboard. &lt;/p&gt;

&lt;p&gt;The Bifrost Gateway dashboard runs at &lt;code&gt;http://localhost:8080&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtiuqo5bfaytdxlwjmm1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtiuqo5bfaytdxlwjmm1.png" alt="Bifrost Gateway dashboard" width="799" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Virtual Key Rankings attribute requests, tokens, and cost to the Customer Support Assistant workload.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkf1w0puvzjufl7om0bku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkf1w0puvzjufl7om0bku.png" alt="Virtual Key" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the image above, the &lt;strong&gt;enterprise-support-assistant&lt;/strong&gt; Virtual Key accounts for all 35 attributed requests and $0.0019 spent. This observability enforces policy and gives finance and platform teams a shared view of LLM usage.&lt;/p&gt;

&lt;p&gt;The Virtual Key Rankings tab also shows how Bifrost attributes traffic back to each workload. In this demo, every request from the Customer Support Assistant arrives with the Virtual Key, and Bifrost maps that key to the workload name, rolls up requests, tokens, and costs in one place.&lt;/p&gt;

&lt;p&gt;From the LLM logs tab in the dashboard, we can view total requests, success rate, average latency, total tokens, and cost, along with a log and timestamp for every request the application sent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx2a96l14untnmo12e0r0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx2a96l14untnmo12e0r0.png" alt="LLM" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An example of a policy block is the  HTTP 429 code when the rate limit has been exceeded. This enforcement is done from the AI gateway before the request is sent to the AI provider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyngffmqb944dr9nku9p9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyngffmqb944dr9nku9p9.png" alt="Application" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When an AI model is allowed in the Bifrost configuration, it shows an HTTP 403 message, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyc3bf20zzv7nz15m7xu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyc3bf20zzv7nz15m7xu8.png" alt="Application" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The LLM logs also show the 403 error in the dashboard, with the Groq logo displayed like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh4f2hartcpt24ej0mmdg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh4f2hartcpt24ej0mmdg.png" alt="Data" width="800" height="734"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also view the AI model rankings in the Bifrost dashboard and export the data when needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbfpeju5i0mcvnrq3ms66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbfpeju5i0mcvnrq3ms66.png" alt="Dashboard" width="799" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the Customer support application use case, this shows which AI models have been used for the responses.&lt;/p&gt;

&lt;p&gt;From the image, we can see that there are two models in use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;openai/gpt-oss-20b&lt;/li&gt;
&lt;li&gt;openai/gpt-4o-mini&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also shows the requests on each model and the success rates, tokens used, costs, average latency, and throughput. This gives more insights into the AI models, which help with better understanding and decision-making.&lt;/p&gt;

&lt;p&gt;As shown in the Customer support application above, Enterprise governance is enforced centrally, in the request path. The  LLM traffic flows through one gateway; budgets and limits control growth; model and tool access can be restricted; routing can keep approved traffic moving; and requests remain traceable.&lt;/p&gt;

&lt;p&gt;The LLM provider (for instance, OpenAI, Anthropic) the application needs is defined through a &lt;a href="https://docs.getbifrost.ai/quickstart/gateway/provider-configuration" rel="noopener noreferrer"&gt;provider configuration&lt;/a&gt;. Then the application gets its own identity with a &lt;a href="https://docs.getbifrost.ai/features/governance/virtual-keys" rel="noopener noreferrer"&gt;Virtual Key&lt;/a&gt;, which gives a clear boundary for access and usage.&lt;/p&gt;

&lt;p&gt;From there, you can put required limits around the traffic. The &lt;a href="https://docs.getbifrost.ai/features/governance/budget-and-limits" rel="noopener noreferrer"&gt;budgets and rate limits&lt;/a&gt; help keep an application or team from using more than it should.&lt;/p&gt;

&lt;p&gt;As the system grows and you add more models or providers, and introduce routing so requests can go to the right place, with fallback when an LLM provider is unavailable.&lt;/p&gt;

&lt;p&gt;And once the application becomes an agent, &lt;a href="https://docs.getbifrost.ai/features/governance/mcp-tools" rel="noopener noreferrer"&gt;MCP Tool Filtering&lt;/a&gt; lets you decide which tools that agent can actually access.&lt;/p&gt;

&lt;p&gt;You can get started with one application and one provider, then add the controls as the traffic and the system grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Enterprise AI governance is not only about writing the rules. It is about enforcing them once AI traffic starts spreading across teams, applications, models, providers, and agents.&lt;/p&gt;

&lt;p&gt;That is why an AI gateway matters, giving you a place to see the traffic, apply policies, control access and spend, route requests, govern agent tools, and keep a record of what happened.&lt;/p&gt;

&lt;p&gt;Instead of asking every application to solve these problems on its own, you can handle them at the layer every request already passes through, and your organization can scale AI securely without slowing innovation.&lt;/p&gt;

&lt;p&gt;To get started with Bifrost, check out the &lt;a href="https://docs.getbifrost.ai/overview" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aigateway</category>
      <category>llm</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>What is Mojo?</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Tue, 11 Aug 2026 19:45:23 +0000</pubDate>
      <link>https://dev.to/envitab/what-is-mojo-2oip</link>
      <guid>https://dev.to/envitab/what-is-mojo-2oip</guid>
      <description>&lt;p&gt;The &lt;a href="https://www.collinsdictionary.com/dictionary/english/mojo" rel="noopener noreferrer"&gt;Collins Dictionary&lt;/a&gt; defines Mojo as personal power or influence over people. Other definitions describe it as confidence, energy, self-belief, or the ability to make things happen.&lt;/p&gt;

&lt;p&gt;Mojo is an interesting name for a programming language, and that was the first thing that made me curios about Mojo when I discovered it while doing some research. &lt;/p&gt;

&lt;p&gt;In this article we are referring to the &lt;a href="https://mojolang.org/" rel="noopener noreferrer"&gt;Mojo programming language&lt;/a&gt; developed by &lt;a href="https://www.nondot.org/sabre/" rel="noopener noreferrer"&gt;Chris Lattner&lt;/a&gt; and the &lt;a href="https://www.modular.com/" rel="noopener noreferrer"&gt;Modular&lt;/a&gt; team. Mojo was &lt;a href="https://www.modular.com/blog/mojo-its-finally-here" rel="noopener noreferrer"&gt;launched&lt;/a&gt; on the 2nd of May 2023, and celebrated it's 3rd birthday this year.&lt;/p&gt;

&lt;p&gt;If you've heard about Mojo (or not) but aren't quite sure what it is, or why people are excited about it, this article is for you.&lt;/p&gt;

&lt;p&gt;Let's begin!&lt;/p&gt;

&lt;h2&gt;
  
  
  Mojo (the programming language)
&lt;/h2&gt;

&lt;p&gt;The Modular team defines their &lt;a href="https://mojolang.org/docs/vision/" rel="noopener noreferrer"&gt;vision for Mojo&lt;/a&gt; like so:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our vision for Mojo is to be the one programming language developers need to target diverse hardware—CPUs, GPUs, and other accelerators—using Python's intuitive syntax combined with modern systems programming capabilities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a while now, Software engineers building AI systems had to choose to either code in Python for simplicity or in C++ and CUDA for performance. With Mojo, those trade-offs don't have to be made.&lt;/p&gt;

&lt;p&gt;In simple terms, Mojo is a high performance Python superset programming language powering the next generation of &lt;a href="https://www.ibm.com/think/topics/ai-infrastructure" rel="noopener noreferrer"&gt;AI infrastructure&lt;/a&gt; and making &lt;a href="https://developers.redhat.com/articles/2024/08/07/what-gpu-programming" rel="noopener noreferrer"&gt;GPU programming&lt;/a&gt; accessible to everybody. &lt;/p&gt;

&lt;p&gt;This aligns with Modular's mission:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Enable AI to be used by anyone, anywhere&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9teub2p2lvoysvhhkc4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9teub2p2lvoysvhhkc4o.png" alt="Modular" width="799" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mojo is an innovative, high-performance programming language designed for writing systems-level code for AI workloads. It also comes with a fire emoji file extension &lt;code&gt;.🔥&lt;/code&gt;— literally.&lt;/p&gt;

&lt;p&gt;You can learn more about GPU Programming with Mojo in this video:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/-VsP4kT6DjA"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;💡 The easiest way to think about Mojo is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mojo keeps what software engineers love about Python while making it capable of building the next generation of AI infrastructure, and giving the engineers more control over how their programs run.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Mojo?
&lt;/h2&gt;

&lt;p&gt;Today, millions of people use AI every day.&lt;/p&gt;

&lt;p&gt;Whether it's ChatGPT helping someone write an email or designing a flyer, Windsurf or Claude Code assisting software engineers, AI is gradually becoming part of everyday life.&lt;/p&gt;

&lt;p&gt;Just as social media became mainstream over the last two decades, AI is becoming part of how we work, learn, communicate, and build. Which leads to an important question: What powers all of these AI systems?&lt;/p&gt;

&lt;p&gt;Behind every chatbot, recommendation engine, image generator, AI Agent or autonomous system is an enormous amount of infrastructure working together.&lt;/p&gt;

&lt;p&gt;There are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Massive data centers filled with GPUs.&lt;/li&gt;
&lt;li&gt;Servers processing billions of requests.&lt;/li&gt;
&lt;li&gt;Frameworks serving AI models in real time.&lt;/li&gt;
&lt;li&gt;Programming languages that tell all of this hardware exactly what to do.&lt;/li&gt;
&lt;li&gt;People across different fields of work collaborating from different locations in the world.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is AI infrastructure, the people, software, and hardware foundation that makes AI technology possible. &lt;/p&gt;

&lt;p&gt;As AI continues to grow, building this infrastructure efficiently becomes just as important as building smarter models. &lt;/p&gt;

&lt;p&gt;Beyond the intelligence of the AI models, we have to consider how efficiently and quickly we can train and deploy the models to fully utilize AI hardware.&lt;/p&gt;

&lt;p&gt;Achieving this requires new tools and frameworks such as Mojo and &lt;a href="https://docs.modular.com/max/intro/" rel="noopener noreferrer"&gt;MAX&lt;/a&gt; (Modular Accelerated Xecution).&lt;/p&gt;

&lt;p&gt;Mojo is a programming language designed for AI technology. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs18ch2r0ilovb3t3gs1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs18ch2r0ilovb3t3gs1h.png" alt="Mojo" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was created by Modular to make it easier for engineers to build software that takes full advantage of modern AI hardware; TPUs, NPUs, GPUs, like the &lt;a href="https://www.nvidia.com/en-us/data-center/h100/" rel="noopener noreferrer"&gt;NVIDIA H100&lt;/a&gt;, while keeping the simplicity that made &lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python&lt;/a&gt; one of the world's most &lt;a href="https://survey.stackoverflow.co/2025/technology" rel="noopener noreferrer"&gt;popular&lt;/a&gt; programming languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here's an example of a &lt;a href="https://www.modular.com/blog/understanding-simd-infinite-complexity-of-trivial-problems" rel="noopener noreferrer"&gt;SIMD-vectorized&lt;/a&gt; kernel squaring array elements in place in Mojo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SIMD-vectorized kernel squaring array elements in place.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mojo_square_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PythonObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;comptime&lt;/span&gt; &lt;span class="n"&gt;simd_width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;simd_width_of&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;array_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unsafe_get_as_pointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;vectorize&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;simd_width&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;array_obj&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need to understand every line of this example yet. The important thing is to see what Mojo is trying to make possible: a programming experience that remains approachable while giving developers much more control over how their code interacts with the underlying hardware.&lt;/p&gt;

&lt;p&gt;Then we have &lt;a href="https://www.modular.com/blog/getting-started-with-max-developer-edition" rel="noopener noreferrer"&gt;MAX&lt;/a&gt;, which is Modular's AI framework, designed specifically around high-performance AI inference and deployment. It provides the tools needed to serve generative AI models and deploy them efficiently across different environments, while Mojo provides the programming language and systems-level capabilities underneath the stack.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can think about it simply: Mojo gives you the programming language. MAX gives you the framework for running AI efficiently.&lt;br&gt;
Together, they are part of Modular's broader vision for building a more unified AI software stack.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;MAX is the framework that is natively integrated with Mojo, and is designed specifically for inference rather than training like PyTorch. It delivers incredible performance for serving &lt;a href="https://en.wikipedia.org/wiki/Generative_AI" rel="noopener noreferrer"&gt;GenAI models&lt;/a&gt;, deploying and scaling with ease.&lt;/p&gt;

&lt;p&gt;You can learn more about MAX in the video by Chris Lattner:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Tlncshs047Y"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Together, Mojo and MAX form an important part of Modular's approach to building modern AI infrastructure. And the story around Modular itself has recently entered a new chapter.&lt;/p&gt;

&lt;p&gt;On July 29, 2026, Qualcomm announced that it had completed its acquisition of Modular. &lt;a href="https://www.modular.com/blog/qualcomm-completes-acquisition-of-modular" rel="noopener noreferrer"&gt;Modular is now a Qualcomm company&lt;/a&gt;, while Mojo, MAX, and Modular Cloud continue as products and brands. According to Modular and Qualcomm, the acquisition is intended to combine Modular's AI-native software platform with Qualcomm's high-performance, energy-efficient computing platforms and expand the reach of Mojo and MAX across CPUs, GPUs, NPUs, and other hardware architectures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxiv910p0gf4w89c1gqnm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxiv910p0gf4w89c1gqnm.png" alt="Modular x Qualcomm" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is an important development for anyone building with Mojo. The technology is no longer being built only by an independent AI infrastructure startup. It is now part of a much larger computing company, while the Modular team continues its work on the software stack.&lt;/p&gt;

&lt;p&gt;And for me, that makes the question even more interesting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why did Modular decide that the AI industry needed a new programming language in the first place? We will answer this in the article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where did Mojo come from?
&lt;/h2&gt;

&lt;p&gt;To understand the origins of Mojo, we must first explore the problem that Modular aimed to solve. Founded in 2022 by &lt;a href="https://www.nondot.org/sabre/" rel="noopener noreferrer"&gt;Chris Lattner&lt;/a&gt; and &lt;a href="https://www.timdavis.com/about" rel="noopener noreferrer"&gt;Tim Davis&lt;/a&gt;, Modular sought to create a more unified, efficient, and user-friendly AI infrastructure across various types of hardware.&lt;/p&gt;

&lt;p&gt;As the team delved into this challenge, they discovered that the programming layer itself contributed to the issue. AI developers commonly use Python for its productivity and ease of use. However, when they needed to optimize performance and gain closer access to hardware like CPUs and GPUs, they were often forced to switch to lower-level languages such as &lt;a href="https://en.wikipedia.org/wiki/C%2B%2B" rel="noopener noreferrer"&gt;C++&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/CUDA" rel="noopener noreferrer"&gt;CUDA&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This duality created a productivity problem in the AI industry, as developers worked with different tools for performance and ease of use. Modular decided to address this by rethinking the programming language altogether.&lt;/p&gt;

&lt;p&gt;What if developers could retain the familiarity and productivity of Python while also gaining the performance and hardware control necessary to construct modern AI infrastructure? This innovative concept led to the development of Mojo.&lt;/p&gt;

&lt;p&gt;The thought process behind Mojo is strongly influenced by Chris Lattner, who has a remarkable background in the technology field. He is well-known for creating &lt;a href="https://en.wikipedia.org/wiki/LLVM" rel="noopener noreferrer"&gt;LLVM&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Clang" rel="noopener noreferrer"&gt;Clang&lt;/a&gt;, which are foundational components of modern compiler infrastructure. He also created the &lt;a href="https://en.wikipedia.org/wiki/Chris_Lattner" rel="noopener noreferrer"&gt;Swift&lt;/a&gt; programming language, used by millions of Apple's iOS devices.&lt;/p&gt;

&lt;p&gt;Mojo was developed as part of Modular's broader effort to making AI and AI infrastructure accessible, bringing together a team with deep experience in programming languages, compilers, systems, and modern hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to learn Mojo
&lt;/h2&gt;

&lt;p&gt;As with every skill and programming language, it starts with &lt;em&gt;Why&lt;/em&gt;.&lt;br&gt;
Why do you want to learn a new programming language? If you've read this far into the article, probably you have an interest in the Mojo Programming language, that's great!&lt;/p&gt;

&lt;p&gt;The Modular team is currently running a Mojo 101 series, which started on the 9th of July (my birthday)😎&lt;/p&gt;

&lt;p&gt;It's a great way to learn the basics of Mojo and how to get started building with Mojo. Check out the Mojo 101 series here:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/1Jqp0BheoEQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Thanks to the Modular team for this series! 😎&lt;/p&gt;

&lt;p&gt;There's also a talk by Chris Lattner on how Mojo &amp;amp; MAX contributes to a modern AI-first software development ecosystem&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Kw2FLI7LUGo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You can explore the official &lt;a href="https://docs.modular.com/mojo/manual/" rel="noopener noreferrer"&gt;Mojo documentation&lt;/a&gt; and start experimenting with the language yourself. &lt;/p&gt;

&lt;p&gt;You don't need to understand everything before you start, just get started, one day at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mojo Africa
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://x.com/mojoafricahq" rel="noopener noreferrer"&gt;Mojo Africa&lt;/a&gt; is a platform and community, where we are building the future of AI infrastructure in Africa, and educating on Mojo, the programming language for AI, GPUs, and MAX, the next-generation AI framework.&lt;/p&gt;

&lt;p&gt;We launched in &lt;a href="https://www.linkedin.com/posts/ekeminisamuel_mojoafrica-mojo-africantech-activity-7356759868394942465-yBdA/" rel="noopener noreferrer"&gt;March 2025&lt;/a&gt;, and in 16 months, we've had two Mojo Africa meetup events, with sponsorship from the Modular team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvk8geokdmveprzlop2gi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvk8geokdmveprzlop2gi.jpg" alt="Ekemini Samuel" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In August 2025, we had our &lt;a href="https://x.com/realekemini/status/1954248328645906660?s=46" rel="noopener noreferrer"&gt;first Mojo Africa meetup&lt;/a&gt;, where we introduced Mojo to the community.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fio5dda2x0m6zx550fltr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fio5dda2x0m6zx550fltr.jpg" alt="Mojo Africa meetup" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We had our second &lt;a href="https://www.linkedin.com/posts/ekeminisamuel_mojo-max-ai-ugcPost-7456387262797271040-NRQ5/?utm_source=share&amp;amp;utm_medium=member_ios&amp;amp;rcm=ACoAADHtL_wBfJuNuOfTtHAaXdNww5h3ABDyfao" rel="noopener noreferrer"&gt;Mojo Africa meetup in May 2026&lt;/a&gt;, where we learned and discussed the Role of Mojo and Max in building and scaling AI infrastructure. Great energy at the Mojo Africa meetup! ✨&lt;/p&gt;

&lt;p&gt;Mojo Africa is about more than teaching Africans about Mojo. We want to help redefine the narrative.&lt;/p&gt;

&lt;p&gt;Africa should not only consume AI and technology. We should also be actively contributing, building AI projects, developing AI infrastructure, and creating the technologies that will shape the future.&lt;/p&gt;

&lt;p&gt;We are building Mojo Africa to create an environment where Africans can learn, experiment, contribute, and build the AI technology and infrastructure of the future. And it starts with Mojo.&lt;/p&gt;

&lt;p&gt;That is the vision. &lt;a href="https://discord.gg/CKVMgvTngC" rel="noopener noreferrer"&gt;Join the community&lt;/a&gt;, more updates are coming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Now?
&lt;/h2&gt;

&lt;p&gt;If there was ever a time to pay attention to Mojo and AI infrastructure, I believe it is now.&lt;/p&gt;

&lt;p&gt;AI is moving incredibly fast, and the conversation is gradually shifting from simply building better models to figuring out how we can run those models efficiently, across different hardware and at much larger scale. &lt;/p&gt;

&lt;p&gt;Imagine a company collecting millions of data points from sensors, devices, or other systems. They may want to process that data, run models against it, and generate insights in real time. The performance of the software processing that information can become just as important as the model itself. Mojo is built to excel at use cases like this.&lt;/p&gt;

&lt;p&gt;Modular has also reached some major milestones.&lt;/p&gt;

&lt;p&gt;In September 2025, Modular raised &lt;a href="https://www.modular.com/blog/modular-raises-250m-to-scale-ais-unified-compute-layer" rel="noopener noreferrer"&gt;$250 million in funding&lt;/a&gt;, bringing its total capital raised to $380 million and its valuation to $1.6 billion. Then, in July 2026, &lt;a href="https://www.qualcomm.com/news/releases/2026/07/qualcomm-completes-acquisition-of-modular" rel="noopener noreferrer"&gt;Qualcomm completed its acquisition of Modular&lt;/a&gt;, bringing the company, Mojo, and MAX into the Qualcomm ecosystem, with Chris Lattner taking on the role of EVP of Advanced AI Software and Platforms and Tim Davis becoming SVP and GM of Modular.&lt;/p&gt;

&lt;p&gt;And now, another major milestone has arrived. Today, 11th August 2026 as I write and publish this article, &lt;a href="https://www.modular.com/blog/modular-26-5-mojo-1-0-is-here" rel="noopener noreferrer"&gt;Mojo 1.0 is now officially here&lt;/a&gt; 🔥, Modular announced it today! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2bgdl3su66s7lvhm0rbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2bgdl3su66s7lvhm0rbb.png" alt="Mojo" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mojo 1.0 provides a stable, production-ready foundation. &lt;/p&gt;

&lt;p&gt;After years of development and contributions from the community, Modular has officially released Mojo 1.0 as a stable foundation for developers building with the language. Nearly 200 contributors have landed more than 1,100 pull requests affecting over 200,000 lines of code since the standard library was open-sourced, with more than 1,000 people also contributing suggestions, issues that helped shape the language.&lt;/p&gt;

&lt;p&gt;For me, this is significant because 1.0 changes the conversation.&lt;/p&gt;

&lt;p&gt;Mojo is no longer simply an interesting programming language to watch while it is being developed. Developers can now build on a more stable foundation, with Modular committing to primarily additive changes throughout the 1.x era and carefully managing breaking changes.&lt;/p&gt;

&lt;p&gt;Also the Modular team mentioned during the &lt;a href="https://www.youtube.com/watch?v=mXrRWWDHETw" rel="noopener noreferrer"&gt;Ask Us Anything: Modular and Qualcomm&lt;/a&gt; that the Mojo compiler and toolchain will be progressively open-sourced in 2026.&lt;/p&gt;

&lt;p&gt;There is more coming.&lt;/p&gt;

&lt;p&gt;Modular's &lt;a href="https://www.modular.com/modcon" rel="noopener noreferrer"&gt;ModCon 2026&lt;/a&gt;, happening on August 18 in San Francisco, is focused on the next frontier of AI infrastructure, with major announcements and updates around Mojo, MAX, open source, hardware, and the broader AI ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F186s1xb3mqzx7ikdfg3j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F186s1xb3mqzx7ikdfg3j.png" alt="ModCon 2026" width="799" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is why I think the timing matters.&lt;/p&gt;

&lt;p&gt;The technology is evolving quickly. The company behind it has entered a new chapter with Qualcomm. Mojo has reached 1.0. More of the stack is moving toward open source. And developers around the world are beginning to build on top of it.&lt;/p&gt;

&lt;p&gt;We are still early.&lt;/p&gt;

&lt;p&gt;And that is exactly why I believe now is a good time to learn, experiment, contribute, and build.&lt;/p&gt;

&lt;p&gt;For anyone interested in the future of AI infrastructure, this is a good time to start paying attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Thoughts
&lt;/h2&gt;

&lt;p&gt;AI is becoming more powerful, but that also means the computing infrastructure underneath it has to become more efficient. We need software that can take advantage of increasingly diverse hardware without making developers rewrite everything every time the hardware changes.&lt;/p&gt;

&lt;p&gt;Learning Mojo does not mean you have to stop using Python, C++, CUDA, or any other language you already know.&lt;/p&gt;

&lt;p&gt;For me, the value is in understanding another part of the stack.&lt;/p&gt;

&lt;p&gt;The more I learn about AI, the more I realize that the future will not only be about using AI tools. There will be enormous opportunities for people who understand how these systems are actually built.&lt;/p&gt;

&lt;p&gt;And that is why we started &lt;a href="https://x.com/mojoafricahq" rel="noopener noreferrer"&gt;Mojo Africa&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I want to learn, build, and create with other people who are interested in this future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;If you've read this far, I hope you now have a better understanding of what Mojo is, why it exists, where it fits into AI infrastructure, and why I believe it is worth paying attention to.&lt;/p&gt;

&lt;p&gt;The next step is simple: &lt;strong&gt;try it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start with the &lt;a href="https://www.youtube.com/live/1Jqp0BheoEQ?si=Xndk8YygXHV27H2b" rel="noopener noreferrer"&gt;Mojo 101 series&lt;/a&gt;, explore the &lt;a href="https://docs.modular.com/mojo/manual/" rel="noopener noreferrer"&gt;official Mojo documentation&lt;/a&gt;, and write your first Mojo program.&lt;/p&gt;

&lt;p&gt;And if you want to learn and build with other people, join &lt;a href="https://discord.gg/modular" rel="noopener noreferrer"&gt;Modular's Discord&lt;/a&gt; community.&lt;/p&gt;

&lt;p&gt;Thank you for reading ❤️. You can connect with me on &lt;a href="https://x.com/realekemini?s=11" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/ekeminisamuel" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mojo</category>
      <category>ai</category>
      <category>programming</category>
      <category>software</category>
    </item>
    <item>
      <title>Clone MedTalk: HIPAA-Ready Video and Chat Consultations in Flutter</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Mon, 12 Jan 2026 21:28:52 +0000</pubDate>
      <link>https://dev.to/envitab/clone-medtalk-hipaa-ready-video-and-chat-consultations-in-flutter-27dp</link>
      <guid>https://dev.to/envitab/clone-medtalk-hipaa-ready-video-and-chat-consultations-in-flutter-27dp</guid>
      <description>&lt;p&gt;Telehealth is transforming the way patients and providers connect, offering faster access to care and reducing barriers caused by distance or scheduling. A critical part of this experience is enabling secure, real-time video consultations alongside features like chat messaging for sharing updates, questions, and follow-ups.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://getstream.io/chat/solutions/healthcare/" rel="noopener noreferrer"&gt;Stream's healthcare chat solution&lt;/a&gt;, developers can build HIPAA-ready communication features into their apps.&lt;/p&gt;

&lt;p&gt;In this tutorial, you'll learn how to clone &lt;a href="https://medtalk.co/" rel="noopener noreferrer"&gt;MedTalk&lt;/a&gt;, a telehealth platform designed for seamless communication between doctors and patients. We'll use Flutter Web to deliver a responsive cross-platform experience and Stream's Chat &amp;amp; Video SDKs to power HIPAA-ready messaging and video calls. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here’s a demo video of the complete application:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/qE58wXuTeN4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://getstream.io/try-for-free/" rel="noopener noreferrer"&gt;free Stream account&lt;/a&gt; and API key&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/studio?gclsrc=aw.ds&amp;amp;gad_source=1&amp;amp;gad_campaignid=21831783744&amp;amp;gclid=CjwKCAjw7fzDBhA7EiwAOqJkhybR1q73l_7c6U-WTqiP4ckb5sRc4NhY712UjxkQJy286r9gRSoheRoCzYwQAvD_BwE" rel="noopener noreferrer"&gt;Android Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Flutter SDK installed on your computer&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Set Up Your Development Environment
&lt;/h2&gt;

&lt;p&gt;To install Flutter on your computer, first open &lt;a href="https://flutter.dev/" rel="noopener noreferrer"&gt;flutter.dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fosxogwirhfiugymvpbpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fosxogwirhfiugymvpbpo.png" alt="Flutter" width="799" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;If you already have your development environment set up, you can clone the project and get started quickly.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &amp;lt;https://github.com/Tabintel/medtech.git&amp;gt;

&lt;span class="nb"&gt;cd &lt;/span&gt;medtech
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Run this command to install the dependencies needed for the app:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;If not, then continue with the steps below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;First, &lt;a href="https://docs.flutter.dev/get-started/install?_gl=1*1z9esc*_ga*NTAxMTAxMjkzLjE3NTI5NTM5NDU.*_ga_04YGWK0175*czE3NTI5NTM5NDUkbzEkZzEkdDE3NTI5NTQwMzMkajYwJGwwJGgw" rel="noopener noreferrer"&gt;download&lt;/a&gt; the version compatible with your computer’s operating system: Windows, macOS, Linux, or ChromeOS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24otcxyrkngs19g8vmym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24otcxyrkngs19g8vmym.png" alt="App" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then choose the type of app you’ll be building. For this tutorial, we’re building a Flutter web application, so select Web from the list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvashvyltdgxws4r6z2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvashvyltdgxws4r6z2u.png" alt="Code" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After selecting the type of app you’re building, the next step is setting up the requirements to begin building with Flutter. Follow the steps in the &lt;a href="https://docs.flutter.dev/get-started/install/windows/web" rel="noopener noreferrer"&gt;Flutter documentation&lt;/a&gt; to proceed. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’re using another operating system, follow the &lt;a href="https://docs.flutter.dev/get-started/install/macos" rel="noopener noreferrer"&gt;guide for your OS&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Specific requirements for a Windows OS are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;a href="https://gitforwindows.org/" rel="noopener noreferrer"&gt;Git for Windows&lt;/a&gt; to manage Flutter versions and your source code versioning.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.google.com/chrome/dr/download/" rel="noopener noreferrer"&gt;Google Chrome&lt;/a&gt; to debug JavaScript code for web apps.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/docs/setup/windows" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; with the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter" rel="noopener noreferrer"&gt;Flutter extension for VS Code&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Also, note that we are installing Flutter with VS Code.&lt;/li&gt;
&lt;li&gt;Once you meet all these requirements, you’re ready to start building.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install the Flutter SDK
&lt;/h2&gt;

&lt;p&gt;In your VS Code editor, run the Ctrl + Shift +p command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc343cgpe4d9ysjx3um1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc343cgpe4d9ysjx3um1z.png" alt="Flutter" width="799" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then select &lt;strong&gt;Download&lt;/strong&gt; when this window shows.&lt;/p&gt;

&lt;p&gt;After downloading and installing the Flutter SDK, you will choose the type of Flutter template you want to install.&lt;/p&gt;

&lt;p&gt;For this project, select &lt;strong&gt;Application&lt;/strong&gt; as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa89kl4uodlzn6z0w4oz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa89kl4uodlzn6z0w4oz.png" alt="Code" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have the Flutter SDK, we can proceed to further develop the Virtual Health consultation platform with Stream and Flutter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you need any clarification, &lt;a href="https://docs.flutter.dev/install/with-vs-code" rel="noopener noreferrer"&gt;read the Flutter docs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To confirm that you installed Flutter correctly, run flutter doctor -v in your command prompt terminal.&lt;/p&gt;

&lt;p&gt;It should print out a similar response like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0563mxd0vc2saqnegd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0563mxd0vc2saqnegd3.png" alt="Code" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow &lt;a href="https://docs.flutter.dev/platform-integration/web/building" rel="noopener noreferrer"&gt;these steps&lt;/a&gt; to set up the project for building with Flutter Web.&lt;/p&gt;

&lt;p&gt;Create a medtech folder on your computer, and open it in the VS Code editor. In your command prompt, run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;flutter create . \--platforms web&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The output then displays like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0f8yt2cxjw02ejnuz8ek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0f8yt2cxjw02ejnuz8ek.png" alt="project" width="672" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It creates the structure for the &lt;a href="https://docs.flutter.dev/platform-integration/web/building" rel="noopener noreferrer"&gt;Flutter web&lt;/a&gt; project. It also makes a web/ directory containing the web assets used to bootstrap and run your Flutter app, like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz35b5mk2uw92y3nad7wp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz35b5mk2uw92y3nad7wp.png" alt="App" width="443" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the following steps, we will look at how to set up Stream for the virtual health consultation platform with Flutter SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started with Stream
&lt;/h2&gt;

&lt;p&gt;First, &lt;a href="https://getstream.io/accounts/signup/" rel="noopener noreferrer"&gt;create an account&lt;/a&gt; in the Stream dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsan5r249b1kr78rhjh2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsan5r249b1kr78rhjh2v.png" alt="Stream" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on &lt;strong&gt;Create App&lt;/strong&gt; to create a new application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foeebe99em5dis9518bge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foeebe99em5dis9518bge.png" alt="App" width="797" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter the details of the application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Filt1tfcszhk7urr1gkx4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Filt1tfcszhk7urr1gkx4.png" alt="App" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating the Stream app, &lt;a href="https://dashboard.getstream.io/?_gl=1*okfyq9*_gcl_au*MTk2NjYwMzM1LjE3NjEzMTY4NzM.*_ga*MjAxMDg2ODcxMS4xNzUzMjA1MjM0*_ga_FX552ZCLHK*czE3Njc5Njc1NTYkbzI3JGcxJHQxNzY3OTcwMzc5JGo2MCRsMCRoMTcxMjI1NzY2NA.." rel="noopener noreferrer"&gt;go to your dashboard&lt;/a&gt; and click Chat Messaging in the top right. You will then see the App Access Keys displayed. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15i23h24gdk6n397p99f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F15i23h24gdk6n397p99f.png" alt="App" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create an &lt;code&gt;.env&lt;/code&gt;file in your project’s root directory, copy the App key and secret, and save it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;STREAM&lt;span class="se"&gt;\_&lt;/span&gt;API&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-key  
STREAM&lt;span class="se"&gt;\_&lt;/span&gt;API&lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="nv"&gt;SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you’re ready to build and integrate Stream using the &lt;a href="https://getstream.io/chat/docs/flutter-dart/" rel="noopener noreferrer"&gt;Flutter SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In your terminal, run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command installs all the dependencies listed in your &lt;code&gt;pubspec.yaml&lt;/code&gt;, making packages like &lt;code&gt;stream_chat_flutter&lt;/code&gt;, &lt;code&gt;flutter_dotenv&lt;/code&gt;, available to your app. It’s required after any change to &lt;code&gt;pubspec.yaml&lt;/code&gt; before you can use new packages in your code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh85268qk7p74hbnwegxq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh85268qk7p74hbnwegxq.png" alt="Code" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To integrate prebuilt UI components for chat in Flutter, use the stream_chat_flutter package. This package includes reusable and customizable UI components already integrated with Stream’s API.&lt;/p&gt;

&lt;p&gt;Add the package to your dependencies like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dependencies:  
  stream&lt;span class="se"&gt;\_&lt;/span&gt;chat&lt;span class="se"&gt;\_&lt;/span&gt;flutter: ^1.0.1-beta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This package provides full chat UI widgets you can use directly in your app for fast integration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For video, UI components to Stream Video in Flutter, check out &lt;a href="https://getstream.io/video/docs/flutter/ui-components-overview/" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Build the Role Selection Screen
&lt;/h2&gt;

&lt;p&gt;As we develop the app, the &lt;a href="https://github.com/Tabintel/medtech" rel="noopener noreferrer"&gt;codebase&lt;/a&gt; will increase to what is in the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtypph5eizi0zda586fu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtypph5eizi0zda586fu.png" alt="App" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;lib/screen/auth&lt;/code&gt; directory, create a file called &lt;code&gt;role_selection_screen.dart&lt;/code&gt; and enter the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../../stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_client.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoleSelectionScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;RoleSelectionScreen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RoleSelectionScreen&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_RoleSelectionScreenState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;_RoleSelectionScreenState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RoleSelectionScreen&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_loading&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_connectAndNavigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_loading&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connectUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_loading&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="n"&gt;Navigator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pushNamed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFF7F9FC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
          &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;24.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
            &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
            &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;  
              &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                &lt;span class="s"&gt;'assets/images/medtalk&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_logo.png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
              &lt;span class="p"&gt;),&lt;/span&gt;  
              &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
              &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                &lt;span class="s"&gt;'MedTalk'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF2A4D9B&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
              &lt;span class="p"&gt;),&lt;/span&gt;  
              &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
              &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                &lt;span class="s"&gt;'Talk to a doctor anytime, anywhere'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF4A4A4A&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
                &lt;span class="nl"&gt;textAlign:&lt;/span&gt; &lt;span class="n"&gt;TextAlign&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
              &lt;span class="p"&gt;),&lt;/span&gt;  
              &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
              &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_loading&lt;/span&gt;  
                  &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
                  &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                      &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;  
                        &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                          &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_connectAndNavigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'doctor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/doctor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'Dr. Sarah Lee'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                            &lt;span class="nl"&gt;minimumSize:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                            &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF2A4D9B&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                            &lt;span class="nl"&gt;foregroundColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                            &lt;span class="nl"&gt;textStyle:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                            &lt;span class="nl"&gt;shape:&lt;/span&gt; &lt;span class="n"&gt;RoundedRectangleBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                              &lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                            &lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Continue as Doctor'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                        &lt;span class="p"&gt;),&lt;/span&gt;  
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                        &lt;span class="n"&gt;OutlinedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                          &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_connectAndNavigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'patient'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/patient'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'Patient'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;OutlinedButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                            &lt;span class="nl"&gt;minimumSize:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                            &lt;span class="nl"&gt;foregroundColor:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF2A4D9B&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                            &lt;span class="nl"&gt;textStyle:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                            &lt;span class="nl"&gt;side:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BorderSide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF2A4D9B&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                            &lt;span class="nl"&gt;shape:&lt;/span&gt; &lt;span class="n"&gt;RoundedRectangleBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                              &lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                            &lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Continue as Patient'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                        &lt;span class="p"&gt;),&lt;/span&gt;  
                      &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
                    &lt;span class="p"&gt;),&lt;/span&gt;  
            &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
          &lt;span class="p"&gt;),&lt;/span&gt;  
        &lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="p"&gt;),&lt;/span&gt;  
    &lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: You can get the asset in &lt;code&gt;assets/images/medtalk_logo.png&lt;/code&gt; from this &lt;a href="https://github.com/Tabintel/medtech/tree/master/assets/images" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. Download the &lt;a href="https://github.com/Tabintel/medtech/blob/master/assets/images/medtalk_logo.png" rel="noopener noreferrer"&gt;medtalk_logo.png&lt;/a&gt; file and add it to the &lt;code&gt;/assets/images&lt;/code&gt; directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The role selection screen is the entry point to the virtual health consultation application. It allows users to choose between &lt;strong&gt;doctor and patient&lt;/strong&gt; roles. This screen sets the user's identity and routes to the appropriate dashboard.&lt;/p&gt;

&lt;p&gt;The state management uses Flutter's built-in &lt;code&gt;StatefulWidget&lt;/code&gt; to manage UI states and tracks the loading state with a &lt;code&gt;_loading&lt;/code&gt; boolean to show/hide the loading indicator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;_RoleSelectionScreenState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RoleSelectionScreen&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nx"&gt;bool&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;_loading&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="c1"&gt;// ...  &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We import &lt;code&gt;flutter/material.dart&lt;/code&gt; for UI components and &lt;code&gt;../../stream_client.dart&lt;/code&gt; to manage connections to Stream Chat using a user’s ID and name. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The screen is built as a &lt;a href="https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html" rel="noopener noreferrer"&gt;stateful widget&lt;/a&gt; to handle dynamic changes, such as displaying a loading spinner during Stream connection, with a _loading boolean (shown in the code snippet above) that tracks this connection status.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;_connectAndNavigate&lt;/code&gt; function sets &lt;code&gt;_loading&lt;/code&gt; to true, calls &lt;code&gt;StreamClientProvider.connectUser(userId, name: name)&lt;/code&gt; to connect the user, then navigates to either &lt;em&gt;/doctor&lt;/em&gt; or &lt;em&gt;/patient&lt;/em&gt; once connected.&lt;/p&gt;

&lt;p&gt;The UI has an off-white background, the MedTalk logo, and a tagline. If &lt;code&gt;_loading&lt;/code&gt; is true, a &lt;code&gt;CircularProgressIndicator&lt;/code&gt; is shown; otherwise, two buttons appear — “Continue as Doctor” (blue, connects with Dr. Sarah Lee) and “Continue as Patient” (outlined, connects with Patient).&lt;/p&gt;

&lt;p&gt;This screen is the entry point to the virtual health consultation platform, guiding users with a personalized experience based on their role; &lt;em&gt;Doctor or Patient&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the Doctor’s Home Screen
&lt;/h2&gt;

&lt;p&gt;After users select their role and connect to &lt;strong&gt;Stream&lt;/strong&gt;, doctors are taken to the &lt;code&gt;DoctorHomeScreen&lt;/code&gt;. This is the main dashboard for doctors, where they can see demo patients, start chats, and view existing conversations.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;lib/screens/doctor/&lt;/code&gt; create a file: &lt;code&gt;doctor_home_screen.dart&lt;/code&gt;, and enter the code below:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The full code is in the &lt;a href="https://github.com/Tabintel/medtech/blob/master/lib/screens/doctor/doctor_home_screen.dart" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter/stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../../stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_client.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../../demo&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_users.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DoctorHomeScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DoctorHomeScreen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DoctorHomeScreen&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_DoctorHomeScreenState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;_DoctorHomeScreenState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DoctorHomeScreen&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;StreamChannelListController&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_channelListController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_channelListController&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamChannelListController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="nl"&gt;client:&lt;/span&gt; &lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nl"&gt;filter:&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'members'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentUser&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;  
      &lt;span class="nl"&gt;limit:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFF7F9FC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Doctor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF2A4D9B&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
        &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="nl"&gt;iconTheme:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;IconThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF2A4D9B&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
        &lt;span class="nl"&gt;elevation:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;  
            &lt;span class="c1"&gt;// Patient list  &lt;/span&gt;
            &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
              &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;separated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                &lt;span class="nl"&gt;scrollDirection:&lt;/span&gt; &lt;span class="n"&gt;Axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="nl"&gt;itemCount:&lt;/span&gt; &lt;span class="n"&gt;demoPatients&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="nl"&gt;separatorBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                &lt;span class="nl"&gt;itemBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;patient&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;demoPatients&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  
                  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                    &lt;span class="nl"&gt;elevation:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                    &lt;span class="nl"&gt;shape:&lt;/span&gt; &lt;span class="n"&gt;RoundedRectangleBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
                    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                      &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                      &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                        &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;  
                          &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
                          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                            &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                              &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
                              &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

                              &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                                &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                  &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Current user not found. Please log in again.'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
                                &lt;span class="p"&gt;);&lt;/span&gt;  
                                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
                              &lt;span class="p"&gt;}&lt;/span&gt;  
                              &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                                &lt;span class="c1"&gt;// Create the channel  &lt;/span&gt;
                                &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                  &lt;span class="s"&gt;'messaging'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                                  &lt;span class="nl"&gt;extraData:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                                    &lt;span class="s"&gt;'members'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
                                    &lt;span class="s"&gt;'created&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_by&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_id'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                                  &lt;span class="p"&gt;},&lt;/span&gt;  
                                &lt;span class="p"&gt;);&lt;/span&gt;  

                                &lt;span class="c1"&gt;// Create the channel on Stream  &lt;/span&gt;
                                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  

                                &lt;span class="c1"&gt;// Navigate to the chat  &lt;/span&gt;
                                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
                                &lt;span class="n"&gt;Navigator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                  &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                                  &lt;span class="n"&gt;MaterialPageRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                    &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StreamChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                      &lt;span class="nl"&gt;channel:&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                                      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;StreamChannelPage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
                                    &lt;span class="p"&gt;),&lt;/span&gt;  
                                  &lt;span class="p"&gt;),&lt;/span&gt;  
                                &lt;span class="p"&gt;);&lt;/span&gt;  
                              &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
                                &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                  &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Error creating chat: &lt;/span&gt;&lt;span class="si"&gt;${e.toString()}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
                                &lt;span class="p"&gt;);&lt;/span&gt;  
                              &lt;span class="p"&gt;}&lt;/span&gt;  
                            &lt;span class="p"&gt;},&lt;/span&gt;  
                            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Start Chat'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="p"&gt;),&lt;/span&gt;  
                        &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
                      &lt;span class="p"&gt;),&lt;/span&gt;  
                    &lt;span class="p"&gt;),&lt;/span&gt;  
                  &lt;span class="p"&gt;);&lt;/span&gt;  
                &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We import four files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flutter/material.dart&lt;/code&gt; for UI components.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stream_chat_flutter.dart&lt;/code&gt; for prebuilt Stream Chat widgets and controllers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stream_client.dart&lt;/code&gt; for the connected Stream client instance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;demo_users.dart&lt;/code&gt; for sample patient data, so the demo works without a real backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;DoctorHomeScreen&lt;/code&gt; is a stateful widget because it manages a &lt;code&gt;StreamChannelListController&lt;/code&gt;, which fetches channels based on filters. In &lt;code&gt;initState()&lt;/code&gt;, the controller is created with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;client: StreamClientProvider.client — the active Stream connection.&lt;/li&gt;
&lt;li&gt;filter: Filter.in_('members', [currentUser.id]) — returns only channels that include the logged-in doctor.&lt;/li&gt;
&lt;li&gt;limit: 20 — loads the latest 20 channels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the build method, we get the currentUser to display their info and use it when creating new chats. The UI has two main sections:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Patient List (top horizontal list)&lt;/strong&gt;&lt;br&gt;
A scrollable row of demo patients from demoPatients. Each patient card shows their name and a &lt;strong&gt;Start Chat&lt;/strong&gt; button. It then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a messaging channel with members [doctorId, patientId].&lt;/li&gt;
&lt;li&gt;Calls await &lt;code&gt;channel.create()&lt;/code&gt; to set up the conversation in Stream.&lt;/li&gt;
&lt;li&gt;Navigates to &lt;code&gt;StreamChannelPage&lt;/code&gt;, which shows the chat UI if there’s an error, and a SnackBar displays the message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Chat List (main section)&lt;/strong&gt;&lt;br&gt;
A &lt;code&gt;StreamChannelListView&lt;/code&gt; that automatically lists all the doctor’s existing chats based on the controller’s filter. Tapping a channel opens it in StreamChannelPage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The StreamChannelPage&lt;/strong&gt;&lt;br&gt;
This uses three widgets from &lt;code&gt;stream_chat_flutter&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; StreamChannelHeader(): shows the other user’s name and a back button.&lt;/li&gt;
&lt;li&gt; StreamMessageListView(): displays real-time messages.&lt;/li&gt;
&lt;li&gt; StreamMessageInput(): lets users send new messages with emoji and attachments.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://getstream.io/chat/docs/sdk/flutter/" rel="noopener noreferrer"&gt;Stream Chat’s Flutter SDK&lt;/a&gt; handles message syncing, typing indicators, history, and UI updates, so we don’t build these features manually.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This setup allows doctors to see their patients, start conversations, view existing chats, and message in real time. Because this is Flutter Web, you can even open two browser tabs: one as a doctor and one as a patient.&lt;/p&gt;

&lt;p&gt;Next, we’ll switch to the patient’s view in &lt;code&gt;lib/screens/patient/patient_home_screen.dart&lt;/code&gt; to see how the experience mirrors the doctor’s while keeping roles separate.&lt;/p&gt;
&lt;h2&gt;
  
  
  Build the Patient’s Home Screen
&lt;/h2&gt;

&lt;p&gt;Just like the doctor’s view, our patient home screen gives users two main capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See available doctors.&lt;/li&gt;
&lt;li&gt;Start or continue chats with them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a &lt;code&gt;patient_home_screen.dart&lt;/code&gt; file in the &lt;code&gt;lib/screens/patient&lt;/code&gt; directory and enter the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter/stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../../stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_client.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../../demo&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_users.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PatientHomeScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PatientHomeScreen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PatientHomeScreen&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_PatientHomeScreenState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;_PatientHomeScreenState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PatientHomeScreen&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;StreamChannelListController&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_channelListController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_channelListController&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamChannelListController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="nl"&gt;client:&lt;/span&gt; &lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nl"&gt;filter:&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'members'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentUser&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;  
      &lt;span class="nl"&gt;limit:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFF7F9FC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Patient'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF2A4D9B&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
        &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="nl"&gt;iconTheme:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;IconThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF2A4D9B&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
        &lt;span class="nl"&gt;elevation:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;  
            &lt;span class="c1"&gt;// Doctor list  &lt;/span&gt;
            &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
              &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;separated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                &lt;span class="nl"&gt;scrollDirection:&lt;/span&gt; &lt;span class="n"&gt;Axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="nl"&gt;itemCount:&lt;/span&gt; &lt;span class="n"&gt;demoDoctors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="nl"&gt;separatorBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                &lt;span class="nl"&gt;itemBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;doctor&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;demoDoctors&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  
                  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                    &lt;span class="nl"&gt;elevation:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                    &lt;span class="nl"&gt;shape:&lt;/span&gt; &lt;span class="n"&gt;RoundedRectangleBorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;borderRadius:&lt;/span&gt; &lt;span class="n"&gt;BorderRadius&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;circular&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
                    &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                      &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                      &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                        &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;  
                          &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doctor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
                          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                            &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                              &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
                              &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

                              &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
                                &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Error: User not logged in'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
                                &lt;span class="p"&gt;);&lt;/span&gt;  
                                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
                              &lt;span class="p"&gt;}&lt;/span&gt;  

                              &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                                &lt;span class="c1"&gt;// Create the channel  &lt;/span&gt;
                                &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                  &lt;span class="s"&gt;'messaging'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                                  &lt;span class="nl"&gt;extraData:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                                    &lt;span class="s"&gt;'members'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doctor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
                                    &lt;span class="s"&gt;'created&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_by&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_id'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                                  &lt;span class="p"&gt;},&lt;/span&gt;  
                                &lt;span class="p"&gt;);&lt;/span&gt;  

                                &lt;span class="c1"&gt;// Create the channel on Stream  &lt;/span&gt;
                                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  

                                &lt;span class="c1"&gt;// Navigate to the chat  &lt;/span&gt;
                                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
                                &lt;span class="n"&gt;Navigator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                  &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                                  &lt;span class="n"&gt;MaterialPageRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                    &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StreamChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                      &lt;span class="nl"&gt;channel:&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                                      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;StreamChannelPage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
                                    &lt;span class="p"&gt;),&lt;/span&gt;  
                                  &lt;span class="p"&gt;),&lt;/span&gt;  
                                &lt;span class="p"&gt;);&lt;/span&gt;  
                              &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
                                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
                                &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
                                  &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Error creating chat: &lt;/span&gt;&lt;span class="si"&gt;${e.toString()}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  
                                &lt;span class="p"&gt;);&lt;/span&gt;  
                              &lt;span class="p"&gt;}&lt;/span&gt;  
                            &lt;span class="p"&gt;},&lt;/span&gt;  
                            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Start Chat'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
                          &lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Get the &lt;a href="https://github.com/Tabintel/medtech/tree/master/lib/screens/patient" rel="noopener noreferrer"&gt;full code in GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the screen loads, &lt;code&gt;initState()&lt;/code&gt; creates a &lt;code&gt;StreamChannelListController&lt;/code&gt; that acts as the inbox manager. It connects to &lt;code&gt;StreamClientProvider.client&lt;/code&gt; (set up in role_selection_screen.dart) and filters for any channels where the logged-in patient is a member:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nl"&gt;filter:&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'members'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentUser&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because the patient is connected to Stream through getstream-token-server.js. (We will explain this later in the article.)&lt;/p&gt;

&lt;p&gt;The Node server takes the patient’s ID ("patient"), uses the Stream API key and secret from .env, generates a user token, and registers them with Stream. Without that step, currentUser would be null and chats wouldn’t work.&lt;/p&gt;

&lt;p&gt;The UI starts with a horizontal list of doctors pulled from demoDoctors. &lt;/p&gt;

&lt;p&gt;When a patient taps &lt;strong&gt;Start Chat&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We create or retrieve a messaging channel with both patient and doctor IDs.&lt;/li&gt;
&lt;li&gt;If it doesn’t exist, Stream creates it automatically.&lt;/li&gt;
&lt;li&gt;We call await channel.create() to finalize it.&lt;/li&gt;
&lt;li&gt;We navigate to StreamChannelPage, which uses
StreamMessageListView() for real-time messages and    StreamMessageInput() for sending new ones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the bottom, &lt;code&gt;StreamChannelListView&lt;/code&gt; displays all active chats, including ones started by doctors. Stream’s SDK updates this list instantly, so new messages appear in real time.&lt;/p&gt;

&lt;p&gt;With both doctor and patient home screens complete, we have a two-way chat system using &lt;a href="https://flutter.dev/multi-platform/web" rel="noopener noreferrer"&gt;Flutter Web&lt;/a&gt;, &lt;a href="https://getstream.io/chat/docs/sdk/flutter/" rel="noopener noreferrer"&gt;Stream Flutter SDK&lt;/a&gt;, and a &lt;a href="https://github.com/Tabintel/medtech/blob/master/getstream-token-server.js" rel="noopener noreferrer"&gt;Node token server&lt;/a&gt;. The next step is to add a shared chat screen functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the Shared Chat Screen
&lt;/h2&gt;

&lt;p&gt;Both doctors and patients use the shared chat screen. It provides a reusable UI for a single channel, powered by the Stream Chat Flutter SDK. You pass in a channelId and userId, and the screen connects to that channel, showing real-time messages with Stream’s prebuilt widgets.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;chat_screen.dart&lt;/code&gt; file in the &lt;code&gt;lib/screens/shared&lt;/code&gt; directory and enter the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter/stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'../../stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_client.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ChatScreen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatScreen&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_ChatScreenState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;_ChatScreenState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatScreen&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="s"&gt;'messaging'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nl"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nl"&gt;extraData:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'members'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;  
    &lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;StreamChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="nl"&gt;channel:&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;StreamChannelHeader&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
        &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;  
            &lt;span class="n"&gt;Expanded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;StreamMessageListView&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;  
            &lt;span class="n"&gt;StreamMessageInput&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
          &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
        &lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="p"&gt;),&lt;/span&gt;  
    &lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the widget loads, &lt;code&gt;initState()&lt;/code&gt; creates a Channel from the global client in StreamClientProvider.&lt;/p&gt;

&lt;p&gt;It calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
  &lt;span class="s"&gt;'messaging'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="nl"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="nl"&gt;extraData:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'members'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;  
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This communicates with Stream to: &lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Get or create this messaging channel with these members.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the channel doesn’t exist yet, Stream will create it when &lt;code&gt;channel.create()&lt;/code&gt; is called earlier in the flow. The &lt;code&gt;extraData&lt;/code&gt; helps identify channel members and supports filtering and admin logic.&lt;/p&gt;

&lt;p&gt;After creating the channel object, the code calls channel.watch(). This starts streaming channel state and events—messages, typing indicators, presence updates, and reactions—over a WebSocket connection. Updates arrive instantly without polling.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;build()&lt;/code&gt; method, the UI is wrapped in &lt;code&gt;StreamChannel(channel: channel, child: ...)&lt;/code&gt;. StreamChannel is a provider widget that makes the channel available to its children. Inside, the prebuilt widgets handle the chat experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;StreamChannelHeader() → shows the channel name and back button.&lt;/li&gt;
&lt;li&gt;StreamMessageListView() → displays the live message feed with avatars, ordering, and read receipts.&lt;/li&gt;
&lt;li&gt;StreamMessageInput() → sends messages, attachments, and reactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These widgets handle sending and receiving messages, rendering updates, retry logic, and syncing with Stream’s servers.&lt;/p&gt;

&lt;p&gt;This screen assumes the user is already connected to Stream through StreamClientProvider—a step that happens earlier (for example, in role_selection_screen.dart). That connection involves calling your Node-based token server (getstream-token-server.js), which uses your API secret to generate a user token. The Flutter app then calls client.connectUser(User(id: ...), token) to authenticate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwt0izqmzyvndac44nnne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwt0izqmzyvndac44nnne.png" alt="App" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a doctor or patient clicks “Start Chat” from their home screen, the app either creates the channel there or navigates into this ChatScreen with the existing channelId. As long as both members are included in the channel’s members list, Stream will route messages correctly and store them in your &lt;a href="https://dashboard.getstream.io/?_gl=1*1hqky2c*_gcl_au*MTk2NjYwMzM1LjE3NjEzMTY4NzM.*_ga*MjAxMDg2ODcxMS4xNzUzMjA1MjM0*_ga_FX552ZCLHK*czE3NjgyMzY1OTgkbzI5JGcxJHQxNzY4MjQzNzg0JGo2MCRsMCRoMzc0OTI5ODgz" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect to Stream Client
&lt;/h2&gt;

&lt;p&gt;To connect the Flutter app to the Stream client, create a stream.dart file in the lib/stream directory and enter the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter/stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_dotenv/flutter&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_dotenv.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:convert'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:http/http.dart'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamClientProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;StreamChatClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
    &lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'STREAM&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_API&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_KEY'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="nl"&gt;logLevel:&lt;/span&gt; &lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="c1"&gt;/// Connect a user to the Stream Chat client (production, fetch token from backend)  &lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;connectUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentUser&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disconnectUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="c1"&gt;// Fetch token from backend  &lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;tokenEndpoint&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'TOKEN&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_ENDPOINT'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;'http://localhost:3000/token'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$tokenEndpoint&lt;/span&gt;&lt;span class="s"&gt;?user&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_id=&lt;/span&gt;&lt;span class="si"&gt;$userId&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Failed to fetch Stream Chat token: &lt;/span&gt;&lt;span class="si"&gt;${response.body}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'token'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connectUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
    &lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This connects the Flutter app and the Stream Chat API. It loads your API key from .env, initializes a single Stream client for the whole app, and connects users by fetching their token from your backend.&lt;/p&gt;

&lt;p&gt;At the top, we import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter/stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_dotenv/flutter&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_dotenv.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:convert'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:http/http.dart'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;stream_chat_flutter&lt;/strong&gt; – The official Flutter SDK for Stream Chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;flutter_dotenv&lt;/strong&gt; – Loads environment variables from .env, such as your API key and backend URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dart:convert&lt;/strong&gt; – Decodes JSON responses from the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;http&lt;/strong&gt; – Makes HTTP requests to your backend to get the chat token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main class is StreamClientProvider, which creates one StreamChatClient for the entire app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;StreamChatClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
  &lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'STREAM&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_API&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_KEY'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
  &lt;span class="nl"&gt;logLevel:&lt;/span&gt; &lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the API key comes from .env, and the log level is set to INFO so you can see helpful debug messages about chat events.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;connectUser&lt;/code&gt; method is where the actual login happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;connectUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentUser&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disconnectUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, it checks if the user is already connected with the same ID. If so, it skips the rest. If not, it disconnects any existing users to avoid conflicts.&lt;/p&gt;

&lt;p&gt;Next, it fetches the token from your backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;tokenEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'TOKEN_ENDPOINT'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;'http://localhost:3000/token'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$tokenEndpoint&lt;/span&gt;&lt;span class="s"&gt;?user_id=&lt;/span&gt;&lt;span class="si"&gt;$userId&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token endpoint is read from .env (falling back to localhost for development). The request includes the user ID as a query parameter.&lt;/p&gt;

&lt;p&gt;If the server responds with anything other than 200 OK, it throws an error. Otherwise, it extracts the token from the JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;'Failed to fetch Stream Chat token: &lt;/span&gt;&lt;span class="si"&gt;${response.body}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s"&gt;'token'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, it connects the user to Stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connectUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
  &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
  &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK uses this ID, optional display name, and the JWT token from your backend to log in securely. The token is generated server-side using your API key and secret, so those sensitive credentials never touch the Flutter app.&lt;/p&gt;

&lt;p&gt;Basically, the stream_client.dart file loads your API key from .env, requests a secure token from the server, and connects the user to Stream Chat so they can send and receive messages in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmaagfpzeue2ix7yfb2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmaagfpzeue2ix7yfb2c.png" alt="App" width="799" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Up the Main App Entry Point
&lt;/h2&gt;

&lt;p&gt;In the lib directory, create a main.dart file. This will serve as the entry point for your Flutter application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_dotenv/flutter&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_dotenv.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter/stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_chat&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_flutter.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_localizations/flutter&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_localizations.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'stream&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_client.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'screens/auth/role&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_selection&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_screen.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'screens/doctor/doctor&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_home&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_screen.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'screens/patient/patient&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_home&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_screen.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'screens/shared/video&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_call&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;_screen.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="n"&gt;WidgetsFlutterBinding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ensureInitialized&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fileName:&lt;/span&gt; &lt;span class="s"&gt;'.env'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MedTalkApp&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MedTalkApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="n"&gt;MedTalkApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;  
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;StreamChatClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StreamClientProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
  &lt;span class="nd"&gt;@override&lt;/span&gt;  
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
      &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="s"&gt;'MedTalk'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="n"&gt;ThemeData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="nl"&gt;colorScheme:&lt;/span&gt; &lt;span class="n"&gt;ColorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;seedColor:&lt;/span&gt; &lt;span class="n"&gt;Colors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
        &lt;span class="nl"&gt;useMaterial3:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="nl"&gt;localizationsDelegates:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;  
        &lt;span class="n"&gt;GlobalMaterialLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="n"&gt;GlobalWidgetsLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="n"&gt;GlobalCupertinoLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
      &lt;span class="nl"&gt;supportedLocales:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;  
        &lt;span class="n"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  
      &lt;span class="nl"&gt;initialRoute:&lt;/span&gt; &lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="nl"&gt;routes:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;RoleSelectionScreen&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
        &lt;span class="s"&gt;'/doctor'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DoctorHomeScreen&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
        &lt;span class="s"&gt;'/patient'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PatientHomeScreen&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
        &lt;span class="s"&gt;'/video'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;VideoCallScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;participantName:&lt;/span&gt; &lt;span class="s"&gt;'Dr. Sarah Lee'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
      &lt;span class="p"&gt;},&lt;/span&gt;  
      &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StreamChat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="nl"&gt;client:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
      &lt;span class="p"&gt;),&lt;/span&gt;  
    &lt;span class="p"&gt;);&lt;/span&gt;  
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by importing the required packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flutter/material.dart for core Flutter UI components&lt;/li&gt;
&lt;li&gt;flutter_dotenv to load environment variables from the .env file&lt;/li&gt;
&lt;li&gt;stream_chat_flutter for integrating the Stream Chat SDK&lt;/li&gt;
&lt;li&gt;flutter_localizations to add localization support&lt;/li&gt;
&lt;li&gt;stream_client.dart` (local file) and screen widgets for different roles and features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we set up the main() function, which ensures Flutter’s bindings are initialized, loads our environment variables, and finally runs the MedTalkApp.&lt;/p&gt;

&lt;p&gt;The MedTalkApp widget extends StatelessWidget and defines the app’s overall configuration. It creates a StreamChatClient instance (from StreamClientProvider.client) and sets up the app theme, localization delegates, and supported locales.&lt;/p&gt;

&lt;p&gt;For navigation, we define our initial route ('/'), which takes the user to the &lt;strong&gt;Role Selection Screen&lt;/strong&gt;, and register other routes for the Doctor Home, Patient Home, and Video Call screens. This makes navigating between different app parts easy, depending on the user’s role and actions.&lt;/p&gt;

&lt;p&gt;The builder property of MaterialApp wraps the child widget with the StreamChat widget, passing it on to the client. This ensures the Stream Chat SDK is available throughout the app’s widget tree, enabling messaging and chat features in any screen.&lt;/p&gt;

&lt;p&gt;With the app’s entry point ready, the next step is to create a node server to generate Stream Chat tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Up the Node.js Server
&lt;/h2&gt;

&lt;p&gt;Create a getstream-token-server.js file in the root directory and enter the code in the &lt;a href="https://github.com/Tabintel/medtech/blob/master/getstream-token-server.js" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This runs a Node.js + Express server that generates secure JWT tokens for authenticating users with the Stream Chat API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In production, do not hardcode your Stream API secret directly in your Flutter app. Instead, the server/backend generates a timed token for the authenticated user, and your Flutter app uses that token to connect to Stream. This is exactly what this Node token server does in this context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We start by importing the required packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;express to create a simple HTTP server&lt;/li&gt;
&lt;li&gt;stream-chat to talk to Stream’s API and create tokens&lt;/li&gt;
&lt;li&gt;cors so our Flutter web app can call the server without CORS issues&lt;/li&gt;
&lt;li&gt;dotenv so we can store our API key and secret safely in a &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we load the environment variables and check if the STREAM_API_KEY and STREAM_API_SECRET are set. The server will stop immediately if they're missing; we can’t run without them.&lt;/p&gt;

&lt;p&gt;We then initialize a StreamChat server client using the API key and secret. This client will be able to create tokens, seed users, and perform admin operations.&lt;/p&gt;

&lt;p&gt;The server exposes two main endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/token?user_id=USER_ID – This generates a JWT token for the given user_id. Flutter will call this when connecting to Stream.&lt;/li&gt;
&lt;li&gt;/ – A simple HTML page just to verify that the server is running.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request logging so you can see when tokens are requested.&lt;/li&gt;
&lt;li&gt;A *&lt;em&gt;user seeding script *&lt;/em&gt;(seedUsers) that adds demo doctors and patients to Stream. These IDs and names match the ones in our Flutter app, so you can test right away.&lt;/li&gt;
&lt;li&gt;Graceful shutdown handling so the server closes cleanly when you stop it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open a terminal and start the server with this command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;node getstream-token-server.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now your Flutter app can request tokens securely and connect to Stream Chat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Up Demo Users for MedTalk
&lt;/h2&gt;

&lt;p&gt;To keep development simple, we’ll define a set of demo doctors and patients that our MedTalk app can use for testing. This will allow us to quickly switch between roles (doctor or patient).&lt;/p&gt;

&lt;p&gt;In the lib directory, create a new file called demo_users.dart and add the following code from the &lt;a href="https://github.com/Tabintel/medtech/blob/master/lib/demo_users.dart" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this setup, we can easily pull a list of doctors or patients in the UI, making it much faster to test the application.&lt;/p&gt;

&lt;p&gt;Now the full Flutter app is set up, and we can begin testing it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the Virtual Health Consultation App
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Select &lt;a href="https://www.google.com/chrome/" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt; as your app's target device to run and debug a &lt;a href="https://docs.flutter.dev/platform-integration/web/building" rel="noopener noreferrer"&gt;Flutter web app&lt;/a&gt;:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
flutter run \-d chrome&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can also choose Chrome as a target device in your IDE.&lt;/p&gt;

&lt;p&gt;If you prefer, you can use the edge device type on Windows, or use web-server to navigate to a local URL in the browser of your choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run From the Command Line
&lt;/h2&gt;

&lt;p&gt;If you use Flutter run from the command line, you can now run hot reload on the web with the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
flutter run \-d chrome \--web-experimental-hot-reload&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
When hot reload is enabled, you can restart your application by pressing "r" in the running terminal or "R".&lt;/p&gt;

&lt;p&gt;The Flutter web app loads and opens this URL in the Chrome browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:60767/" rel="noopener noreferrer"&gt;http://localhost:60767/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the application's landing page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcoflam65psdse7tb4txf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcoflam65psdse7tb4txf.png" alt="MedTalk" width="799" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click on either &lt;strong&gt;Continue as Doctor&lt;/strong&gt; or &lt;strong&gt;Continue as Patient&lt;/strong&gt;, it then redirects to a loading screen for a few seconds, like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferqptff69ae9xqwjkxrw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferqptff69ae9xqwjkxrw.png" alt="App" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which then redirects to the chat page, with this URL (if you selected Patient role), like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:57331/#/patient" rel="noopener noreferrer"&gt;http://localhost:57331//#/patient&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5z8zgcsd4cchf0qgfm4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5z8zgcsd4cchf0qgfm4.png" alt="Chat" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the moment, there are no active chats yet.&lt;/p&gt;

&lt;p&gt;The users then show:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6yphtqaqsxyg5m6a1kq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6yphtqaqsxyg5m6a1kq.png" alt="App" width="799" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the patients:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48y098w5gjcagwuwdgje.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48y098w5gjcagwuwdgje.png" alt="App" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the Patient/Doctor chat loads:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fspa1h8cbidb5tdol9woq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fspa1h8cbidb5tdol9woq.png" alt="App" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the Stream dashboard, check the chat logs to confirm the application is integrated with Stream through the &lt;a href="https://getstream.io/chat/docs/flutter-dart/" rel="noopener noreferrer"&gt;Flutter SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00frvrq3f9xsshf20l2j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00frvrq3f9xsshf20l2j.png" alt="Flutter" width="799" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: We built a Node.js server for handling the Stream tokens for chat/video.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7adb0m1oz99063vbbuan.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7adb0m1oz99063vbbuan.png" alt="App" width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test the endpoint in your browser or with curl: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkadgj19l0srdo5e8av4k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkadgj19l0srdo5e8av4k.png" alt="App" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should get a JSON response like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh37j2i2tmnpd2hqjplc4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh37j2i2tmnpd2hqjplc4.png" alt="JSON" width="800" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Run the Flutter Web App
&lt;/h2&gt;

&lt;p&gt;To connect the Flutter web app with the Stream Flutter SDK, we set up a server using Node.js​​, as shown in the step above. This server generates tokens from Stream Chat using the Stream API, which is connected through the API key set up in the .env file at the root of the project directory codebase.&lt;/p&gt;

&lt;p&gt;To do this, we first need to run the Node.js server, seed some users (Doctors and Patients), and assign Stream tokens to each user. This enables users to chat asynchronously on the platform.&lt;/p&gt;

&lt;p&gt;To get started running the app, first run the Node.js server in a different terminal.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We use two command prompts in a terminal to run this application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the first terminal, run this command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
node getstream-token-server.js&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This starts the node server and shows an output like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1acfxably8znjorryaa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1acfxably8znjorryaa.png" alt="Node" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To test an endpoint, open this URL in your browser: &lt;a href="http://localhost:3000/token?user_id=doctor_anna" rel="noopener noreferrer"&gt;http://localhost:3000/token?user_id=doctor_anna&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It then loads this HTML page, and when you click Get token for doctor anna.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe41lv2rtxasoxyedzkhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe41lv2rtxasoxyedzkhb.png" alt="App" width="657" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It generates a token like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtaj5qj6u92061bu8xes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtaj5qj6u92061bu8xes.png" alt="Node" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also view the server logs in the Node.js terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdlmyc0xwtv69nfv5dse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdlmyc0xwtv69nfv5dse.png" alt="App" width="729" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The token server is now up and running perfectly with this setup:&lt;/p&gt;

&lt;p&gt;Server is listening on &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;br&gt;
 It has successfully generated a token for a Doctor user, doctor_anna&lt;br&gt;
 All demo users are seeded in Stream Chat&lt;/p&gt;

&lt;p&gt;Next, let's test the Flutter app, which should now be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to the token server&lt;/li&gt;
&lt;li&gt;Authenticate users&lt;/li&gt;
&lt;li&gt;Create chat channels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the Flutter app with this command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;flutter run \-d chrome&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The Flutter app begins to start and shows these logs in your terminal. If you look closely at the log messages, you will see that the Flutter app is connected to the Stream API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F881lbc03llhhkjut69ii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F881lbc03llhhkjut69ii.png" alt="App" width="799" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Flutter app then loads and opens in your Chrome browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2k0ckukst4paqte09iev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2k0ckukst4paqte09iev.png" alt="App" width="799" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on either Continue as Doctor or Patient, and then try starting a new chat. If you select the Doctor role, the chat loads, showing the history of previous chats with Patients.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbeas4ryh3hf23lx3tlc5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbeas4ryh3hf23lx3tlc5.png" alt="App" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on any user, say Bob Brown, to start a chat&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpe0mxl7odswisuaie6o6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpe0mxl7odswisuaie6o6.png" alt="App" width="799" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can also confirm that the messages are sent through Stream by &lt;a href="https://dashboard.getstream.io/app/1407599/chat/apilogs?_gl=1*ttx6av*_gcl_au*MTk2NjYwMzM1LjE3NjEzMTY4NzM.*_ga*MjAxMDg2ODcxMS4xNzUzMjA1MjM0*_ga_FX552ZCLHK*czE3NjgyMzY1OTgkbzI5JGcxJHQxNzY4MjUwMTc0JGo2MCRsMCRoMzc0OTI5ODgz" rel="noopener noreferrer"&gt;viewing the real-time chat logs&lt;/a&gt; in your dashboard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydf0qnabxkkcv8wac6jk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydf0qnabxkkcv8wac6jk.png" alt="App" width="799" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s try out the Patient's chat!&lt;/p&gt;

&lt;p&gt;Navigate to the homepage of the application with &lt;a href="http://localhost:64150" rel="noopener noreferrer"&gt;http://localhost:64150&lt;/a&gt;, and then click on Continue as Patient, which then loads the view as shown below:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The direct URL to the patient view of the app is: &lt;a href="http://localhost:64150/#/patient" rel="noopener noreferrer"&gt;http://localhost:64150/#/patient&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrak5b20l7d6l42drng5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrak5b20l7d6l42drng5.png" alt="App" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on any of the Doctors, say Doctor Emily, to start a conversation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F596e4fch94om0830fwd1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F596e4fch94om0830fwd1.png" alt="App" width="799" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. As a Doctor:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Click "Start Chat" with a patient&lt;/li&gt;
&lt;li&gt; Send a message in the chat&lt;/li&gt;
&lt;li&gt; The chat should now appear in your chat list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. As a Patient:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log in as the patient you chatted with&lt;/li&gt;
&lt;li&gt;You should see the chat in your chat list&lt;/li&gt;
&lt;li&gt;You can reply, and the doctor will see it immediately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Verify Real-time Sync:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the same chat on two devices (or two different user accounts)&lt;/li&gt;
&lt;li&gt;Messages should appear instantly on both devices when sent from either side&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can view the total number of messages on the Stream chat dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdp7lyucoy9gnpblkt4q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdp7lyucoy9gnpblkt4q.png" alt="App" width="798" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To run the app locally, clone the GitHub repository with this command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: As mentioned in the article earlier, make sure you have Flutter set up and installed on your computer. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Navigate to the project directory, then run this command to install the dependencies needed for the app.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;flutter pub get&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Also, ensure your Stream API keys are added to an .env file in the project directory.&lt;/p&gt;

&lt;p&gt;Then run the application with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;flutter run \-d chrome&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And that’s it! You have a working virtual consultation app, fully set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you built a MedTalk clone, a &lt;a href="https://getstream.io/blog/hipaa-video-conferencing/" rel="noopener noreferrer"&gt;HIPAA-compliant telehealth application&lt;/a&gt; powered by Flutter and Stream. By integrating Stream Chat and Stream Video, you enabled secure messaging and real-time video consultations between doctors and patients. This prototype shows how quickly you can deliver compliant, modern healthcare communication experiences with Stream’s SDKs.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>coding</category>
    </item>
    <item>
      <title>Build AI-Powered Smart Replies with React and Synthetic</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Fri, 19 Dec 2025 07:59:08 +0000</pubDate>
      <link>https://dev.to/envitab/build-ai-powered-smart-replies-with-react-and-synthetic-44hc</link>
      <guid>https://dev.to/envitab/build-ai-powered-smart-replies-with-react-and-synthetic-44hc</guid>
      <description>&lt;p&gt;You’ve probably used Gmail’s Smart Reply or Slack’s suggested messages, which are short, AI-generated responses like “Sounds good 👍” or “Let’s do it!”. With a single tap response, you can save time and keep conversations flowing.&lt;/p&gt;

&lt;p&gt;By reducing time spent typing, conversations stay active, drop-off rates decrease, and chat apps feel more innovative and modern.&lt;/p&gt;

&lt;p&gt;Smart replies can be used in customer support apps (agents pick relevant replies and respond faster), marketplaces or communities (buyers and sellers exchange quick transactional messages), or even productivity tools or workplace chat platforms (users save time on repetitive confirmations like “Yes,” “Got it,” or “On it”).&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll use &lt;a href="https://getstream.io/chat/sdk/react/" rel="noopener noreferrer"&gt;Stream’s React SDK&lt;/a&gt; and &lt;a href="https://synthetic.new/landing/home" rel="noopener noreferrer"&gt;Synthetic’s&lt;/a&gt; inference API to bring smart replies into chat. You’ll build a chat application that generates real-time, context-aware reply suggestions directly within conversations.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch a demo of what you’ll build:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/3u_mK8KwpLs"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along, make sure you have the following prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js (version 16 or higher) and NPM installed&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://getstream.io/accounts/signup/" rel="noopener noreferrer"&gt;free Stream account&lt;/a&gt; with your API key&lt;/li&gt;
&lt;li&gt;A Synthetic API key for accessing the inference API&lt;/li&gt;
&lt;li&gt;Basic knowledge of React, Next.js, and Typescript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Ready? Let’s begin!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stream is a platform that provides &lt;a href="https://getstream.io/" rel="noopener noreferrer"&gt;APIs and SDKs&lt;/a&gt; for developers to build real-time chat, video, and activity feeds into their applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh22eemyg331g7jo28ajh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh22eemyg331g7jo28ajh.png" alt="Stream" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get started with Stream, &lt;a href="https://getstream.io/try-for-free/" rel="noopener noreferrer"&gt;create your free account&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learn how to set up your Stream account in &lt;a href="https://getstream.io/blog/stream-getting-started-guide/" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After you’ve signed up, navigate to your dashboard and click on the &lt;strong&gt;Create App&lt;/strong&gt; button in the top right of the page, like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3b4dip7mn71w2wgm44jj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3b4dip7mn71w2wgm44jj.png" alt="App" width="798" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This modal then loads, where you enter details of the new app you’re creating. Enter the app name and then select the chat &amp;amp; video data region, feed data storage location, and environment (you can choose 'development'). Click '&lt;strong&gt;Create App&lt;/strong&gt;' to proceed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkqlfdplbxna4x30xf6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkqlfdplbxna4x30xf6a.png" alt="App" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you’ll get your App API Access Keys from the Chat Overview in your &lt;a href="https://dashboard.getstream.io/app/1440849/chat/overview" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt;. Copy the Key and Secret and add it to your .env file in your codebase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbw5a5j8495tzwi0flfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbw5a5j8495tzwi0flfj.png" alt="Keys" width="800" height="383"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;STREAM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;”your_stream_api”
&lt;span class="nv"&gt;STREAM_API_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;”your_stream_secret”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will be using the Stream React Chat SDK for this application. Learn more about it in the &lt;a href="https://getstream.io/chat/docs/sdk/react/basics/installation/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the React Project
&lt;/h2&gt;

&lt;p&gt;Open your terminal and enter this command to create a new app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest smart-chat &lt;span class="nt"&gt;--typescript&lt;/span&gt; &lt;span class="nt"&gt;--tailwind&lt;/span&gt; &lt;span class="nt"&gt;--eslint&lt;/span&gt; &lt;span class="nt"&gt;--app&lt;/span&gt; &lt;span class="nt"&gt;--src-dir&lt;/span&gt; &lt;span class="nt"&gt;--import-alias&lt;/span&gt; &lt;span class="s2"&gt;"@/*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new Next.js app in your directory called “smart-chat”, with Typescript and Tailwind configuration. It also adds an &lt;code&gt;Import alias&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Import aliases help with better imports and readability in your codebase. When adding new imports, simply update the alias configuration in &lt;code&gt;tsconfig.json&lt;/code&gt; or &lt;code&gt;jsconfig.json&lt;/code&gt;, rather than updating import paths throughout your codebase.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, navigate to the new project directory we just created with this command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;smart-chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, install the &lt;a href="https://getstream.io/chat/docs/sdk/react/" rel="noopener noreferrer"&gt;Stream Chat React SDK&lt;/a&gt; with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;stream-chat stream-chat-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command installs the &lt;code&gt;stream-chat&lt;/code&gt; and &lt;code&gt;stream-chat-react&lt;/code&gt; packages. We will import the required components, such as &lt;code&gt;Chat&lt;/code&gt;, &lt;code&gt;Channel&lt;/code&gt;, &lt;code&gt;MessageList&lt;/code&gt;, and &lt;code&gt;MessageInput&lt;/code&gt;, from stream-chat-react into the main application file, which is the &lt;code&gt;page.tsx&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Next, create a &lt;code&gt;stream-chat-container.tsx&lt;/code&gt; file in the &lt;code&gt;components/&lt;/code&gt; directory and import the necessary components from &lt;code&gt;stream-chat-react&lt;/code&gt; to build the chat interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamChat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ChannelHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MessageList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MessageInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react/dist/css/v2/index.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stream-chat package provides the core Stream Chat client for initialising and managing chat functionality, while stream-chat-react offers pre-built React components like &lt;code&gt;Chat&lt;/code&gt;,&lt;code&gt;Channel&lt;/code&gt;, &lt;code&gt;MessageList&lt;/code&gt;, and &lt;code&gt;MessageInput&lt;/code&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Chat&lt;/code&gt; component acts as a provider, wrapping the chat interface to manage the Stream client.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Channel&lt;/code&gt; component renders a specific chat channel. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MessageList&lt;/code&gt; displays the conversation history, and &lt;code&gt;&amp;lt;MessageInput&amp;gt;&lt;/code&gt; allows you to compose messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The full implementation is available in the &lt;a href="https://github.com/Tabintel/smart-chat/blob/master/components/stream-chat-container.tsx" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In your project’s root directory, create a &lt;code&gt;page.tsx&lt;/code&gt; file in the &lt;code&gt;/app&lt;/code&gt; directory and enter the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamChatContainer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/stream-chat-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoggedIn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUserId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUserName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;inputUserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setInputUserId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;inputUserName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setInputUserName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleLogin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputUserId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;inputUserName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputUserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputUserName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setIsLoggedIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StreamChatContainer&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Due to the length of the code file above, we have included only a snippet here. The complete code is available in the &lt;a href="https://github.com/Tabintel/smart-chat/blob/master/app/page.tsx" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This sets up the main entry point for the application by prompting you to enter a User ID and Display Name before rendering the chat interface. &lt;/p&gt;

&lt;p&gt;It uses React’s &lt;code&gt;useState&lt;/code&gt; hook to manage user login state. When you enter a User ID and Display Name and click the Join Chat button, the &lt;code&gt;handleLogin&lt;/code&gt; function updates the state and renders the  component, passing the &lt;code&gt;userId&lt;/code&gt; and &lt;code&gt;userName&lt;/code&gt; props. &lt;/p&gt;

&lt;p&gt;For the UI, we’re adapting a Discord-like interface.&lt;br&gt;
To set up the layout for this application, create a &lt;code&gt;layout.tsx&lt;/code&gt; file also in the &lt;code&gt;/app&lt;/code&gt; directory, and enter this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Geist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Geist_Mono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/font/google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;


&lt;span class="c1"&gt;// Initialize fonts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Geist&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;subsets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;latin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--font-geist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;300&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;400&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;600&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;700&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;800&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;900&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geistMono&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Geist_Mono&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;subsets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;latin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--font-geist-mono&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;300&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;400&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;600&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;700&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;800&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;900&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AI-Powered Live Chat with Intelligent Replies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Real-time chat with context-aware AI reply suggestions powered by Synthetic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nb"&gt;Readonly&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;geist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;geistMono&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font-sans&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/html&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connecting to the Chat App
&lt;/h2&gt;

&lt;p&gt;Next, we initialise the Stream client with your API Key from the app we created earlier in the Stream Dashboard above.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;app&lt;/code&gt; directory, create a file named &lt;code&gt;api/token/route.ts&lt;/code&gt; to handle user authentication for &lt;a href="https://getstream.io/chat/docs/sdk/react/" rel="noopener noreferrer"&gt;Stream’s Chat React SDK&lt;/a&gt;. Add the following code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/token/route.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamChat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_STREAM_API_KEY&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STREAM_API_SECRET&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing Stream credentials - NEXT_PUBLIC_STREAM_API_KEY or STREAM_API_SECRET not set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stream credentials not configured. Please add NEXT_PUBLIC_STREAM_API_KEY and STREAM_API_SECRET environment variables.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userId and userName are required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generating token for user:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="c1"&gt;// Use Stream official server client to create a user token&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StreamChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Ensure the user exists/updated on Stream&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;serverClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsertUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;serverClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Token generated successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Token generation error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to generate token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This API route authenticates users for the React SDK. It uses the &lt;code&gt;StreamChat.getInstance&lt;/code&gt; method to create a server-side client with your &lt;strong&gt;NEXT_PUBLIC_STREAM_API_KEY and STREAM_API_SECRET&lt;/strong&gt; (set these in your .env file from your Stream dashboard). &lt;/p&gt;

&lt;p&gt;The createToken method generates a &lt;a href="https://getstream.io/glossary/jwt/" rel="noopener noreferrer"&gt;JWT token&lt;/a&gt; for the user (from the API key), which is used to connect to Stream’s chat service. This token is returned to the client for use in &lt;code&gt;components/stream-chat-container.tsx&lt;/code&gt;. The route includes error handling for missing credentials or invalid requests to ensure secure authentication.&lt;/p&gt;

&lt;p&gt;Then, in &lt;code&gt;components/stream-chat-container.tsx&lt;/code&gt;, initialise the Stream client and connect the user like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_STREAM_API_KEY&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stream API key is not configured. Please check your environment variables.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({}))&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errorData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to get authentication token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StreamChat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to establish connection with Stream Chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nf"&gt;setClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An unknown error occurred&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stream initialization error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to initialize chat: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnectUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;components/stream-chat-container.tsx&lt;/code&gt;, the &lt;code&gt;useEffect&lt;/code&gt; hook initialises the Stream client using &lt;code&gt;StreamChat.getInstance&lt;/code&gt; with the &lt;strong&gt;NEXT_PUBLIC_STREAM_API_KEY&lt;/strong&gt;.  It fetches a user token from the &lt;code&gt;api/token/route.ts&lt;/code&gt; endpoint, passing &lt;code&gt;userId&lt;/code&gt; and &lt;code&gt;userName&lt;/code&gt; from the login form at &lt;code&gt;app/page.tsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;connectUser&lt;/code&gt; method authenticates the user with the token, establishing a real-time connection to Stream’s chat service. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;useMemo&lt;/code&gt; hook creates an &lt;em&gt;AI Smart Replies&lt;/em&gt; channel and adds the authenticated user as a member:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;team&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AI Smart Replies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;members&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;created_by_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full initialisation logic, including error handling and loading states, is in the &lt;a href="https://github.com/Tabintel/smart-chat/tree/master" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Still in the &lt;code&gt;/app&lt;/code&gt; directory, create an &lt;code&gt;app&lt;/code&gt; folder, which will have these sub-folders and files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/smart-replies/route.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateSmartReplies&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/synthetic-ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;messages array is required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateSmartReplies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Smart replies API error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to generate smart replies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Here is an overview of how the project’s directory looks:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2g22eauw3qn74r5z0ajn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2g22eauw3qn74r5z0ajn.png" alt="Project" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Chat UI
&lt;/h2&gt;

&lt;p&gt;To set up the chat UI, we start with the &lt;code&gt;&amp;lt;Chat&amp;gt;&lt;/code&gt; component we added earlier in the &lt;code&gt;stream-chat-container.tsx&lt;/code&gt; file. This wraps the interface, passing the initialised client to provide access to Stream’s chat functionality. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;Channel&amp;gt;&lt;/code&gt; component renders the active channel (created in the &lt;code&gt;useMemo&lt;/code&gt; hook), and the &lt;code&gt;&amp;lt;Window&amp;gt;&lt;/code&gt; organises the layout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamChat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ChannelHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MessageList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;MessageInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react/dist/css/v2/index.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SmartReplyBar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./smart-reply-bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Chat&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;str-chat__theme-dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Channel&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Window&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ChannelHeader&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Live Stream Chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;live&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MessageList&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SmartReplyBridge&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MessageInput&lt;/span&gt; &lt;span class="nx"&gt;focus&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Window&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Channel&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Chat&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ChannelHeader&lt;/code&gt; displays the channel name (Live Stream Chat) with a live indicator, &lt;code&gt;MessageList&lt;/code&gt; shows the conversation history, and &lt;code&gt;MessageInput&lt;/code&gt; provides a text input for sending messages. For custom styling, we use &lt;code&gt;styles/globals.css&lt;/code&gt; to define a dark theme with Tailwind CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dark&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;oklch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.145&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;foreground&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;oklch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.985&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;oklch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.985&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;foreground&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;oklch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.205&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... (Other theme variables)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The complete code for the UI is available in the &lt;a href="https://github.com/Tabintel/smart-chat/tree/master/components" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the chat UI, we adapt a &lt;a href="https://github.com/Tabintel/smart-chat/blob/master/components/discord-chat-layout.tsx" rel="noopener noreferrer"&gt;Discord-like&lt;/a&gt; feel to the interface, create a &lt;code&gt;discord-chat-layout.tsx&lt;/code&gt; file in the &lt;code&gt;/components&lt;/code&gt; directory, and enter the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamChat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MessageList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MessageInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useChatContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useChannelStateContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SmartReplyBar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./smart-reply-bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Component to bridge Stream messages to SmartReplyBar&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MessageBridge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChannelStateContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChatContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSelectReply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simplifiedMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created_at&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SmartReplyBar&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;simplifiedMessages&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onSelectReply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSelectReply&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DiscordChatLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;DiscordChatLayoutProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messagesEndRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLDivElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Auto-scroll to bottom when messages change&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;messagesEndRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;scrollIntoView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;smooth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex h-screen bg-[#1e1f22]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Sidebar */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-16 bg-[#2b2d31] flex flex-col items-center py-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Server icon and channels would go here */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Main Chat Area */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex-1 flex flex-col&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Channel Header */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h-14 border-b border-[#1e1f22] bg-[#2b2d31] flex items-center px-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-white font-semibold&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;general&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Messages */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex-1 overflow-y-auto p-4 bg-[#313338]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MessageList&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messagesEndRef&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Message Input */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p-4 border-t border-[#1e1f22] bg-[#383a40]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MessageInput&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MessageBridge&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, we adapted a Discord-style UI for the interface with a dark theme and a message display area, styled with Tailwind. We customised Stream’s built-in components, such as &lt;code&gt;MessageList&lt;/code&gt; and &lt;code&gt;MessageInput&lt;/code&gt;, to align with Discord’s aesthetic while maintaining their real-time functionality. &lt;/p&gt;

&lt;p&gt;We also added a connector component that bridges Stream’s message system with our AI Smart Reply feature. This component handles message data transformation to make sure that AI-generated replies appear seamlessly within the chat flow.&lt;/p&gt;

&lt;p&gt;Now that we have the chat set up and integrated with Stream’s React SDK, let’s set up Synthetic, which we will use for the AI smart replies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Synthetic
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://synthetic.new" rel="noopener noreferrer"&gt;Synthetic&lt;/a&gt; is an AI Inference platform that provides a solution for running open-source AI LLMs, such as Llama, Mistral, and DeepSeek.&lt;/p&gt;

&lt;p&gt;Visit the synthetic website and &lt;a href="https://synthetic.new/users/create" rel="noopener noreferrer"&gt;click on “Sign up”&lt;/a&gt; like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fli1qq29iezi7th9c0sof.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fli1qq29iezi7th9c0sof.png" alt="App" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The authentication page will then load. You can sign up using your email address or use Google social login.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoxj0uj4q7wnclc5zkfl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoxj0uj4q7wnclc5zkfl.png" alt="Code" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After signing up, you will be prompted to enter your username like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fft2euzavp9b8x7v60huz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fft2euzavp9b8x7v60huz.png" alt="AI" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, your Synthetic dashboard loads like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhf2rnqo242gvyluqkq4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhf2rnqo242gvyluqkq4u.png" alt="Build" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the icon in the top left to open the sidebar shown below, where you will get your API key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcstt8f06c6qcfi2v76rr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcstt8f06c6qcfi2v76rr.png" alt="API" width="799" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://synthetic.new/user-settings/api" rel="noopener noreferrer"&gt;API settings&lt;/a&gt;, copy your API key and add it to your environment variable file .env in the project’s codebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SYNTHETIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_api_key"&lt;/span&gt;

&lt;span class="nv"&gt;STREAM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;”your_stream_api”
&lt;span class="nv"&gt;STREAM_API_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;”your_stream_secret”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4f1p62enw8ura1uxctlt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4f1p62enw8ura1uxctlt.png" alt="Key" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this project, we will utilise the OpenAI LLM and its API URL within the codebase. You can find all the necessary information on the page shown above.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;OpenAI API Base URL&lt;/em&gt;: &lt;a href="https://api.synthetic.new/openai/v1" rel="noopener noreferrer"&gt;https://api.synthetic.new/openai/v1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are the available &lt;a href="https://dev.synthetic.new/docs/openai/models" rel="noopener noreferrer"&gt;OpenAI-compatible endpoints&lt;/a&gt; to use with the base URL from Synthetic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;/models&lt;/strong&gt; - Lists all always-on models and any recently used on-demand models&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;/chat/completions&lt;/strong&gt; - Chat-based completions with conversation history&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;/completions&lt;/strong&gt; - Traditional text completions&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;/embeddings&lt;/strong&gt; - Transforms text into vector embeddings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are integrating the chat completions feature in the app we’re building here in this guide. The chat completion from the LLM model will create a model response for a given chat conversation. The &lt;a href="https://dev.synthetic.new/docs/openai/chat-completions" rel="noopener noreferrer"&gt;Synthetic documentation&lt;/a&gt; provides a guide on how to integrate this feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmur1up57502ucxi679r0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmur1up57502ucxi679r0.png" alt="Project" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the code, we will be making a POST request to the API base URL endpoint like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;POST https://api.synthetic.new/openai/v1/chat/completions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When adding the LLM URLs, make sure they are added with this prefix, like so:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;hf:openai/gpt-oss-120b&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;hf:meta-llama/Llama-3.1-405B-Instruct&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are the Always-on &lt;a href="https://dev.synthetic.new/docs/api/models#always-on-models" rel="noopener noreferrer"&gt;models&lt;/a&gt; available on Synthetic:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86km0xe200x5amlamusd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86km0xe200x5amlamusd.png" alt="App" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use a specific LLM model by copying the model’s name to be used in the API. Learn more in the Synthetic &lt;a href="https://dev.synthetic.new/docs/api/overview" rel="noopener noreferrer"&gt;API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbh47nxt5m9wtp8awoxi6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbh47nxt5m9wtp8awoxi6.png" alt="Project" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Synthetic also supports &lt;a href="https://dev.synthetic.new/docs/api/models#lora-models" rel="noopener noreferrer"&gt;LoRA models&lt;/a&gt;, which are Low-rank adapters, called "LoRAs"; they are small, efficient fine-tunes that run on top of existing models. They can modify a &lt;a href="https://dev.synthetic.new/docs/api/models" rel="noopener noreferrer"&gt;model&lt;/a&gt; to be much more effective at specific tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flr0xw8ydxrjhsfizozq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flr0xw8ydxrjhsfizozq0.png" alt="App" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learn about other models like Embedding Models and On-Demand Models &lt;a href="https://dev.synthetic.new/docs/api/models" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; Depending on the length of the content you’re prompting using the AI inference API from Synthetic, you may be required to use a &lt;a href="https://dev.synthetic.new/docs/api/models" rel="noopener noreferrer"&gt;pay-as-you-go service&lt;/a&gt; or upgrade to a pro plan on Synthetic.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;So, don’t fret if you come across errors like this 🙂&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fetch to https://api.synthetic.new/openai/v1/chat/completions failed with status 402 and body: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;:&lt;span class="s2"&gt;"Insufficient credits and no active subscription. Due to context lengths, a minimum balance of &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;.1179648 is required to run meta-llama/Llama-3.3-70B-Instruct. Your current balance is &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;.00. Go to https://synthetic.new/billing to subscribe or purchase credits."&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

Synthetic API error &lt;span class="o"&gt;(&lt;/span&gt;HTTP &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.status&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;:&lt;span class="s2"&gt;"Insufficient credits and no active subscription. Due to context lengths, a minimum balance of &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;.1179648 is required to run meta-llama/Llama-3.3-70B-Instruct. Your current balance is &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;.00. Go to https://synthetic.new/billing to subscribe or purchase credits."&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

at generateSmartReplies &lt;span class="o"&gt;(&lt;/span&gt;/lib/synthetic-ai&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have the Synthetic setup, let’s integrate it into the codebase for the smart chats application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capturing Messages and Sending to Synthetic
&lt;/h2&gt;

&lt;p&gt;We’ll use Stream’s &lt;code&gt;message.new&lt;/code&gt; event to capture incoming messages from the chat, and then forward the message text and context (recent conversation history) to Synthetic’s inference API.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;components/stream-chat-container.tsx&lt;/code&gt;, create a component to capture messages and send them to Synthetic’s API. Add this code within &lt;code&gt;components/stream-chat-container.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SmartReplyBridge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useChannelStateContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChannelStateContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SRMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;simplified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SRMessage&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useChatContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChatContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SmartReplyBar&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;simplified&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onSelectReply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSelect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component uses Stream’s &lt;code&gt;useChannelStateContext&lt;/code&gt; hook to access the latest messages in the channel. It captures the last 10 messages, mapping them to a simplified format (SRMessage) with IDs, user IDs, user names, texts, and timestamps. &lt;/p&gt;

&lt;p&gt;These messages are passed to the component, which sends them to &lt;a href="https://www.google.com/aclk?sa=L&amp;amp;ai=DChsSEwiQheuQ7cOQAxUulVAGHT6pIcIYACICCAEQABoCZGc&amp;amp;co=1&amp;amp;gclid=Cj0KCQjwsPzHBhDCARIsALlWNG0CVUE6vDkOG4VVriTrszx7CvJbePnetCBkhlCFDwGiih3ODAqiTloaAhvXEALw_wcB&amp;amp;cid=CAASmAHkaFBTe2eIIrbU3Uf6MIIuL8urOPWhf6-9Cd-qSYvO6f9fHxh3dUx_mwVKtiZmydNO-UBRL_xu5Wujiimv5AxxR218EsgHwklH12y1YY6XQA8GW8NBnQKrm_Lr_v-yGhe26r0LBT61njnDo1Z91DmrS3t5331XglUlf0HLYIFrTw_nwaETc_AdkduO_5bEp-ogW4CRVRfnGg&amp;amp;cce=2&amp;amp;sig=AOD64_2WCY3MGoAChk6ET3Og1HOAiRZv2w&amp;amp;q&amp;amp;adurl&amp;amp;ved=2ahUKEwjJm-SQ7cOQAxVbQEEAHXlGO-UQ0Qx6BAgZEAE" rel="noopener noreferrer"&gt;Synthetic’s API&lt;/a&gt; via the &lt;code&gt;[api/smart-replies/route.ts](https://github.com/Tabintel/smart-chat/tree/master/app/api/smart-replies)&lt;/code&gt; endpoint. The &lt;code&gt;useChatContext&lt;/code&gt; hook provides access to the channel object, used later for sending replies. &lt;/p&gt;

&lt;p&gt;This setup listens for &lt;a href="https://getstream.io/chat/docs/sdk/react/components/message-components/message/" rel="noopener noreferrer"&gt;Stream’s message component&lt;/a&gt; implicitly through the reactive messages state, with real-time updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating Smart Reply Suggestions with Synthetic API
&lt;/h2&gt;

&lt;p&gt;We define a function to call &lt;a href="https://dev.synthetic.new/docs/api/overview" rel="noopener noreferrer"&gt;Synthetic’s API endpoint&lt;/a&gt; with the chat context and parse the API response to extract multiple thoughtful reply options.&lt;/p&gt;

&lt;p&gt;In your &lt;a href="https://github.com/Tabintel/smart-chat/tree/master/lib" rel="noopener noreferrer"&gt;lib directory&lt;/a&gt;, create a file named synthetic-ai.ts to integrate &lt;a href="https://dev.synthetic.new/docs/api/models" rel="noopener noreferrer"&gt;Synthetic’s LLM models&lt;/a&gt;, and enter the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SimpleMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SmartReplyResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="nx"&gt;isAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateSmartReplies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimpleMessage&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SmartReplyResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SYNTHETIC_API_KEY&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getContextualReplies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;isAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;conversationContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a smart reply assistant for a live chat application. Your job is to generate 3 short, contextually relevant, and natural reply suggestions based on the conversation.

Guidelines:
- Each reply should be 5-50 characters
- Make them casual, friendly, and conversational
- Match the tone of the conversation
- Use emojis sparingly and naturally
- Avoid generic responses
- Be authentic and human-like

Return ONLY a valid JSON array of 3 strings, nothing else. Example: ["That's awesome!", "Let's go!", "I'm down"]`&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.synthetic.new/openai/v1/chat/completions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hf:meta-llama/Llama-3.3-70B-Instruct&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Recent conversation:\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;conversationContext&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\nGenerate 3 smart reply suggestions as a JSON array.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// ... (Error handling and response parsing as provided)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synthetic API error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getContextualReplies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;isAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;generateSmartReplies&lt;/code&gt; function integrates Synthetic’s LLM model: &lt;strong&gt;hf:meta-llama/Llama-3.3-70B-Instruct&lt;/strong&gt;, to generate context-aware smart replies. &lt;/p&gt;

&lt;p&gt;It retrieves the last 10 messages, formats them within a conversation context, and sends them to Synthetic’s API endpoint: &lt;a href="https://api.synthetic.new/openai/v1/chat/completions" rel="noopener noreferrer"&gt;https://api.synthetic.new/openai/v1/chat/completions&lt;/a&gt;, along with a system prompt that defines guidelines for concise, natural replies. &lt;/p&gt;

&lt;p&gt;The API returns a JSON array of three replies, which are parsed and returned with isAI: true and the model name. If the API call fails (for example, due to a missing SYNTHETIC_API_KEY or rate limits), the &lt;code&gt;getContextualReplies&lt;/code&gt; function provides fallback replies based on the conversations.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here is the LLM system prompt in use for the chat app:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are a smart reply assistant for a live chat application. Your job is to generate 3 short, contextually relevant, and natural reply suggestions based on the conversation.&lt;span class="sb"&gt;


&lt;/span&gt;Guidelines:
&lt;span class="p"&gt;-&lt;/span&gt; Each reply should be 5-50 characters
&lt;span class="p"&gt;-&lt;/span&gt; Make them casual, friendly, and conversational
&lt;span class="p"&gt;-&lt;/span&gt; Match the tone of the conversation
&lt;span class="p"&gt;-&lt;/span&gt; Use emojis sparingly and naturally
&lt;span class="p"&gt;-&lt;/span&gt; Avoid generic responses
&lt;span class="p"&gt;-&lt;/span&gt; Be authentic and human-like&lt;span class="sb"&gt;


&lt;/span&gt;Return ONLY a valid JSON array of 3 strings, nothing else. Example: ["That's awesome!", "Let's go!", "I'm down"]&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;You can access the full code in this &lt;a href="https://github.com/Tabintel/smart-chat/blob/master/lib/synthetic-ai.ts" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Next, in &lt;code&gt;app/api/smart-replies/route.ts&lt;/code&gt;, create an API route to handle this logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateSmartReplies&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/synthetic-ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;messages array is required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateSmartReplies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Smart replies API error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to generate smart replies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This route receives messages from &lt;code&gt;SmartReplyBar&lt;/code&gt;, calls &lt;code&gt;generateSmartReplies&lt;/code&gt;, and returns the replies. The integration combines Stream’s real-time message data with Synthetic’s AI inference for dynamic suggestions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Displaying the Smart Replies in the Chat
&lt;/h2&gt;

&lt;p&gt;Next, we create a custom &lt;code&gt;SmartReplyBar&lt;/code&gt; component and render the AI reply suggestions as clickable buttons under the message input.&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;/components&lt;/code&gt; directory, create a file named &lt;code&gt;smart-reply-bar.tsx&lt;/code&gt; to display AI-generated reply suggestions, then add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SmartReplyBarProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;onSelectReply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SmartReplyBar&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onSelectReply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;SmartReplyBarProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setReplies&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isAI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsAI&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;generateReplies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateReplies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formattedMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}))&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/smart-replies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formattedMessages&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;setReplies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replies&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
        &lt;span class="nf"&gt;setIsAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isAI&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setReplies&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
        &lt;span class="nf"&gt;setIsAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setReplies&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
      &lt;span class="nf"&gt;setIsAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;SmartReplyBar&lt;/code&gt; component displays AI-generated replies as clickable Button components from the &lt;code&gt;components/ui/button library.tsx&lt;/code&gt; below the message input. &lt;code&gt;useEffect&lt;/code&gt; hook triggers &lt;code&gt;generateReplies&lt;/code&gt; when new messages arrive from Stream’s messages via &lt;code&gt;&amp;lt;SmartReplyBridge&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;generateReplies&lt;/code&gt; function sends the last five messages to the &lt;code&gt;[api/smart-replies/route.ts](https://github.com/Tabintel/smart-chat/tree/master/app/api/smart-replies)&lt;/code&gt; endpoint, which calls Synthetic’s LLM to generate replies. AI replies are then stored in state and rendered as buttons with a dynamic style. The &lt;code&gt;components/ui/button.tsx&lt;/code&gt; file defines reusable button styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttonVariants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cva&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&amp;amp;_svg]:pointer-events-none [&amp;amp;_svg:not([class*='size-'])]:size-4 shrink-0 [&amp;amp;_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-primary text-primary-foreground hover:bg-primary/90&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// ... (Other variants)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h-9 px-4 py-2 has-[&amp;gt;svg]:px-3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// ... (Other sizes)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;defaultVariants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures consistent, accessible buttons for the AI smart replies from Synthetic’s Inference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending Suggested Replies
&lt;/h2&gt;

&lt;p&gt;We use &lt;code&gt;sendMessage&lt;/code&gt; and the button click to call &lt;code&gt;channel.sendMessage()&lt;/code&gt; with the chosen reply.  The reply then gets added to the conversation instantly.&lt;br&gt;
In &lt;code&gt;components/stream-chat-container.tsx&lt;/code&gt;, the component handles sending selected replies like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useChatContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream-chat-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChatContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SmartReplyBar&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;simplified&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onSelectReply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSelect&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you click a smart reply button in &lt;code&gt;SmartReplyBar&lt;/code&gt;, the &lt;code&gt;onSelectReply&lt;/code&gt; callback triggers &lt;code&gt;handleSelect&lt;/code&gt;, which uses Stream’s &lt;code&gt;channel.sendMessage&lt;/code&gt; method to send the selected reply to the channel. This integrates &lt;a href="https://getstream.io/chat/docs/sdk/react/" rel="noopener noreferrer"&gt;Stream’s Chat React SDK&lt;/a&gt; with Synthetic’s AI-generated replies, instantly adding the reply to the conversation. The &lt;code&gt;useChatContext&lt;/code&gt; hook provides access to the channel object, ensuring that all messages in the chat are sent properly, along with smart AI-suggested replies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the App
&lt;/h2&gt;

&lt;p&gt;Now that we have set up the chat application and Synthetic API integration, let’s run the application.&lt;/p&gt;

&lt;p&gt;To start the application, open your terminal (command prompt) and enter this command &lt;code&gt;npm run dev&lt;/code&gt; like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzzj27f6a6ef4lraqnfxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzzj27f6a6ef4lraqnfxt.png" alt="App" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; and open it in your browser. The app then loads like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0kewfcxsdhv5vm10hs8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0kewfcxsdhv5vm10hs8.png" alt="Chat" width="799" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter a random user ID and Display Name, then click on &lt;strong&gt;Join Chat&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nwsdfsuzqodr5920jh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nwsdfsuzqodr5920jh6.png" alt="App" width="799" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you enter your ID and username, the app shows the “Connecting to Stream chat” loader:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ueaan17oo1ucv58e0dn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ueaan17oo1ucv58e0dn.png" alt="Chat" width="799" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chat app then loads like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbrccalkkr6u1b6p9rn2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbrccalkkr6u1b6p9rn2v.png" alt="App" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We used a Discord-like interface for the Frontend UI. Now, send a message in the chat space. When typing, you’ll notice some quick replies you can send, which are done with AI Inference using Synthetic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9tp44e4pkom4888pine.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9tp44e4pkom4888pine.png" alt="Discord" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you send messages in the app, you can see the chat logs on your Stream dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61wsglxthkwsfm5qdbme.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61wsglxthkwsfm5qdbme.png" alt="Dashboard" width="799" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloning the GitHub repository
&lt;/h3&gt;

&lt;p&gt;To get up to speed, you can also clone the &lt;a href="https://github.com/Tabintel/smart-chat" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; for the project directory, install the required dependencies, and run the chat application.&lt;/p&gt;

&lt;p&gt;Open your command terminal and run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Tabintel/smart-chat.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then navigate to this directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;smart-chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After opening the directory, run this command to install all the dependencies needed for the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--legacy-peer-deps&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the “legacy-peer-deps” command to instruct npm to ignore peer dependency conflicts during package installation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm50e4rjw766qo2oyqbbh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm50e4rjw766qo2oyqbbh.png" alt="Install" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the dependencies are installed, run the &lt;code&gt;npm run dev&lt;/code&gt; command to start the application. You can then open the '&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;' URL in your browser to test the chat app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;In this guide, we &lt;a href="https://getstream.io/blog/build-chat-messaging-app/" rel="noopener noreferrer"&gt;created a chat app&lt;/a&gt; using Stream and Synthetic, with AI-powered smart replies that save users time with one-tap responses, keep conversations active, and make your app feel more innovative and modern. &lt;/p&gt;

&lt;p&gt;You can extend the features we’ve built to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Include syncing responses across different channels. &lt;/li&gt;
&lt;li&gt;Add multiple &lt;a href="https://github.com/GetStream/Vision-Agents" rel="noopener noreferrer"&gt;AI agents&lt;/a&gt; to handle diverse use cases, such as customer support, marketplaces, or workplace chat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy coding! 🎉&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Speed up your designs to functional web apps with Kombai</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Wed, 03 Dec 2025 19:20:52 +0000</pubDate>
      <link>https://dev.to/envitab/-16n1</link>
      <guid>https://dev.to/envitab/-16n1</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/envitab/from-figma-to-production-how-i-optimized-frontend-development-time-by-70-2obk" class="crayons-story__hidden-navigation-link"&gt;From Figma to Production: How I Optimized Frontend Development Time by 70%&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/envitab" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F823697%2F01838b0b-d454-4382-ad1b-75d036ea50dd.jpg" alt="envitab profile" class="crayons-avatar__image" width="298" height="277"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/envitab" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ekemini Samuel
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ekemini Samuel
                
              
              &lt;div id="story-author-preview-content-2901919" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/envitab" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F823697%2F01838b0b-d454-4382-ad1b-75d036ea50dd.jpg" class="crayons-avatar__image" alt="" width="298" height="277"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ekemini Samuel&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/envitab/from-figma-to-production-how-i-optimized-frontend-development-time-by-70-2obk" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 7 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/envitab/from-figma-to-production-how-i-optimized-frontend-development-time-by-70-2obk" id="article-link-2901919"&gt;
          From Figma to Production: How I Optimized Frontend Development Time by 70%
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/kombai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;kombai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/envitab/from-figma-to-production-how-i-optimized-frontend-development-time-by-70-2obk" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;14&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/envitab/from-figma-to-production-how-i-optimized-frontend-development-time-by-70-2obk#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>ai</category>
      <category>kombai</category>
    </item>
    <item>
      <title>The Best AI Agent For Frontend - Kombai</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Mon, 24 Nov 2025 09:31:33 +0000</pubDate>
      <link>https://dev.to/envitab/the-best-ai-agent-for-frontend-kombai-2033</link>
      <guid>https://dev.to/envitab/the-best-ai-agent-for-frontend-kombai-2033</guid>
      <description>&lt;p&gt;AI Agents have evolved a lot from when I started using them in 2022 till now. If you pay close attention, you will notice that these agents are growing not just in capabilities, that is, the kind of task they can do, but in understanding, which in my opinion is far important than the type of tasks they do. I say this for two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An AI agent is as good as the person using it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An AI agent with a deep understanding of a particular context or subject matter will outperform generalistic AI agents long-term.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you look at this from a wide view, the same applies to humans. A person who has developed knowledge, understanding and expertise in one area, for example, Finance, has built not just skill but discipline over an extended period of time. Say that person wants to learn Product design in 6 months, it won’t be as difficult for someone who has not first focused on a particular skill.&lt;/p&gt;

&lt;p&gt;Now to the main purpose of this article. In the coding space, there are lots of AI coding agents and assistants like v0, Cursor, Claude Code, Lovable, Bolt, and Anything. These AI agents can write code and build websites, web apps and mobile apps quite alright. However, if you examine the UI/UX and Frontend, you will notice that they are similar. Either in terms of colour gradients, fonts, typography, framework (mostly &lt;a href="http://next.js/Vue" rel="noopener noreferrer"&gt;Next.js/Vue&lt;/a&gt;) and even more technical aspects like state management.&lt;/p&gt;

&lt;p&gt;They get the job done, yes, but if you want an actual product you and other people will use, then you definitely have to be unique and quite technical (or you get a technical friend) to help you out.&lt;/p&gt;

&lt;p&gt;I have been using the Kombai AI agent for some time now, specifically for the Frontend of my web application, and in this guide, I will show you why it’s the best AI Agent for frontend development while building the frontend of a bookstore application.&lt;/p&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along, you need these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Basic understanding of JavaScript.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NPM Installed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An IDE like Windsurf, VS Code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Kombai account.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kombai is an AI agent built specifically for frontend development. It converts Figma designs, images, or text into production-ready code with modern web frameworks like React, Next.js, and Tailwind. With Kombai, you have the flexibility to choose the tech stack or framework you want to work with.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learn more about Kombai in this&lt;/em&gt; &lt;a href="https://kombai.com/why/" rel="noopener noreferrer"&gt;&lt;em&gt;resource&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Kombai Does (and Doesn’t Do)
&lt;/h2&gt;

&lt;p&gt;Before we build, it is important to define the scope. To claim Kombai is the "best," we must clarify what it is best &lt;em&gt;at&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kombai is a specialised Frontend AI Agent.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; It specialises in UI, layout, client-side logic, and component architecture. It is designed to work safely within production repositories, understanding your specific design system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What it doesn’t do:&lt;/strong&gt; It is not a backend or database agent. It won't configure your AWS servers or write complex SQL queries.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By focusing entirely on the frontend, Kombai achieves a level of fidelity in CSS, React state, and accessibility that generalist full-stack agents simply cannot match.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes Kombai the best AI coding agent for frontend
&lt;/h2&gt;

&lt;p&gt;Many AI tools can generate code, but their underlying architectures differ. General-purpose models are trained on broad datasets, while some newer agents are highly specialised. This specialisation is key, as the agent's architecture is purpose-built for a specific domain, in this case, frontend development. This focused approach is evident in its core technology.&lt;/p&gt;

&lt;p&gt;Let's break down the specific features that result from this specialisation. Here are some reasons why Kombai is different from other AI agents:&lt;/p&gt;

&lt;h3&gt;
  
  
  Specialised Context Engineering
&lt;/h3&gt;

&lt;p&gt;Instead of generating a generic &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, the agent can read a project's theme.js file and output a custom &lt;code&gt;&amp;lt;StyledButton variant="primary"&amp;gt;&lt;/code&gt;. This is the result of &lt;em&gt;specialised context engineering.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before generating code, the system scans the repository to learn its structure. It identifies the component library, ingests design system tokens (colours, fonts, spacing), and understands existing custom components. The goal is to produce code that is idiomatic to the specific project, helping it align with established patterns and reducing the need for manual refactoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Planning and Preview Workflow
&lt;/h3&gt;

&lt;p&gt;The Kombai AI workflow is designed to be more transparent than a simple prompt-to-code process. It operates in stages. First, it generates a step-by-step plan in markdown, detailing &lt;em&gt;how&lt;/em&gt; it will build the feature, for example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create UserProfileCard.jsx&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add props for userName and avatarUrl&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Style using Tailwind CSS&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Frontend developer can then review and edit this plan before execution. Once approved, the system generates the code and presents it in a live preview within an isolated browser sandbox. This allows for a verification step to catch runtime issues or styling problems before committing the code to the local branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Figma Interpretation Engine
&lt;/h3&gt;

&lt;p&gt;A common challenge for design-to-code tools is handling imperfect design files if layers aren't clearly grouped.&lt;/p&gt;

&lt;p&gt;Kombia’s AI agent's interpretation engine is designed to be more resilient, directly interpreting and converting the Figma design files to code. This approach effectively converts design frames into responsive components with logical DOM structures, rather than relying on pixel-based or absolute positioning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-in RAG for Frontend Libraries
&lt;/h3&gt;

&lt;p&gt;Another key technology at play is (RAG) &lt;a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation" rel="noopener noreferrer"&gt;Retrieval-Augmented Generation&lt;/a&gt;. Many general-purpose models may "hallucinate" or use outdated syntax when trying to implement a specific library, as their knowledge is based on a static, broad training dataset.&lt;/p&gt;

&lt;p&gt;A RAG-based system addresses this by connecting the language model to an external, up-to-date knowledge base. Here’s how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retrieval:&lt;/strong&gt; When you ask the agent to build a component using, say, Chakra UI, it doesn't just rely on its general knowledge. It first &lt;em&gt;retrieves&lt;/em&gt; relevant, up-to-date information and code patterns from its own curated and human-tested database of over 30 frontend libraries (like Tailwind, MUI, Shadcn/ui).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generation&lt;/strong&gt;: It then &lt;em&gt;augments&lt;/em&gt; the prompt. It essentially tells its AI model:&amp;nbsp;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;“Here is the user's request, and here is the correct, best-practice pattern for doing this in Chakra UI. Now, generate the code."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This RAG-based approach improves the model's output in verified patterns. It's not &lt;em&gt;remembering&lt;/em&gt; how to use a library; it's &lt;em&gt;referencing&lt;/em&gt; a correct implementation in real-time. This method is designed to improve accuracy and ensure the generated code follows the best practices for that specific library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enterprise-Ready Security
&lt;/h3&gt;

&lt;p&gt;For enterprise teams that use AI coding agents, security is non-negotiable. Kombai has these security features implemented from the beginning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No Training on Your Code:&lt;/strong&gt; Kombai does not train its base models on your private codebase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SOC 2 Compliance:&lt;/strong&gt; It adheres to strict security standards, making it safe for enterprise deployment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local Context:&lt;/strong&gt; It builds context from your local repo to answer questions, keeping data boundaries respectful.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Pricing &amp;amp; Plans
&lt;/h3&gt;

&lt;p&gt;Kombai offers a &lt;a href="https://kombai.com/pricing/" rel="noopener noreferrer"&gt;pricing tier&lt;/a&gt; for every stage of development:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Free Plan:&lt;/strong&gt; Great for students and indie devs. Includes a set amount of credits/month to build small projects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pro Plan:&lt;/strong&gt; Designed for solo founders and freelancers who need higher limits and faster processing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enterprise:&lt;/strong&gt; For teams that need SSO, enhanced security, and custom context limits.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Building the Bookstore app with Kombai
&lt;/h2&gt;

&lt;p&gt;We will be building a bookstore web app with authentication, book listings, a dashboard, and a checkout flow.&lt;/p&gt;

&lt;p&gt;First, let’s set up Kombai in your IDE (Windsurf is the IDE used in this article)&lt;/p&gt;

&lt;p&gt;Create your &lt;a href="https://kombai.com/" rel="noopener noreferrer"&gt;Kombai account&lt;/a&gt;, sign in and install the extension for your IDE, whether it’s VS Code, Cursor, Windsurf or Trae.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo954ui1ks1mqaaq5vxu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo954ui1ks1mqaaq5vxu8.png" alt="app" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re using Windsurf, when you click on the Kombai icon in the left sidebar, it opens like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwoim24het80f5opvmb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwoim24het80f5opvmb4.png" alt="app" width="799" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the right, you can read about the latest updates to the Kombai AI agent and learn how to use the features in your IDE.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Open Folder&lt;/strong&gt; to open a folder or workspace and start using Kombai.&lt;/p&gt;

&lt;p&gt;In your computer, create a folder and name it “bookstore”. Then open it in the Windsurf IDE with Kombai.&lt;/p&gt;

&lt;p&gt;When you start the Kombai extension, it scans your workspace for context awareness. It loads and shows you this modal regarding the &lt;strong&gt;Plan Mode&lt;/strong&gt;. This mode generates a detailed code plan for the task (from your prompts) before the AI agent starts generating the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksm86qg4k4llmyt8qrmv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksm86qg4k4llmyt8qrmv.png" alt="app" width="800" height="674"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that Kombai is set up, let’s proceed with building the bookstore app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Planning before generating code
&lt;/h3&gt;

&lt;p&gt;With most AI tools, the flow for building apps is mostly from text/prompt to action.&amp;nbsp; When you prompt the AI tool, it immediately starts generating the code for the application. While that is faster, it does not give room to plan the task properly, which is helpful for making iterations during frontend development.&lt;/p&gt;

&lt;p&gt;Kombai uses a more professional workflow: &lt;strong&gt;Plan, Code and Ask.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plan:&lt;/strong&gt; You describe the feature. Kombai generates a detailed markdown plan. This makes sure planning is done before any code generation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; You review and approve the plan. Kombai then executes it, generating files and fixing its own errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ask:&lt;/strong&gt; You can ask questions about your repo without changing code.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the Kombai chat, enter this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;We will be building the frontend of a modern bookstore app. Let's plan the bookstore app and UI.
List all required screens, components, and user states for authentication, dashboard, checkout, and book browsing. 
Use React.js + Tailwind + TypeScript. Focus on clean visual hierarchy and modularity.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;You can also instruct Kombai to turn your designs into code by either entering a link to the Figma file or directly connecting Figma by clicking on the&lt;/em&gt; &lt;strong&gt;&lt;em&gt;Connect Figma&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;button in the image.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4darlu6bgd2wxzz31ca6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4darlu6bgd2wxzz31ca6.png" alt="coding" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before you send the prompt, click on the &lt;strong&gt;Code&lt;/strong&gt; dropdown and select &lt;strong&gt;Plan&lt;/strong&gt;. This will create a structured plan before coding.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fraarqdcpyixv7wujynsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fraarqdcpyixv7wujynsw.png" alt="app" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you have selected the Plan mode, send the prompt to Kombai. It then lists the tech stack that will be used. You can either let Kombai AI scan the workspace for any files, configure the tech stack to be used and then proceed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8hjptrpa00i7uj1eulc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8hjptrpa00i7uj1eulc.png" alt="app" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now click on &lt;strong&gt;Proceed&lt;/strong&gt;. The AI agent then prepares a comprehensive plan, including all of these. Scroll down the &lt;a href="http://plan.md" rel="noopener noreferrer"&gt;&lt;em&gt;plan.md&lt;/em&gt;&lt;/a&gt; document or click on the file icon at the top right to expand the view.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design philosophy:&lt;/strong&gt; For the design philosophy, the AI agent suggests the visual identity as a modern, clean, editorial-inspired design with a focus on book discovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14gjlsdvwyafk3kj6w84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14gjlsdvwyafk3kj6w84.png" alt="code" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Screen architecture:&lt;/strong&gt; The screen architecture shows the screens for the bookstore app, the routes where authentication is required, and the purpose of each screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxy4sgjher0rmp5lnuq29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxy4sgjher0rmp5lnuq29.png" alt="app" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Component breakdown:&lt;/strong&gt; The AI agent also lists all the components needed, like the layout, authentication, book components, shopping, dashboard, the shared UI, and other components needed in the bookstore app
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsx2wpx7f46kmn1bprnt2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsx2wpx7f46kmn1bprnt2.png" alt="app" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User state management:&lt;/strong&gt; Next is the state management, which includes the state domains, storage, key data and type definitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv378r0j3mfd1n5gxfdyv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv378r0j3mfd1n5gxfdyv.png" alt="app" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication flow:&lt;/strong&gt; The AI agent also creates a visual representation of the authentication flow, from when the user visits the bookstore app, authenticates, adds a book to the cart and checks out. It also includes the Auth States: idle, loading, authenticated, error.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8g7u3ey83976doaq1ehx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8g7u3ey83976doaq1ehx.png" alt="code" width="799" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checkout user journey:&lt;/strong&gt; Next, we have the checkout user journey, which includes the steps, screens, actions and validation. The AI agent also includes a progress indicator and a stepper component (1/6 → 6/6).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feludt3rlniee7imaxhna.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feludt3rlniee7imaxhna.png" alt="code" width="799" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browse page layout:&lt;/strong&gt; There is also the browse page layout, like the header (search, cart, user), filters sidebar, the book genre, price for each book and ratings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lypp1xxag1uftjcux6t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lypp1xxag1uftjcux6t.png" alt="app" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reusable patterns:&lt;/strong&gt; The AI agent also lists reusable patterns, their usage and props to be used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65gdh18i2qgo16ifuobm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65gdh18i2qgo16ifuobm.png" alt="coding" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility checklist:&lt;/strong&gt; It also adds the accessibility checklist, like ARIA labels for icon buttons,&amp;nbsp; Alt text for all book covers, and screen reader announcements for cart updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foen5seonedcou1iskcx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foen5seonedcou1iskcx8.png" alt="app" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance optimisations:&lt;/strong&gt; The AI Agent also lists the performance optimisations with the strategies and how they will be implemented in the bookstore application.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvoj6l2bw25a0ucb6q7y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvoj6l2bw25a0ucb6q7y.png" alt="app" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above metrics are important when developing the frontend of a web application, and it’s necessary to consider them before building. Instead of going directly to generating code.&lt;/p&gt;

&lt;p&gt;The Kombai AI agent also lists the Todos (10) needed for the bookstore web app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw1u32j13ra0whxs7sq9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw1u32j13ra0whxs7sq9.png" alt="app" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you’re okay with the plan the AI agent generated, click on &lt;strong&gt;Approve Plan&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftse87yjxcne4gls46aq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftse87yjxcne4gls46aq6.png" alt="coding" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI agent now begins creating the bookstore application with the plan we approved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the bookstore project
&lt;/h3&gt;

&lt;p&gt;The Kombai AI agent begins creating the application and installing the needed dependencies with the npm command. Click &lt;strong&gt;Run&lt;/strong&gt; like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98c0r16sqo41f2neehbv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98c0r16sqo41f2neehbv.png" alt="code" width="799" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While installing the dependencies, the AI agent loads the terminal to the right side and continues generating the code for the bookstore app. It lists every file it’s creating and the purpose for each one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fau81dquzqe4ctb6shm7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fau81dquzqe4ctb6shm7w.png" alt="app" width="799" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the AI agent is done setting up the bookstore app project and generating the code files, it then shows a prompt to run the app with the &lt;em&gt;npm run dev&lt;/em&gt; command. Click on the &lt;strong&gt;Run&lt;/strong&gt; button as shown in the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzovfo6hzzm1vegmuxfn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzovfo6hzzm1vegmuxfn1.png" alt="code" width="799" height="645"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the AI agent runs the bookstore application, it provides a local URL: &lt;a href="http://localhost:5173" rel="noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt; you can open in your browser to test the application. Also, you can open the preview in the Kombai Browser by clicking on the &lt;strong&gt;Kombai Browser&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cap2qt64pr50lqb7j9c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cap2qt64pr50lqb7j9c.png" alt="code" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bookstore app then loads in the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flekyzpqtrmzi4bbz70qm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flekyzpqtrmzi4bbz70qm.png" alt="app" width="799" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can view all the books displayed like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkz6hym5ulo1b8inn9tt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkz6hym5ulo1b8inn9tt.png" alt="books" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have the frontend of the bookstore developed with the Kombai AI agent. Next, we will check the agent-controlled and user-controlled browser features of Kombai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent-controlled and User-controlled Browsers
&lt;/h2&gt;

&lt;p&gt;The Kombai team shipped an update recently called the Kombai browser, which transforms how AI-assisted Frontend development works. It involves an &lt;a href="https://docs.kombai.com/features/agent-controlled-browser#agent-controlled-browser" rel="noopener noreferrer"&gt;Agent-controlled&lt;/a&gt; and &lt;a href="https://docs.kombai.com/features/user-controlled-browser" rel="noopener noreferrer"&gt;User-controlled browser&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Let’s go deeper into the capabilities and use cases of each browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent-controlled Browser
&lt;/h3&gt;

&lt;p&gt;The idea for the Agent-controlled browser is simple: instead of you running your app and manually checking everything, the agent can open your app and use it like a real user.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch&lt;/em&gt; &lt;a href="https://vimeo.com/1136800408?fl=pl&amp;amp;fe=sh" rel="noopener noreferrer"&gt;&lt;em&gt;this video&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to see the Kombai Agent-controlled browser in action.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It can navigate, click, type, scroll, and perform actions on the web application.&lt;/p&gt;

&lt;p&gt;For the bookstore app we’re building, I asked the Agent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Open the browser, sign up, log in, and add a book to your cart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the Agent-controlled browser, it loaded the bookstore app, created an accreated a new account, then logged in and added a book to the cart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wrc1egu5h8j06cpr0pq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wrc1egu5h8j06cpr0pq.png" alt="app" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the AI agent adds the book to the cart, it displays like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzc40sj2c15zgzaiannp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzc40sj2c15zgzaiannp.png" alt="code" width="798" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI agent also provides a summary of all the actions it took on the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb519rofg7751kzki11xd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb519rofg7751kzki11xd.png" alt="code" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here is a video showing how the Agent-controlled browser works when we give it the prompt&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/zLnw5dpNs9A"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  User-controlled Browser
&lt;/h3&gt;

&lt;p&gt;The Kombai User-controlled browser lets you preview your app’s local deployment in the browser (optimised for Google Chrome and Chromium-based browsers), with built-in listeners so you can iterate fast by sending elements and errors back to Kombai as context.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;To open the preview, follow these steps:&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Start Preview&lt;/strong&gt; button below the Kombai input box. Kombai will start the dev server.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feogh1s8q31vy7j4kw8np.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feogh1s8q31vy7j4kw8np.png" alt="code" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the server is running, click the &lt;strong&gt;Kombai Browser&lt;/strong&gt; button to open the browser preview. Alternatively, press the Kombai Browser button that appears after the code generation process is completed.&lt;/p&gt;

&lt;p&gt;You can also prompt the AI agent to make changes to the web app in real-time using the chat window, as shown in the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju3b0ipsqs234umohhzx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju3b0ipsqs234umohhzx.png" alt="code" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use the select elements button highlighted below to select any section that needs an update. This makes implementing additional changes faster using the user-controlled browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp575x3kry0x65cwt4z4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp575x3kry0x65cwt4z4.png" alt="Bookstore app" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also initialise a GitHub repository directly with the AI agent by giving it a prompt and clicking on the &lt;strong&gt;Run&lt;/strong&gt; button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Initialise a GitHub repository, git add. git commit, and git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpiflp0mqln7zss67gj65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpiflp0mqln7zss67gj65.png" alt="code" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can clone the &lt;a href="https://github.com/Tabintel/bookstore" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;, install the required dependencies and run the bookstore application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Tabintel/bookstore.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;bookstore
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it! We have built the frontend of a bookstore application with Kombai and tested it out using the Agent and User-controlled browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;The major takeaway from developing the frontend of applications with Kombai is that the AI agent works as an AI coding “partner”, not a plugin for an IDE. You can make updates to the bookstore by adding a community feature where other users can post comments on books they buy.  &lt;/p&gt;

&lt;p&gt;Optimise your frontend development today, start building with &lt;a href="https://kombai.com/" rel="noopener noreferrer"&gt;Kombai&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>ai</category>
      <category>webdev</category>
      <category>kombai</category>
    </item>
    <item>
      <title>Analysing European Soccer Data with Deepnote in Windsurf IDE</title>
      <dc:creator>Ekemini Samuel</dc:creator>
      <pubDate>Tue, 11 Nov 2025 19:09:43 +0000</pubDate>
      <link>https://dev.to/envitab/analysing-european-soccer-data-with-deepnote-in-windsurf-ide-3275</link>
      <guid>https://dev.to/envitab/analysing-european-soccer-data-with-deepnote-in-windsurf-ide-3275</guid>
      <description>&lt;p&gt;Word on the street? Deepnote is now open-source! &lt;a href="https://www.linkedin.com/in/jakubjurovych?miniProfileUrn=urn%3Ali%3Afsd_profile%3AACoAAAR4ktcB_f_6g8BjwHyezwhafD-MGu1Q2t4&amp;amp;lipi=urn%3Ali%3Apage%3Ad_flagship3_detail_base%3B7GVTyObBRBipRQJeb2yTkw%3D%3D" rel="noopener noreferrer"&gt;Jakub Jurových&lt;/a&gt;, the Founder of Deepnote, &lt;a href="https://www.linkedin.com/posts/jakubjurovych_were-open-sourcing-the-successor-to-the-activity-7391476816688918528-3WvY/?utm_source=share&amp;amp;utm_medium=member_desktop&amp;amp;rcm=ACoAADVQjhABmJaZyg3Z4FlWL6moWpeWb7YODoo" rel="noopener noreferrer"&gt;announced on LinkedIn&lt;/a&gt; this week. &lt;/p&gt;

&lt;p&gt;During my research, I found out that the European Soccer Database from &lt;a href="https://www.kaggle.com/datasets/hugomathien/soccer" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt; has 25K+ matches, 10K+ players, and 2008–2016 seasons across 11 leagues. In this guide will use Deepnote in Windsurf to analyse the European sports data from Kaggle for three use cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To find out which team improved most post-2012 from the data?&lt;/li&gt;
&lt;li&gt;To predict "star players" and their value, with the Top 10 players by goal impact&lt;/li&gt;
&lt;li&gt;To analyse win rates, goal differences, and home/away advantages over seasons for the teams&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Let's get started!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You will need the following to proceed&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Kaggle account&lt;/li&gt;
&lt;li&gt;Windsurf account and IDE installed on your computer&lt;/li&gt;
&lt;li&gt;A Deepnote account&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Deepnote?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://deepnote.com/" rel="noopener noreferrer"&gt;Deepnote&lt;/a&gt; is a cloud-based, collaborative data science notebook platform that enhances Jupyter notebooks with real-time co-editing, integrated data connections, and simplified sharing, acting as a central workspace for teams to develop and deploy data-driven products. It supports Python, R, and SQL&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgavp3yqz156ce3ivgivi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgavp3yqz156ce3ivgivi.png" alt="Deepnote" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get started, we will download the European soccer database from Kaggle. Click on the &lt;strong&gt;Download&lt;/strong&gt; button like so&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fagnqypovsd2knwotm72u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fagnqypovsd2knwotm72u.png" alt="Kaggle" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on &lt;strong&gt;Download dataset as zip&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3y1bp1ut1y2j77ybcpq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3y1bp1ut1y2j77ybcpq.png" alt="dataset" width="667" height="765"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the dataset is downloaded on your computer, extract it to your preferred directory. For this project, I created a directory on my computer called &lt;em&gt;windsurf_deepnote&lt;/em&gt;. You can do the same and extract the dataset to that specific folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foynxuekpox6ch2s57vpw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foynxuekpox6ch2s57vpw.png" alt="directory" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the dataset is extracted, you get a file named &lt;code&gt;database.sqlite&lt;/code&gt; in the directory.&lt;/p&gt;

&lt;p&gt;Now that we have the dataset, lets setup Windsurf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Windsurf IDE
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://windsurf.com/" rel="noopener noreferrer"&gt;Windsurf&lt;/a&gt; is an integrated development environment (IDE) for coding. It is similar to VS Code, with AI-first features.&lt;/p&gt;

&lt;p&gt;Go to the Windsurf website, and download the application suited for your operating system (OS).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk9alq3bdqaoudf0cdd8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk9alq3bdqaoudf0cdd8.png" alt="Windsurf" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After it has downloaded, sign up with your preferred method, and open the Windsurf IDE with the directory you want to work with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qr1ct2sf6volz87rtjn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qr1ct2sf6volz87rtjn.png" alt="Project" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also view your Windsurf account activity in the &lt;a href="https://windsurf.com/profile" rel="noopener noreferrer"&gt;profile section&lt;/a&gt; like so&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevduyuf0lgd0vujszke5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevduyuf0lgd0vujszke5.png" alt="Code" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Deepnote in Windsurf
&lt;/h2&gt;

&lt;p&gt;Now we have the dataset and Windsurf setup, let's proceed to working with Deepnote in Windsurf.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://deepnote.com/" rel="noopener noreferrer"&gt;Sign up here&lt;/a&gt; to create your Deepnote account&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the Windsurf IDE, go to the extensions tab at the left sidebar, search for &lt;em&gt;Deepnote&lt;/em&gt; and click on the &lt;strong&gt;Install&lt;/strong&gt; button like so&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscs9agv2zc20qbeaid4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscs9agv2zc20qbeaid4c.png" alt="Deepnote" width="760" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After it has installed, this tab loads. It has information on how to get started using Deepnote in Windsurf&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqn9cibd8tq7smzdwb20.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqn9cibd8tq7smzdwb20.png" alt="Deepnote" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, use the &lt;code&gt;Ctrl+Shift+P&lt;/code&gt; command to create a new Deepnote project, and name it &lt;code&gt;soccer&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5lszmr5fmb1x1vkeq7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5lszmr5fmb1x1vkeq7d.png" alt="soccer" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It then creates a new project called &lt;code&gt;soccer&lt;/code&gt; and loads a new Deepnote block, just as in the Deepnote web app UI&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi51m4jax48zl3s8o90m5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi51m4jax48zl3s8o90m5.png" alt="Deepnote" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We are using Kaggle to get the &lt;a href="https://www.kaggle.com/datasets/hugomathien/soccer" rel="noopener noreferrer"&gt;football/soccer data&lt;/a&gt; that we will be analysing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can enter python code in blocks and run like so&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68prvphifbkpf7knsxcd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68prvphifbkpf7knsxcd.png" alt="Deepnote" width="799" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to convert a notebook to a deepnote notebook, open your terminal and run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @deepnote/convert notebook.ipynb &lt;span class="c"&gt;# This will convert the notebook and create notebook.deepnote&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Enter yes (y) for the prompts that come up during installation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After it has been installed, Then open your &lt;code&gt;.deepnote&lt;/code&gt; file in Windsurf IDE.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the data analysis of the three use Cases
&lt;/h2&gt;

&lt;p&gt;From the soccer dataset, we will run each of this Python codes to analyse the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Find Teams That Improved Most Post-2012
&lt;/h3&gt;

&lt;p&gt;We will find which teams improved most after 2012, by comparing average team performance (win rate, goals) before and after 2012&lt;/p&gt;

&lt;p&gt;Enter this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;

&lt;span class="c1"&gt;# Connect to the SQLite database
&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;database.sqlite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Load match data
&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_sql_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
SELECT
    m.id, m.season, m.home_team_api_id, m.away_team_api_id,
    m.home_team_goal, m.away_team_goal,
    t.team_long_name AS home_team
FROM Match m
JOIN Team t ON m.home_team_api_id = t.team_api_id
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Compute win rate per team per season
&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home_team_goal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;away_team_goal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; 
              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home_team_goal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;away_team_goal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;team_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home_team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;season&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;value_counts&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;unstack&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Split pre- and post-2012 averages
&lt;/span&gt;&lt;span class="n"&gt;pre_2012&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_level_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;season&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2012/2013&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home_team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;post_2012&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_level_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;season&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2012/2013&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home_team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;win_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;improvement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_2012&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pre_2012&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Visualize
&lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;improvement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Top 10 Most Improved Teams Post-2012&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Win Rate Improvement&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After we run the code block above, we get this visualization:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fon7i1f953bi8hj6gq8zt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fon7i1f953bi8hj6gq8zt.png" alt="dataset" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the data, we confirm that Juventus showed the biggest leap after 2012, improving their win rate by +30%.&lt;/p&gt;

&lt;p&gt;Borussia Mönchengladbach and AS Saint-Étienne followed closely, both gaining around 25%, proving that the post-2012 era rewarded better management, new ownership money, and tactics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Predict Star Players by Goal Impact
&lt;/h3&gt;

&lt;p&gt;Next we predict star players and rank the Top 10 by goal impact score, by combining player attributes and match stats, then using regression to find impact.&lt;/p&gt;

&lt;p&gt;Enter this python code block and run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.linear_model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LinearRegression&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;database.sqlite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Load player data with names
&lt;/span&gt;&lt;span class="n"&gt;players&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_sql_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    SELECT p.player_api_id, p.player_name, 
           pa.overall_rating, pa.potential 
    FROM Player p
    JOIN Player_Attributes pa ON p.player_api_id = pa.player_api_id
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Load match data
&lt;/span&gt;&lt;span class="n"&gt;goals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_sql_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    SELECT home_player_1, home_player_2, home_player_3, home_team_goal 
    FROM Match
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Aggregate goals by player
&lt;/span&gt;&lt;span class="n"&gt;player_goals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;goals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;melt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;value_vars&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_player_1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_player_2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_player_3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;var_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;value_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;player_api_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;player_goals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;player_goals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;player_api_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goal_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Merge with player data and calculate impact score
&lt;/span&gt;&lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;player_api_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;player_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_goals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;player_api_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inner&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Model goal impact
&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;overall_rating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;potential&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goal_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LinearRegression&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;impact_score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Get top 10 players
&lt;/span&gt;&lt;span class="n"&gt;top_players&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;impact_score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Plot with player names
&lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;barh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_players&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;player_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;top_players&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;impact_score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;orange&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Top 10 Star Players by Goal Impact Score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pad&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Predicted Impact Score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Player Name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gca&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;invert_yaxis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the code, we get this visualisation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs71sgpqy4dbrwe75icia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs71sgpqy4dbrwe75icia.png" alt="data" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the data, we confirm that Iker Casillas  tops the list as the star player with the highest predicted goal-impact score (~41), followed closely by Sergio Ramos, Manuel Neuer, and Petr Čech. John Terry also rounds out the top spot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Win Rate &amp;amp; Home Advantage Analysis
&lt;/h3&gt;

&lt;p&gt;Then we analyse win rates, goal differences, and home/away advantages across seasons. We do this by comparing home vs away wins by season and visualize trends.&lt;/p&gt;

&lt;p&gt;Run this python code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;

&lt;span class="c1"&gt;# Connect to the database
&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;database.sqlite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Load match data with team names
&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_sql_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    SELECT 
        m.season,
        t1.team_long_name as home_team,
        t2.team_long_name as away_team,
        m.home_team_goal,
        m.away_team_goal
    FROM Match m
    JOIN Team t1 ON m.home_team_api_id = t1.team_api_id
    JOIN Team t2 ON m.away_team_api_id = t2.team_api_id
    WHERE m.season IS NOT NULL
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Calculate match results
&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_win&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;draw&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_win&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Calculate win rates per team
&lt;/span&gt;&lt;span class="n"&gt;home_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_win&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_win&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wins&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goals_for&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goals_against&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;away_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_win&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_win&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wins&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goals_for&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_team_goal&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goals_against&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Combine home and away stats
&lt;/span&gt;&lt;span class="n"&gt;team_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;home_stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;away_stats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total_matches&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;home_team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;value_counts&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;away_team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;value_counts&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;fill_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;win_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wins&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total_matches&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goal_difference&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goals_for&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goals_against&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Get top 10 teams by win rate
&lt;/span&gt;&lt;span class="n"&gt;top_teams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;team_stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nlargest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;win_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;win_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Plotting
&lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Create bar plot
&lt;/span&gt;&lt;span class="n"&gt;bars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;barh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_teams&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;win_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;skyblue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Top 10 Teams by Win Rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fontsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pad&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Win Rate (%)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fontsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fontsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add value labels
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_width&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
             &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_y&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_height&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
             &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;va&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="n"&gt;fontsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linestyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Save the plot
&lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;savefig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team_win_rates.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bbox_inches&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tight&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dpi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Top 10 Teams by Win Rate:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_teams&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;win_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total_matches&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goal_difference&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;win_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Plot saved as &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team_win_rates.png&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the code, we get the visualization of the dataset like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqtb3yp47obwlsdg799j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqtb3yp47obwlsdg799j.png" alt="data" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We confirm that FC Barcelona dominates with a 77% overall win rate across the entire period, followed by Real Madrid (75%) and SL Benfica (74.6%). The data shows the classic home advantage of roughly 55–60% win rate for home teams throughout 2008–2016.&lt;/p&gt;

&lt;p&gt;And that's it! We have a great analysis of the soccer data!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Show the Deepnote some support and star the &lt;a href="https://github.com/deepnote/deepnote" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; !😎&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdqhnyj6yk8ybawrsj5q.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdqhnyj6yk8ybawrsj5q.jpeg" alt="Deepnote" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>data</category>
      <category>deepnote</category>
      <category>opensource</category>
      <category>soccer</category>
    </item>
  </channel>
</rss>
