<?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: Daniel Chifamba</title>
    <description>The latest articles on DEV Community by Daniel Chifamba (@dchif).</description>
    <link>https://dev.to/dchif</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2172230%2Fdabeb61f-c9b2-4b03-b059-6ecf4989eb64.jpeg</url>
      <title>DEV Community: Daniel Chifamba</title>
      <link>https://dev.to/dchif</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dchif"/>
    <language>en</language>
    <item>
      <title>One Command to Check How Ready Your System Is for Local AI</title>
      <dc:creator>Daniel Chifamba</dc:creator>
      <pubDate>Sun, 24 Aug 2025 09:44:28 +0000</pubDate>
      <link>https://dev.to/dchif/one-command-to-check-how-ready-your-system-is-for-local-ai-16e6</link>
      <guid>https://dev.to/dchif/one-command-to-check-how-ready-your-system-is-for-local-ai-16e6</guid>
      <description>&lt;p&gt;If you’re running local AI tools like &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;, &lt;a href="https://jan.ai/" rel="noopener noreferrer"&gt;Jan&lt;/a&gt;, &lt;a href="https://lmstudio.ai/" rel="noopener noreferrer"&gt;LM Studio&lt;/a&gt; or &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;, one of the first things you’ll want to know is whether your GPU is up to the task. VRAM size, compute capability, and driver support all play a big role in whether models will run smoothly (or crash out of memory).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A neat shortcut&lt;/strong&gt;: if you have &lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; installed, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;--yes&lt;/span&gt; node-llama-cpp inspect gpu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though this command comes from &lt;a href="https://node-llama-cpp.withcat.ai/" rel="noopener noreferrer"&gt;node-llama-cpp&lt;/a&gt;, the output is universally useful. It quickly reports your OS, GPU, CPU, RAM, and driver metrics— that apply no matter which local AI framework you’re using.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sample Output&lt;/em&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%2F02d30x0rzsna0qgfs887.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%2F02d30x0rzsna0qgfs887.png" alt="Sample output of AI metrics" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this quick check, you’ll know exactly what your machine can handle, and can better choose the right models and settings for your local AI experiments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>terminal</category>
      <category>ollama</category>
      <category>tips</category>
    </item>
    <item>
      <title>Making a Browser That Slips Past WiFi Captive Portals — and Why This Loophole Isn’t Worth It</title>
      <dc:creator>Daniel Chifamba</dc:creator>
      <pubDate>Wed, 13 Aug 2025 09:54:20 +0000</pubDate>
      <link>https://dev.to/dchif/making-a-browser-that-slips-past-wi-fi-captive-portals-and-why-this-loophole-isnt-worth-it-13o</link>
      <guid>https://dev.to/dchif/making-a-browser-that-slips-past-wi-fi-captive-portals-and-why-this-loophole-isnt-worth-it-13o</guid>
      <description>&lt;p&gt;Here's something fun — a loophole in WiFi captive portals that lets you &lt;strong&gt;surf the internet using a special web browser even if you haven't logged in&lt;/strong&gt; yet. But before you pack up your laptop and head for free WiFi...there’s a catch! 😜 &lt;/p&gt;

&lt;p&gt;You've seen it before - you connect to a coffee shop hotspot or hotel internet, and you're instantly met with the familiar "Please log in or pay" page. Until you do, every site you visit redirects right back to that portal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most people assume captive portals completely block internet access until you've paid up or agreed to the terms.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But there's a small, odd exception buried under the hood...&lt;/strong&gt; This isn't a "free internet" hack, and it won't stream Netflix, but it's a technical side effect of how the internet's plumbing works.&lt;/p&gt;

&lt;p&gt;I built a &lt;em&gt;functioning, text-only web browser&lt;/em&gt; that works entirely on top of this exception and I'll show you &lt;strong&gt;&lt;em&gt;how&lt;/em&gt;&lt;/strong&gt; it works, &lt;strong&gt;&lt;em&gt;why&lt;/em&gt;&lt;/strong&gt; captive portals sometimes allow it, and &lt;strong&gt;&lt;em&gt;why&lt;/em&gt;&lt;/strong&gt; you probably shouldn't rely on it for anything important.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you prefer to jump to checking the code and running the browser, you can find the completed code here: &lt;a href="https://github.com/nadchif/dnSurfer" rel="noopener noreferrer"&gt;https://github.com/nadchif/dnSurfer&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Really Happening Behind That Wi-Fi Login Page
&lt;/h2&gt;

&lt;p&gt;When you connect to public Wi-Fi, you're not immediately on the open internet. You're placed inside a restricted network, with a captive portal controlling what traffic gets through. Until you log in or accept the terms, it appears that every request you make is intercepted and redirected back to that same login page.&lt;/p&gt;

&lt;p&gt;But here's the interesting part — before your browser can load &lt;code&gt;example.com&lt;/code&gt;, it first has to ask a &lt;a href="https://www.cloudflare.com/learning/dns/what-is-dns/" rel="noopener noreferrer"&gt;DNS (Domain Name System)&lt;/a&gt; server for that site's IP address.&lt;/p&gt;

&lt;p&gt;And sometimes, the captive portal doesn't block that lookup step.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They assume DNS is harmless&lt;/strong&gt; ("it's just phone numbers for websites"). 🤷‍♂️&lt;/li&gt;
&lt;li&gt;Blocking DNS can break their own login page for people using a custom DNS setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: &lt;strong&gt;even though your web requests (HTTP/HTTPS) are locked away, plain old DNS queries can still slip out into the open internet&lt;/strong&gt;. And because DNS can carry little snippets of text (via &lt;a href="https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/" rel="noopener noreferrer"&gt;TXT&lt;/a&gt; records), clever folks have figured out you can string those snippets together and turn DNS into a tiny — but working — &lt;strong&gt;data tunnel&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So naturally, I had to build one to see if this actually worked in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning DNS Into a Web Browser
&lt;/h2&gt;

&lt;p&gt;The DNS browsing setup requires two parts.&lt;/p&gt;

&lt;p&gt;When you type a URL in the DNS Browser, it doesn't make a normal HTTP request. Instead, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Encodes the URL&lt;/strong&gt; into Base64 so it can fit inside valid DNS labels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Splits the encoded data&lt;/strong&gt; into chunks small enough for a DNS query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sends each chunk&lt;/strong&gt; as part of a DNS &lt;a href="https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/" rel="noopener noreferrer"&gt;TXT&lt;/a&gt; query to your custom DNS server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://github.com/nadchif/dnSurfer/tree/main/server" rel="noopener noreferrer"&gt;DNS server&lt;/a&gt; — running somewhere outside the captive portal — decodes those labels, fetches the actual webpage from the real internet, strips it down to plain text or Markdown, and splits it into small pieces. It sends each piece back as a &lt;a href="https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/" rel="noopener noreferrer"&gt;TXT&lt;/a&gt; record. The &lt;a href="https://github.com/nadchif/dnSurfer/tree/main/desktop-client" rel="noopener noreferrer"&gt;Electron app (Browser)&lt;/a&gt; then stitches those chunks together and renders them in the window.&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%2Fsjpc87avb79xkoymx522.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%2Fsjpc87avb79xkoymx522.png" alt="Overview flow of how system works"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This works because captive portals that leak DNS will let these &lt;a href="https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/" rel="noopener noreferrer"&gt;TXT&lt;/a&gt; queries and responses pass before login — meaning you can sneak tiny bits of webpage content back to your device.&lt;/p&gt;

&lt;p&gt;To see the difference, let's look at the actual network traffic using &lt;a href="https://www.wireshark.org/" rel="noopener noreferrer"&gt;Wireshark&lt;/a&gt;. I visited a page from &lt;a href="https://news.ycombinator.com" rel="noopener noreferrer"&gt;https://news.ycombinator.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Packets captured when we loaded the URL from Chrome showed:&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%2F2ba8770ke9wijf0c2oh8.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.amazonaws.com%2Fuploads%2Farticles%2F2ba8770ke9wijf0c2oh8.jpg" alt="Wireshark screenshot of Chrome packets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While packets captured when we loaded the same URL from our DNS browser showed:&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%2Fp4pycsk8f8lzh6d0pz4q.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.amazonaws.com%2Fuploads%2Farticles%2Fp4pycsk8f8lzh6d0pz4q.jpg" alt="Wireshark screenshot of dnSurfer packets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the difference: our DNS browser relied solely on the DNS protocol (over UDP port 53), while Chrome used TCP connections for HTTPS (TLSv1.2).&lt;/p&gt;

&lt;p&gt;The trick is simple: only DNS is used, and DNS is still open. No magic here, folks 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Loophole Isn't Worth It
&lt;/h2&gt;

&lt;p&gt;Sure, it's fun, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero HTTPS protection&lt;/strong&gt;. Everything travels in plain text, so anyone between you and the DNS server can read every page you load. It's basically broadcasting your activity in public, leaving you susceptible to MITM attacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bare-bones browsing&lt;/strong&gt;. Forget forms, images, or anything interactive this is text-only, and even that's slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not dependable&lt;/strong&gt;. Some captive portals already block DNS traffic, and those that don't might close the gap at any time. (The network admins read posts like this too, apparently 😅) &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Think of it like this:&lt;/strong&gt; if your neighbor locks the front door but leaves a small window open, it doesn't mean you should climb in. &lt;strong&gt;If you can put together a system like this, you can probably also manage the far simpler task of paying for that coffee or grabbing a Wi-Fi pass.&lt;/strong&gt; 😉&lt;/p&gt;

&lt;p&gt;Treat dnSurfer as a curiosity — a safe way to explore how protocols behave under unusual conditions — &lt;strong&gt;not a replacement for normal browsing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The full source code (including chunking logic, multi-page navigation, and rendering) is in the repo: &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nadchif" rel="noopener noreferrer"&gt;
        nadchif
      &lt;/a&gt; / &lt;a href="https://github.com/nadchif/dnSurfer" rel="noopener noreferrer"&gt;
        dnSurfer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A DNS-only web browser — browse even when HTTP(S) is blocked.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;dnSurfer Suite&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;A web browser that surfs entirely over DNS&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/nadchif/dnSurfer/blob/main/screenshots/loaded%20page.png?raw=true"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fnadchif%2FdnSurfer%2Fraw%2Fmain%2Fscreenshots%2Floaded%2520page.png%3Fraw%3Dtrue" alt="Preview screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dnSurfer&lt;/strong&gt; is a proof-of-concept browser that operates entirely over DNS, using a client–server connection to turn DNS queries into text-only web content.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;DNS Browser (Desktop/Electron)&lt;/strong&gt; — A text-only browser that fetches pages entirely over DNS TXT records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom DNS Server (Node.js)&lt;/strong&gt; — Serves stripped-down web pages as DNS responses.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No HTTPS. No TCP. Just DNS queries slipping past wifi captive portals.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Quick Start – How to Run the Browser&lt;/h1&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Pre-requisites&lt;/h4&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en/download" rel="nofollow noopener noreferrer"&gt;Node.js 20+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/" rel="nofollow noopener noreferrer"&gt;Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Custom DNS Server &lt;a href="https://github.com/nadchif/dnSurfer/server/README.md" rel="noopener noreferrer"&gt;(See how to setup server)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h4 class="heading-element"&gt;Steps&lt;/h4&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Clone this project&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/nadchif/dnSurfer.git&lt;/pre&gt;

&lt;/div&gt;

&lt;ol start="2"&gt;
&lt;li&gt;Change directory to the cloned folder&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;cd&lt;/span&gt; dnSurfer&lt;/pre&gt;

&lt;/div&gt;

&lt;ol start="3"&gt;
&lt;li&gt;Launch Browser&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run browser&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How It Works&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Best explained in the blog post I published &lt;a href="https://dev.to/dchif/making-a-browser-that-slips-past-wi-fi-captive-portals-and-why-this-loophole-isnt-worth-it-13o" rel="nofollow"&gt;here&lt;/a&gt;&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Bundling / Compiling Executable&lt;/h2&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Change directory to the desktop client folder:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;cd desktop-client
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Install dependencies&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Build app&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;npm run dist:mac
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;npm run dist:win
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;&lt;li&gt;…&lt;/li&gt;&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nadchif/dnSurfer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  High-level code breakdown
&lt;/h3&gt;

&lt;p&gt;For those who want the technical details:&lt;/p&gt;

&lt;h4&gt;
  
  
  Server Side (Node.js):
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;We use the &lt;a href="https://www.npmjs.com/package/dns2" rel="noopener noreferrer"&gt;dns2&lt;/a&gt; library to create a UDP server listening on port 53&lt;/li&gt;
&lt;li&gt;When TXT query requests come in, we extract the Base64-encoded URL from the DNS query&lt;/li&gt;
&lt;li&gt;Decode the Base64 to get the original URL, then fetch that page using standard HTTP&lt;/li&gt;
&lt;li&gt;Convert the HTML response to clean Markdown using the &lt;a href="https://www.npmjs.com/package/turndown" rel="noopener noreferrer"&gt;turndown&lt;/a&gt; library&lt;/li&gt;
&lt;li&gt;Store the converted content in &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; with a 5-minute TTL for caching&lt;/li&gt;
&lt;li&gt;Split the Markdown response into chunks small enough for DNS TXT records&lt;/li&gt;
&lt;li&gt;Return each chunk with metadata like &amp;lt;|1/4|&amp;gt; indicating chunk number and total chunks&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Client Side (Electron):
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Create a minimal browser interface with a URL input field&lt;/li&gt;
&lt;li&gt;When user submits a URL, encode it to Base64 and split into DNS-safe chunks&lt;/li&gt;
&lt;li&gt;Use Node.js &lt;a href="https://nodejs.org/api/dgram.html" rel="noopener noreferrer"&gt;dgram&lt;/a&gt; to create UDP connections for DNS queries&lt;/li&gt;
&lt;li&gt;Send TXT queries using the syntax: ..dns.me&lt;/li&gt;
&lt;li&gt;Parse responses to extract chunk metadata (&amp;lt;|1/4|&amp;gt;, &amp;lt;|2/4|&amp;gt;, etc.)&lt;/li&gt;
&lt;li&gt;Continue querying until all chunks are received&lt;/li&gt;
&lt;li&gt;Reassemble the chunks, convert Markdown back to HTML using the marked package&lt;/li&gt;
&lt;li&gt;Render the final HTML in the Electron webview&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole system essentially turns DNS into a slow but functional HTTP proxy, with the server doing the heavy lifting and the client just reassembling text chunks.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you're responsible for one of these captive portal setups, this is probably something to look into. While most people won't build custom DNS browsers, it's still an unintended hole that's worth plugging.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  If you're into this kind of weirdness, check out:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ch.at/" rel="noopener noreferrer"&gt;Ch.at&lt;/a&gt; (chat with an LLM over DNS)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://links.twibright.com/" rel="noopener noreferrer"&gt;Links&lt;/a&gt; (browser in terminal)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gophie.org/" rel="noopener noreferrer"&gt;Gophie&lt;/a&gt; (browse in &lt;a href="https://wiki.archlinux.org/title/Gopher" rel="noopener noreferrer"&gt;gopher&lt;/a&gt;, alternative to http protocol)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>electron</category>
      <category>cybersecurity</category>
      <category>networking</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Real-Time Transcriber &amp; Translator to 100+ languages</title>
      <dc:creator>Daniel Chifamba</dc:creator>
      <pubDate>Sun, 24 Nov 2024 14:39:20 +0000</pubDate>
      <link>https://dev.to/dchif/real-time-transcriber-translator-to-100-languages-4n0b</link>
      <guid>https://dev.to/dchif/real-time-transcriber-translator-to-100-languages-4n0b</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/assemblyai"&gt;AssemblyAI Challenge &lt;/a&gt;: Really Rad Real-Time.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built a platform where users can start a live stream in english, and listeners can join to receive real-time translations in over 100 different languages. &lt;/p&gt;

&lt;p&gt;This could be is great for a multi-lingual conference or gathering where the speaker is speaking, and the audience see the subtitle-like  translations in a language of their choice. &lt;/p&gt;

&lt;p&gt;When the stream ends, the broadcaster can also download an audio recording as well as text transcript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Demo Site: &lt;a href="https://gtreach.vercel.app" rel="noopener noreferrer"&gt;https://gtreach.vercel.app&lt;/a&gt;
&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%2Fk4v4dyta7d9q9012c3ew.gif" 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%2Fk4v4dyta7d9q9012c3ew.gif" alt="Image description" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client Code: &lt;a href="https://github.com/nadchif/gt-reach-ui" rel="noopener noreferrer"&gt;https://github.com/nadchif/gt-reach-ui&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Server Code: &lt;a href="https://github.com/nadchif/gt-reach-server" rel="noopener noreferrer"&gt;https://github.com/nadchif/gt-reach-server&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;After familiarizing myself more with AssemblyAI documentation, I was really excited to give this project a go! &lt;/p&gt;

&lt;p&gt;This has always been something I've wanted to do, but I didn't really know where to begin. When I saw how fast and accurate the AssemblyAI transcriber is, this helped me make progress. &lt;br&gt;
I could focus on the other pieces of the platform and leave AssemblyAI to do its magic 🪄 🧃&lt;/p&gt;

&lt;p&gt;I'm excited about the AssemblyAI future and I hope they can support &lt;a href="https://www.assemblyai.com/docs/getting-started/transcribe-streaming-audio-from-a-microphone/typescript" rel="noopener noreferrer"&gt;more languages&lt;/a&gt; for transcribing in the future. I'd like to be able to what a movie in another language and have live subtitles on the fly 😎&lt;/p&gt;

&lt;p&gt;This has been a journey I'll remember 🙂 &lt;/p&gt;

&lt;h3&gt;
  
  
  Price categories
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Really Rad Real-Time&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>assemblyaichallenge</category>
      <category>ai</category>
      <category>api</category>
    </item>
    <item>
      <title>Fun Flashcards Game for Kids using AI Speech Recognition</title>
      <dc:creator>Daniel Chifamba</dc:creator>
      <pubDate>Fri, 22 Nov 2024 06:59:37 +0000</pubDate>
      <link>https://dev.to/dchif/fun-flashcards-game-for-kids-using-ai-speech-recognition-4ip7</link>
      <guid>https://dev.to/dchif/fun-flashcards-game-for-kids-using-ai-speech-recognition-4ip7</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/assemblyai"&gt;AssemblyAI Challenge &lt;/a&gt;: Really Rad Real-Time.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I made a fun math game for kids with math flashcards. Kids answer by speaking, and the game uses &lt;a href="//assemblyai.com/"&gt;AI Powered Speech Recognition&lt;/a&gt; to check their answers.&lt;/p&gt;

&lt;p&gt;It has three levels of difficulty and a setting to turn sound effects on or off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Live: &lt;a href="https://mathwave.vercel.app" rel="noopener noreferrer"&gt;https://mathwave.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;screenshot 1&lt;/th&gt;
&lt;th&gt;screenshot 2&lt;/th&gt;
&lt;th&gt;screenshot 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Fd7mzmpxvdfa0n7qtzl6w.png" alt="Image description" width="800" height="1644"&gt;&lt;/td&gt;
&lt;td&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%2Fk5hyyomp0sdbadce583n.png" alt="Image description" width="800" height="1644"&gt;&lt;/td&gt;
&lt;td&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%2Fmnwav38e2q4ue978zkrv.png" alt="Image description" width="800" height="1644"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;I had fun hacking this one out. Initially setup most of the game using the native browser Speech Recognition API. That was an eye opener as I noted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The recognition was rather poor. I assumed it was my voice so I had other people try it out. They too, seemed to end up frustrated as it was failing to recognize the right answer. &lt;/li&gt;
&lt;li&gt;A lot of code and handling of edge cases was required&lt;/li&gt;
&lt;li&gt;Results are not consistent across browsers. (ahem...Safari 😅)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then came AssemblyAI to the rescue 🛟 🌊 &lt;br&gt;
The (HUGE) difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Very easy to setup, very few lines of code&lt;/li&gt;
&lt;li&gt;Consistent experience across browser&lt;/li&gt;
&lt;li&gt;Awesome speech recognition from kid's voices to adult voices, even where it was a bit noisy 🔥&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Oh...and the Docs and Cookbook were on point 🙂 I found a perfect example pretty close to what I wanted to do &lt;a href="https://github.com/AssemblyAI-Community/realtime-react-example" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I simply followed this example to come up with &lt;a href="https://github.com/nadchif/mathwave-ui/blob/main/src/hook/useRecognition.ts#L63C1-L142C6" rel="noopener noreferrer"&gt;the code&lt;/a&gt; that transcribes the answers by voice&lt;/p&gt;

&lt;p&gt;The rest of the Source Code can be found here: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/nadchif/mathwave-ui" rel="noopener noreferrer"&gt;https://github.com/nadchif/mathwave-ui&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Price categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Really Rad Real-Time&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>assemblyaichallenge</category>
      <category>ai</category>
      <category>api</category>
    </item>
    <item>
      <title>Run Your Offline AI Chat: Pure Browser, No Backend</title>
      <dc:creator>Daniel Chifamba</dc:creator>
      <pubDate>Thu, 10 Oct 2024 14:18:01 +0000</pubDate>
      <link>https://dev.to/dchif/run-your-offline-ai-chat-assistant-pure-browser-zero-backend-1e48</link>
      <guid>https://dev.to/dchif/run-your-offline-ai-chat-assistant-pure-browser-zero-backend-1e48</guid>
      <description>&lt;p&gt;Imagine running a ChatGPT-like AI right here in your browser - completely offline. No server needed, no API calls, just pure browser power. Sounds impossible? Not anymore! Modern browsers have evolved into incredibly capable platforms, and I'm excited to show you just what they can do. Together, we will build a &lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React.js&lt;/a&gt; web app that you can chat with, even while you're offline&lt;/p&gt;

&lt;p&gt;In this guide, we'll go through three options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Quick Start Approach&lt;/strong&gt;. A &lt;a href="https://private-ai-chat.vercel.app/" rel="noopener noreferrer"&gt;ready-to-use version&lt;/a&gt; that works offline after downloading your chosen LLM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Local Development Path&lt;/strong&gt;. Running &lt;a href="https://github.com/nadchif/in-browser-llm-inference" rel="noopener noreferrer"&gt;the source code&lt;/a&gt; locally on your own browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building from the Ground Up&lt;/strong&gt;. Making a similar project from scratch using &lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Boring...😴" Okay, If you're eager to jump straight into implementation, feel free to skip to "Running your own local models" 😜&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The AI revolution has transformed how we work, and for good reason. Consider this: while it might take a person 20-30 minutes to read and summarize a book, an AI can accomplish the same task in seconds. Many of us have embraced tools like &lt;a href="https://chatgpt.com/" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt;, &lt;a href="https://gemini.google.com/" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt;, &lt;a href="https://claude.ai/" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; and &lt;a href="https://x.com/i/grok" rel="noopener noreferrer"&gt;Grok&lt;/a&gt; for this very reason. But what if you want these capabilities without sharing your data with cloud services? &lt;/p&gt;

&lt;p&gt;Thanks to the open-source AI community, we have many LLMs (Large Language Models) that are publicly available. Take the &lt;a href="https://developers.googleblog.com/en/introducing-gemma-3-270m/" rel="noopener noreferrer"&gt;latest LLMs released by Google&lt;/a&gt; for example, they are compact enough to run on-device and the good news is they are available for anyone to use.  But here's the thing - once you've got these models in your hands, how do you actually use them locally?&lt;/p&gt;

&lt;p&gt;Popular solutions like &lt;a href="https://ollama.ai/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; and &lt;a href="https://lmstudio.ai/" rel="noopener noreferrer"&gt;LMStudio&lt;/a&gt; offer impressive local AI capabilities, making it possible for you to run a model on your device. Ollama offers a nice CLI chat, while LMStudio provides a polished GUI experience. Both support API integration for custom applications. But today, we're exploring something cool - running these models directly in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running your own local models in your browser without a backend
&lt;/h2&gt;

&lt;p&gt;What makes this approach special is that we're running LLM models directly in your browser - no backend required. The concept is straightforward: download a model file once, then run inference locally, right in your browser. The capabilities of modern browsers are truly remarkable.&lt;/p&gt;

&lt;p&gt;This magic is possible thanks to WebAssembly, which allows us to leverage powerful C/C++ tools like llama.cpp. &lt;/p&gt;

&lt;p&gt;Drum-roll please 🥁... &lt;/p&gt;

&lt;p&gt;Introducing &lt;a href="https://github.com/ngxson/wllama" rel="noopener noreferrer"&gt;Wllama&lt;/a&gt; - an impressive project that provides WebAssembly bindings for &lt;a href="https://github.com/ggerganov/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt;, enabling seamless in-browser LLM inference. &lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Quick Start Approach 🚀
&lt;/h3&gt;

&lt;p&gt;Visit the pre-built demo site: &lt;a href="https://private-ai-chat.vercel.app/" rel="noopener noreferrer"&gt;https://private-ai-chat.vercel.app/&lt;/a&gt; and experience it yourself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose your preferred model from the dropdown&lt;/li&gt;
&lt;li&gt;Type your prompt&lt;/li&gt;
&lt;li&gt;Hit enter&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;That's all it takes&lt;/strong&gt;. The page downloads your selected model directly to your device (browser cache), and from there, everything runs locally. Want proof? Try disconnecting from the internet or check your browser's network tab - no external calls, just pure local processing power at work.&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%2F5v3e6hso271g624xzarp.gif" 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%2F5v3e6hso271g624xzarp.gif" alt="Preview of llm inference" width="760" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ready to explore more models? Head over to &lt;a href="https://huggingface.co/models?library=gguf&amp;amp;pipeline_tag=text-generation" rel="noopener noreferrer"&gt;HuggingFace's GGUF model collection&lt;/a&gt;. You'll find a vast array of models ready for use - just download any GGUF file (up to 2GB) and load it through the dropdown.&lt;/p&gt;

&lt;p&gt;Here are a few to get you started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/unsloth/gemma-3-270m-it-GGUF/resolve/main/gemma-3-270m-it-Q8_0.gguf" rel="noopener noreferrer"&gt;Gemma 3 270M&lt;/a&gt; by Google DeepMind &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/lmstudio-community/SmolLM2-135M-Instruct-GGUF/blob/main/SmolLM2-135M-Instruct-Q8_0.gguf" rel="noopener noreferrer"&gt;SmolLM2 135M&lt;/a&gt; by HuggingFace &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/unsloth/Qwen3-0.6B-GGUF/resolve/main/Qwen3-0.6B-Q4_K_M.gguf?download=true" rel="noopener noreferrer"&gt;Qwen 3 0.6B&lt;/a&gt; by Alibaba&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF/resolve/main/llama-3.2-1b-instruct-q4_k_m.gguf" rel="noopener noreferrer"&gt;Llama 3.2 1B&lt;/a&gt; by Meta&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"That's nice, but I want to run it locally myself", I hear someone say. Sure...I got you covered 🙂 &lt;/p&gt;

&lt;h3&gt;
  
  
  The Local Development Path 🏠
&lt;/h3&gt;

&lt;p&gt;Let's set up your own local instance.&lt;/p&gt;

&lt;h4&gt;
  
  
  What You'll Need:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Basic HTML, Javascript, and CSS knowledge&lt;/li&gt;
&lt;li&gt;Web browser &lt;a href="https://caniuse.com/wasm" rel="noopener noreferrer"&gt;with WebAssembly support&lt;/a&gt;. (I'm betting yours has 🤞😁)&lt;/li&gt;
&lt;li&gt;Node.js installed. You can get a copy here and follow the installation steps.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Steps:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/nadchif/in-browser-llm-inference.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install dependencies:
&lt;/li&gt;
&lt;/ol&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;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nt"&gt;-browser-llm-inference&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that it will attempt to download a model during installation to be used as the default option when you run the web app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Launch the web app:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Navigate to &lt;a href="http://localhost:5173/" rel="noopener noreferrer"&gt;http://localhost:5173/&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Have fun!! 😊&lt;/p&gt;

&lt;h3&gt;
  
  
  Building from the Ground Up 💪
&lt;/h3&gt;

&lt;p&gt;Now, let's demystify what is really going on by building our own implementation from scratch.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Create a React App with Vite
&lt;/h4&gt;

&lt;p&gt;First, let's set up our development environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create vite@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name your project (we'll use "browser-llm-chat")&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;React&lt;/code&gt; as your framework&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;JavaScript&lt;/code&gt; as your variant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then initialize your project:&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;browser-llm-chat
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Design the Basic UI
&lt;/h4&gt;

&lt;p&gt;Let's create a clean, functional interface. Replace the contents of &lt;code&gt;App.jsx&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;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="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPrompt&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;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setOutput&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoading&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;progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setProgress&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="mi"&gt;0&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;handlePromptInputChange&lt;/span&gt; &lt;span class="o"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setPrompt&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;shouldDisableSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitPrompt&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="c1"&gt;// We'll implement this next&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;output&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;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&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;role&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;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;output&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;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;%&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hi, How may I help you?&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handlePromptInputChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Enter your prompt here"&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submitPrompt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;shouldDisableSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;→&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add some basic styling by replacing the contents of &lt;code&gt;index.css&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pre-wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-moz-pre-wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-pre-wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-o-pre-wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#aaa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f2f2f2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60vw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40vw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;640px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&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;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%2Fc5zghp18lvao0bnzyoif.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%2Fc5zghp18lvao0bnzyoif.png" alt="Output preview" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Integrate Wllama
&lt;/h4&gt;

&lt;p&gt;Now for the exciting part - let's add AI capabilities to our application:&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; @wllama/wllama @huggingface/jinja
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your &lt;code&gt;App.jsx&lt;/code&gt; to integrate Wllama:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;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="p"&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;Wllama&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;@wllama/wllama/esm/wllama&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;wllamaSingleJS&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;@wllama/wllama/src/single-thread/wllama.js?url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;wllamaSingle&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;@wllama/wllama/src/single-thread/wllama.wasm?url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;Template&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;@huggingface/jinja&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;wllama&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Wllama&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;single-thread/wllama.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wllamaSingleJS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;single-thread/wllama.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wllamaSingle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="cm"&gt;/* You can find more models at HuggingFace: https://huggingface.co/models?library=gguf
 * You can also download a model of your choice and place it in the /public folder, then update the modelUrl like this:
 * const modelUrl = "/&amp;lt;your-model-file-name&amp;gt;.gguf";
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modelUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://huggingface.co/lmstudio-community/SmolLM2-135M-Instruct-GGUF/blob/main/SmolLM2-135M-Instruct-Q8_0.gguf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* See more about templating here:
* https://huggingface.co/docs/transformers/main/en/chat_templating
*/&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formatChat&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;messages&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;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wllama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChatTemplate&lt;/span&gt;&lt;span class="p"&gt;()&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&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;bos_token&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;wllama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detokenize&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;wllama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBOS&lt;/span&gt;&lt;span class="p"&gt;()]),&lt;/span&gt;
    &lt;span class="na"&gt;eos_token&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;wllama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detokenize&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;wllama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEOS&lt;/span&gt;&lt;span class="p"&gt;()]),&lt;/span&gt;
    &lt;span class="na"&gt;add_generation_prompt&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// previous state declarations...&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitPrompt&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;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="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;wllama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isModelLoaded&lt;/span&gt;&lt;span class="p"&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;wllama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadModelFromUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;n_threads&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="na"&gt;useCache&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="na"&gt;allowOffline&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="na"&gt;progressCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progress&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;setProgress&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&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="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;promptObject&lt;/span&gt; &lt;span class="o"&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="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;setOutput&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;promptObject&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;wllama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;formatChat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;promptObject&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;nPredict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sampling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;penalty_repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;onNewToken&lt;/span&gt;&lt;span class="p"&gt;:&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;piece&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setOutput&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;promptObject&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;assistant&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;text&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;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="c1"&gt;// rest of existing code...&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="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations! Nicely done 👏 At this point, you have a functioning AI chat interface running entirely in your 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%2F0voy243aovdl20ybrff5.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%2F0voy243aovdl20ybrff5.png" alt="Preview of your output" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's review what we just did.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We imported and initialized an instance of &lt;a href="https://www.npmjs.com/package/@wllama/wllama" rel="noopener noreferrer"&gt;Wllama&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We added the &lt;a href="https://huggingface.co/lmstudio-community/SmolLM2-135M-Instruct-GGUF/blob/main/SmolLM2-135M-Instruct-Q8_0.gguf" rel="noopener noreferrer"&gt;URL&lt;/a&gt; that will be used to download the model initially.&lt;/li&gt;
&lt;li&gt;We set up Chat Templating. Learn more about that 👉 &lt;a href="https://huggingface.co/docs/transformers/main/en/chat_templating" rel="noopener noreferrer"&gt;Chat Templating Documentation&lt;/a&gt; and &lt;a href="https://github.com/ngxson/wllama/tree/master/examples/main" rel="noopener noreferrer"&gt;Extended Wllama Examples&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your web app will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download and cache the model on first use so it works completely offline after that.&lt;/li&gt;
&lt;li&gt;Process prompts locally, in your browser, using your CPU&lt;/li&gt;
&lt;li&gt;Stream responses as they're generated&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're not already running the web app, start it using &lt;code&gt;npm run dev&lt;/code&gt; and visit &lt;a href="http://localhost:5173/" rel="noopener noreferrer"&gt;http://localhost:5173/&lt;/a&gt;. Test it out, and celebrate 🎉👏 &lt;/p&gt;

&lt;h3&gt;
  
  
  Current Limitations 😅
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Currently CPU-only (no WebGPU support yet)&lt;/li&gt;
&lt;li&gt;2GB file size limit for models, though there's a workaround to split them (see the &lt;a href="https://github.com/ngxson/wllama?tab=readme-ov-file#split-model" rel="noopener noreferrer"&gt;Wllama documentation&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A Huge Thanks To 👏
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ngxson/wllama" rel="noopener noreferrer"&gt;Wllama&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/models/gemma/" rel="noopener noreferrer"&gt;Gemma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/HuggingFaceTB" rel="noopener noreferrer"&gt;SmolLm - HuggingFace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.llama.com/" rel="noopener noreferrer"&gt;Llama 3.2 - Meta&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cool Similar Projects to Check Out 😎
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/spaces/Xenova/realtime-whisper-webgpu" rel="noopener noreferrer"&gt;Realtime Whisper WebGPU&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webllm.mlc.ai/" rel="noopener noreferrer"&gt;WebLLM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference/web_js" rel="noopener noreferrer"&gt;Google Edge AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/spaces/ngxson/wllama" rel="noopener noreferrer"&gt;Wllama Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hrishioa/wasm-ai" rel="noopener noreferrer"&gt;WASM-AI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready for a challenge? (Pick any)&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update your copy of the code and improve the styling/CSS&lt;/li&gt;
&lt;li&gt;Implement the ability for a user to pick a model file on their device. See: &lt;a href="https://github.com/nadchif/in-browser-llm-inference/blob/072335af3af22bfdb3483292e3d6aea2073f770f/src/App.jsx#L47" rel="noopener noreferrer"&gt;Example&lt;/a&gt; and &lt;a href="https://github.ngxson.com/wllama/docs/classes/Wllama.html#loadModel" rel="noopener noreferrer"&gt;Wllama documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The chat output is currently shown as plain text. To enhance the visual presentation, integrate a markdown library like &lt;a href="https://remarkjs.github.io/react-markdown/" rel="noopener noreferrer"&gt;react-markdown&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Share your outcome - let's celebrate your win! 🏆 🕺&lt;/strong&gt; &lt;/p&gt;

</description>
      <category>ai</category>
      <category>react</category>
      <category>webassembly</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Run a Virtual Machine in Your Browser</title>
      <dc:creator>Daniel Chifamba</dc:creator>
      <pubDate>Sun, 06 Oct 2024 01:18:24 +0000</pubDate>
      <link>https://dev.to/dchif/run-a-virtual-machine-in-your-browser-2kjk</link>
      <guid>https://dev.to/dchif/run-a-virtual-machine-in-your-browser-2kjk</guid>
      <description>&lt;p&gt;We're going to run a complete virtual machine right here in your browser! And not just run the VM - we'll boot up both FreeDOS and Alpine Linux. "Wait, what? We already have VirtualBox, VMware, and DOSBox for that stuff!" &lt;strong&gt;Well, who needs VirtualBox when you have a web browser?&lt;/strong&gt; (Just kidding! 😄)&lt;/p&gt;

&lt;p&gt;But seriously, let's have some fun showcasing just how powerful modern browsers have become, especially now that they support &lt;a href="https://webassembly.org/" rel="noopener noreferrer"&gt;WebAssembly&lt;/a&gt; (Wasm). Sure, traditional VM software is great, but being able to run an entire operating system in your browser? That's just cool! 🚀&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you prefer to jump to the finished product, you can find the completed code here &lt;a href="https://github.com/nadchif/in-browser-virtual-machine" rel="noopener noreferrer"&gt;https://github.com/nadchif/in-browser-virtual-machine&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;In this guide, we'll use &lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React.js&lt;/a&gt;, but I'm keeping things generic so you can adapt everything to plain HTML or whatever framework makes you happy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Basic HTML, Javascript, and CSS knowledge&lt;/li&gt;
&lt;li&gt;Web browser with &lt;a href="https://caniuse.com/wasm" rel="noopener noreferrer"&gt;WebAssembly support&lt;/a&gt;. Don't worry, the browser you're using right now will likely work 😊&lt;/li&gt;
&lt;li&gt;Node.js installed. You can get a copy &lt;a href="https://nodejs.org/en/download/package-manager" rel="noopener noreferrer"&gt;here&lt;/a&gt; and follow the installation steps.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Okay, Let's Build This Thing!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Setting Up Our Web App
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;If you're already a React pro, feel free to skip the setup and jump to the Components part!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;First, let's create our app using &lt;a href="https://vite.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; (it's super fast and modern):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create vite@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it asks you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick a project name (I'm going with "browser-vm")&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;React&lt;/code&gt; as the framework.&lt;/li&gt;
&lt;li&gt;Go with &lt;code&gt;Javascript&lt;/code&gt; as the variant. (Keeping it generic as promised 😄 )&lt;/li&gt;
&lt;li&gt;Then run:
&lt;/li&gt;
&lt;/ul&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;browser-vm
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Components&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now let's set up our VM display. Open &lt;code&gt;App.jsx&lt;/code&gt; replace all the contents with just this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"screen_container"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"screen"&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Initializing Emulator…&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;canvas&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;index.css&lt;/code&gt; and replace the contents with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#screen_container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Courier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;monospace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;#fff&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;h3&gt;
  
  
  2. Getting Our Virtual Machine Ready
&lt;/h3&gt;

&lt;p&gt;We're using this cool project called V86 that turns your browser into a proper computer emulator. It uses &lt;a href="https://webassembly.org/" rel="noopener noreferrer"&gt;WebAssembly&lt;/a&gt; to translate computer instructions on the fly - pretty neat, right? More on that &lt;a href="https://github.com/copy/v86" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grab these files from V86's &lt;a href="https://github.com/copy/v86/releases" rel="noopener noreferrer"&gt;GitHub releases&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/copy/v86/releases/download/latest/libv86.js" rel="noopener noreferrer"&gt;libv86.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/copy/v86/releases/download/latest/v86.wasm" rel="noopener noreferrer"&gt;v86.wasm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, download the following BIOS files from the &lt;a href="https://github.com/copy/v86/tree/master/bios" rel="noopener noreferrer"&gt;bios folder&lt;/a&gt; of the V86 repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/copy/v86/raw/refs/heads/master/bios/seabios.bin" rel="noopener noreferrer"&gt;seabios.bin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/copy/v86/raw/refs/heads/master/bios/vgabios.bin" rel="noopener noreferrer"&gt;vgabios.bin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put them in your project like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public/
├── v86.wasm
├── libv86.js
└── bios/
    ├── seabios.bin
    └── vgabios.bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Time to Make It Work!
&lt;/h3&gt;

&lt;p&gt;Update your index.html to include our VM engine by adding &lt;code&gt;&amp;lt;script src="libv86.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset="UTF-8" /&amp;gt;
&amp;lt;link rel="icon" type="image/svg+xml" href="/vite.svg" /&amp;gt;
&amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
 &amp;lt;title&amp;gt;FreeDOS 1.2&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
 &amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;
&lt;span class="gi"&gt;+ &amp;lt;script src="libv86.js"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt; &amp;lt;script type="module" src="/src/main.jsx"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now for the fun part - let's configure our virtual machine! Update the contents of &lt;code&gt;App.jsx&lt;/code&gt; to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;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="s1"&gt;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;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initializeEmulator&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
 &lt;span class="c1"&gt;// See https://github.com/copy/v86/blob/master/src/browser/starter.js for options &lt;/span&gt;
 &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;V86&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="na"&gt;wasm_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v86.wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;screen_container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;screen_container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
   &lt;span class="na"&gt;bios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/bios/seabios.bin&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;vga_bios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/bios/vgabios.bin&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;hda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Hard Disk&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/images/fd12-base.img&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;async&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="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="mi"&gt;419430400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Recommended to add size of the image in URL. see https://github.com/copy/v86/blob/master/src/browser/starter.js &lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="na"&gt;autostart&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="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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"screen_container"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"screen"&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Initializing Emulator…&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;canvas&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more details on the available emulator options check out: &lt;a href="https://github.com/copy/v86/blob/master/src/browser/starter.js" rel="noopener noreferrer"&gt;https://github.com/copy/v86/blob/master/src/browser/starter.js&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Boot Some Operating Systems!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  First Up: FreeDOS
&lt;/h3&gt;

&lt;p&gt;Let's start with something fun - &lt;a href="https://www.freedos.org/" rel="noopener noreferrer"&gt;FreeDOS&lt;/a&gt;! It's perfect for running classic DOS games and software. Grab the &lt;a href="https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/distributions/1.2/qemu/qemu-fd12-base.zip" rel="noopener noreferrer"&gt;pre-built version here&lt;/a&gt;, extract &lt;code&gt;fd12-base.img&lt;/code&gt;, and drop it in your &lt;code&gt;public/images&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Start the Web App by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And open &lt;a href="http://localhost:5173/" rel="noopener noreferrer"&gt;http://localhost:5173/&lt;/a&gt; in your 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%2Ft8sl9ox92t18trb0w3c3.gif" 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%2Ft8sl9ox92t18trb0w3c3.gif" alt="Booting up FreeDOS within the browser" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to Try Linux Instead?
&lt;/h3&gt;

&lt;p&gt;Here's something even cooler - we can run Alpine Linux right in the browser! Download the &lt;a href="https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86/alpine-virt-3.20.3-x86.iso" rel="noopener noreferrer"&gt;latest Alpine virtual ISO&lt;/a&gt; and update your VM settings to:&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;V86&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;wasm_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v86.wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;screen_container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;screen_container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;bios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/bios/seabios.bin&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;vga_bios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/bios/vgabios.bin&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;boot_order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0x123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Boot from CD-ROM first&lt;/span&gt;
  &lt;span class="na"&gt;memory_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 512MB RAM&lt;/span&gt;
  &lt;span class="na"&gt;vga_memory_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 64MB VGA RAM&lt;/span&gt;
  &lt;span class="c1"&gt;// See more: https://github.com/copy/v86/blob/master/docs/networking.md&lt;/span&gt;
  &lt;span class="na"&gt;net_device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;virtio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;relay_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wisps://wisp.mercurywork.shop&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;cdrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Source: https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86/alpine-virt-3.20.3-x86.iso&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/images/alpine-virt-3.20.3-x86.iso&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;autostart&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refresh the browser and wait for Linux to boot. It may take 3–5 min 😅. When prompted to login, enter: &lt;code&gt;root&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.amazonaws.com%2Fuploads%2Farticles%2F8kw6shmi9a0hsnnrk1a7.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%2F8kw6shmi9a0hsnnrk1a7.png" alt="Login prompt for Alpine Linux 3.20 running in the browser" width="800" height="757"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Make It Look Extra Cool 😎
&lt;/h3&gt;

&lt;p&gt;Want that authentic retro computer feel? Let's add the perfect font:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the &lt;a href="https://www.dafont.com/modern-dos.font" rel="noopener noreferrer"&gt;Modern DOS font&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drop the files in your project in &lt;code&gt;assets/fonts/ModernDOS&lt;/code&gt; and update index.css to:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'ModernDOS'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('./assets/fonts/ModernDOS/ModernDOS8x16.ttf')&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;'truetype'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#screen_container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'ModernDOS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;'Courier New'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Courier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;monospace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;#fff&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;Refresh the page and enjoy the new look!&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%2Fz82pw0varmts2ipon6o9.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%2Fz82pw0varmts2ipon6o9.png" alt="FreeDOS running in the browser using a DOS-like font" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Now that you've got a virtual machine running in your browser (how cool is that?!), here are some fun things to try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run some classic DOS games - &lt;a href="https://www.freedos.org/about/games/" rel="noopener noreferrer"&gt;https://www.freedos.org/about/games/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Play with Linux commands - &lt;a href="https://www.freecodecamp.org/news/the-linux-commands-handbook/" rel="noopener noreferrer"&gt;https://www.freecodecamp.org/news/the-linux-commands-handbook/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Show your friends - they won't believe it! 🤯&lt;/li&gt;
&lt;li&gt;Jump to Episode 2: &lt;a href="https://dev.to/nadchif/run-your-offline-ai-chat-assistant-pure-browser-zero-backend-1e48"&gt;Run Your Offline AI Chat Assistant: Pure Browser, Zero Backend&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Want the complete code? Grab it here:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/nadchif/in-browser-virtual-machine" rel="noopener noreferrer"&gt;FreeDOS version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nadchif/in-browser-virtual-machine/tree/boot-linux" rel="noopener noreferrer"&gt;Linux version&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Similar Cool Stuff to Check Out
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bellard.org/jslinux/" rel="noopener noreferrer"&gt;JSLinux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pcjs.org/" rel="noopener noreferrer"&gt;PC.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.benjdoherty.com/2017/08/07/Writing-a-minimal-boot-sector-for-the-v86-emulator/" rel="noopener noreferrer"&gt;Making a Boot Sector
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/DustinBrett/daedalOS" rel="noopener noreferrer"&gt;daedalOS: Desktop environment in the browser 🤯&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nepx.github.io/halfix-demo/" rel="noopener noreferrer"&gt;Halfix x86 Emulator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labs.leaningtech.com/blog/mini-webvm-your-linux-box-from-dockerfile-via-wasm" rel="noopener noreferrer"&gt;Mini.WebVM: Your own Linux box from Dockerfile, virtualized in the browser via WebAssembly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Big Thanks To
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The awesome &lt;a href="https://github.com/copy/v86" rel="noopener noreferrer"&gt;V86&lt;/a&gt; project&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.freedos.org/" rel="noopener noreferrer"&gt;FreeDOS&lt;/a&gt; and &lt;a href="https://alpinelinux.org/" rel="noopener noreferrer"&gt;Alpine Linux&lt;/a&gt; teams&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.seabios.org/SeaBIOS" rel="noopener noreferrer"&gt;SeaBIOS&lt;/a&gt; folks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.dafont.com/modern-dos.font" rel="noopener noreferrer"&gt;Modern DOS&lt;/a&gt; font creator&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webassembly</category>
      <category>virtualmachine</category>
      <category>linux</category>
      <category>react</category>
    </item>
  </channel>
</rss>
