Skip to content

Commit

Permalink
Merge branch 'staging' into jay/accessibility-0808
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaylyn-Barbee authored Aug 13, 2024
2 parents 28d3aa6 + 6bfdc6d commit 49f7931
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 63 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 108 additions & 0 deletions apps/blog/src/posts/web-windows-ai/web-windows-ai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
layout: post
title: Building AI enhanced Progressive Web Apps on Windows
excerpt: Building AI enhanced PWAs!
description: This post covers the latest around on-device AI on the Web and how to build AI enhanced PWAs on Windows.
date: 2024-07-08
updatedDate: 2024-07-08
trending: true
featured: true
image: posts/web-windows-ai/header-photo.png
isPost: true
backUrl: '/'
author:
name: Justin Willis
twitter: Justinwillis96
title: Senior Software Engineer
tagline: Seattle based Software Engineer with a passion for the Web
image: /author_images/justin_image.jpg
tags:
- post
- AI
- PWAs
---

<img src="/posts/web-windows-ai/header-photo.png" alt="Cover photo displaying the text 'AI + Windows + Web'" style="display: block;
margin-left: auto;
margin-right: auto;
width: 50%;"></img>

Are you a Web Developer? Do you use Windows or are you interested in switching to Windows? Let's dive into how you can use Windows to have the **BEST Web Development experience**. We will set up a dev environment with **WSL (Windows Subsystem for Linux)** and discuss how it brings a familiar dev environment to your PC. Then, we'll explore the details of how **PWAs (Progressive Web Applications)** work on Windows. Finally, if you make it to the end, we'll quickly get started building a new PWA on Windows!

## Setting up your Dev Environment

### Installing Windows Subsystem for Linux (WSL)

[WSL](https://learn.microsoft.com/en-us/windows/wsl/install) allows developers to install a Linux distribution (such as Ubuntu, Debian, Arch, etc.) and use Linux applications, utilities, and command-line tools directly on Windows, with low overhead. To install WSL:
1. Open PowerShell in administrator mode.
2. Enter the following command: `wsl --install`
3. Restart your machine.
4. This command will enable the necessary features to run WSL and install the Ubuntu distribution of Linux.
5. The first time you launch a newly installed Linux distribution, a console window will open, and you'll see a message asking you to wait for the distro to finish installing.


#### Why WSL?

WSL enables several key experiences for web developers:
- Node.js and other Node-based tools are sometimes built with the assumption that there will be a Unix-based environment, which means they may not work well on Windows. WSL fixes this.
- Node.js can be slow on Windows, especially around NPM installs. WSL provides a solution to this.
- WSL allows you to test your PWA in browsers on Linux, right from your PC.

Learn more about WSL [here](https://learn.microsoft.com/en-us/windows/wsl/about)


## Progressive Web Applications on Windows

Progressive Web Applications are first-class on Windows.
- PWAs are deeply integrated, with PWAs in the Microsoft Store, showing up in normal app settings, synced across your Windows devices and more
- Have access to all the capabilities a large majority of application types need, including:
- File System Access
- Custom Titlebars
- Windows Widgets
- Shortcuts
- Taskbar badging
- Sharing: Both receiving shared content from other apps, and sharing content from a PWA to other apps, using the native share UI on Windows.
- Push Notifications
- Background Data Syncing
- Low Latency Inking
- Device Haptics
- Bluetooth
- GPU Access
- USB
- WebAssembly

- Run natively on both arm64 and x86/64 devices, with no extra developer effort needed.
- Automatic, seamless updates
- Can be installed directly from the browser or from the store

and much more.

### PWAs in the Microsoft Store
The Microsoft Store has full support for PWAs, just like other app types such as UWP. This gives Web Developers another channel to get new users, with no specific changes needed for the store. With this, you can build a PWA and engage with users through the channel you are used to, the browser, BUT you can also take that same app, with no changes, and ship it to the Microsoft Store. This also gives you access to other benefits from the Microsoft Store, such as:
- Showing up in searches in the Store
- The ability to advertise your PWA to users with Microsoft Store ADs
- The ability to get ratings and reviews of your app, something that can be key to improving your app, and something that cannot be easily gotten through the browser.

Getting your PWA in the Microsoft Store is also easy! First, grab the URL to your PWA. Second, go to https://www.pwabuilder.com, enter your URL and click Start. PWABuilder will then check if your PWA is store-ready (same requirements your PWA needs to be installed in the browser) and give you a button to click to package your PWA for the Microsoft Store, along with instructions for testing and publishing.

## Let's build a new PWA!

Alright, now that we have talked about how to best set up your dev environment and how awesome PWAs are on Windows, let's start building a new PWA!

AI is all the hype nowadays, and AI on the web has made some huge strides recently! [ONNX Runtime](https://onnxruntime.ai/docs/get-started/with-javascript/web.html) has full support for running models like Whisper locally, on your device, from your PWA. It can run these models on either your CPU or the GPU (using WebGPU). With Whisper for example, which is an AI model that can automatically transcribe speech to text, you can build speech-to-text powered PWAs that share no data with the cloud.

To help developers get started with building Whisper powered speech-to-text PWAs, we decided to create a new starter template, the PWA Whisper Starter https://github.com/pwa-builder/pwa-whisper-starter! You may be familiar with our PWA Starter https://github.com/pwa-builder/pwa-starter, which is a template that gives you everything you need to start building PWAs. This new starter gives you everything you need to build PWAs, but also with
Fluent Web Components: The Fluent Web Components project gives you UI components, like buttons, dialogs, tabs etc that implement Fluent Design! This means you can build PWAs that follow the Fluent Design look and feel that you would get from a native app on Windows.
Whisper + Transformers.js: Transformers.js https://huggingface.co/docs/transformers.js/index, in a basic way, is a wrapper around Onnx Runtime that makes it easy to run different models, all locally on your device. In the starter, you get this library and all the code you need to do speech-to-text using Whisper, enabling you to focus on your actual app!

To get started building a new PWA using the Whisper Starter, follow these steps:
1. Ensure you have [Node](https://nodejs.org/en) installed.
2. Open your terminal and run `npm install -g @pwabuilder/cli
3. Next, run `pwa create -t=whisper myWhisperPWA`. This will create a new template app for you, with all the dependencies you need etc.
4. After that, just follow the instructions in the output of the CLI to get started developing!

and with that, you can start building your app! Check out https://docs.pwabuilder.com/#/starter/quick-start for more docs and details.


You made it to the end! As you can see, Windows is an amazing platform for not only building PWAs, but also has incredible support for PWAs itself! WSL brings a familiar Linux environment to Windows and allows you to build high quality PWAs using the tools you are familiar with. Combine that with the first-class support for PWAs in Windows itself and in the Microsoft Store, powerful laptops, and other tools like VSCode, Windows terminal etc, and Windows is ready to be your web development platform.

1 change: 1 addition & 0 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ PWABuilder currently offers two different PWA templates:

* **default** - The original PWA Starter template. This template has [full documentation]() available and is our recommended choice.
* **basic** - A simplified version of the PWA Starter template. This template has fewer dependencies and is closer to VanillaJS than the default template.
* **whisper** - The original PWA Starter template set up to get you started with on-device AI. This adds [Fluent UI](https://learn.microsoft.com/en-us/fluent-ui/web-components/) and [Transformers.js](https://huggingface.co/docs/transformers.js/index) on top of the original Starter template.

You can specify a template with the `-t|--template` option:

Expand Down
40 changes: 29 additions & 11 deletions apps/cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pwabuilder/cli",
"version": "0.0.15",
"version": "0.0.18",
"description": "",
"main": "dist/index.js",
"files": [
Expand All @@ -18,7 +18,7 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^18.11.10",
"@types/node": "^18.19.33",
"@types/yargs": "^17.0.15",
"shx": "^0.3.4",
"typescript": "^4.9.3"
Expand Down
49 changes: 37 additions & 12 deletions apps/cli/src/analytics/usage-analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ import * as crypto from 'crypto';
import * as os from 'os';
import * as fs from 'fs';
import { doesFileExist } from '../util/fileUtil';
import { CampaignMap } from '../util/campaignUtil';
import { spawn } from 'child_process';
const path = require('node:path');

export interface CreateEventData {
template: string
}

export interface MessageShowEventData {
label: string
}

export interface PWABuilderData {
user: {
id: string
}
},
campaignMap?: CampaignMap
}

export function initAnalytics(): void {
Expand Down Expand Up @@ -81,18 +87,23 @@ function getUserID(): string {
const userData: PWABuilderData = JSON.parse(fs.readFileSync(pwabuilderDataFilePath, {encoding: 'utf-8'}));
userId = userData.user.id;
} else {
userId = crypto.randomUUID();
const newUserData: PWABuilderData = {
user: {
id: userId
}
}
fs.writeFileSync(pwabuilderDataFilePath, JSON.stringify(newUserData), {encoding: 'utf-8'});
userId = createUserDataAndWrite(pwabuilderDataFilePath).user.id;
}

return userId;
}

export function createUserDataAndWrite(path: string): PWABuilderData {
const userId: string = crypto.randomUUID();
const newUserData: PWABuilderData = {
user: {
id: userId
}
}
fs.writeFileSync(path, JSON.stringify(newUserData), {encoding: 'utf-8'});
return newUserData;
}

function addUserIDtoTelemetry(id: string): void {
defaultClient.addTelemetryProcessor((envelope, context) => {
envelope["tags"]['ai.user.id'] = id;
Expand All @@ -114,23 +125,37 @@ function spawnAnalyticsProcess(event: string, properties?: any) {
child.unref();
}

function getThisPackageVersion(): string {
return require("../../package.json").version;
}

function appendPackageVersionToEventData(eventData: object): object {
var versionAppendedEventData: object = eventData;
versionAppendedEventData['version'] = getThisPackageVersion();
return versionAppendedEventData;
}

function resolveNodeSpawnArgs(event: string, properties?: any): string [] {
const scriptPath: string = path.resolve(__dirname, 'track-events.js')
return properties ? [scriptPath, event, JSON.stringify(properties)] : [scriptPath, event];
}

export function trackErrorWrapper(_error: Error): void {
spawnAnalyticsProcess('error', {error: _error});
spawnAnalyticsProcess('error', {error: _error, version: getThisPackageVersion()});
}

export function trackCreateEventWrapper(createEventData: CreateEventData): void {
spawnAnalyticsProcess('create', createEventData);
spawnAnalyticsProcess('create', appendPackageVersionToEventData(createEventData));
}

export function trackBuildEventWrapper(): void {
spawnAnalyticsProcess('build');
spawnAnalyticsProcess('build', {version: getThisPackageVersion()});
}

export function trackStartEventWrapper(): void {
spawnAnalyticsProcess('start');
spawnAnalyticsProcess('start', {version: getThisPackageVersion()});
}

export function trackMessageShowEventWrapper(messageShowEventData: MessageShowEventData): void {
spawnAnalyticsProcess('messageShow', appendPackageVersionToEventData(messageShowEventData));
}
2 changes: 2 additions & 0 deletions apps/cli/src/commands/build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Arguments, CommandBuilder } from "yargs";
import { isDirectoryTemplate, outputError, spawnWrapper } from "../util/util";
import { trackBuildEventWrapper, trackErrorWrapper } from "../analytics/usage-analytics";
import { WHISPER_CAMPAIGN, handleCampaign } from "../util/campaignUtil";

const COMMAND_DESCRIPTION_STRING: string = 'Build the PWA Starter using Vite.';
const VITEARGS_DESCRIPTION_STRING: string = 'Arguments to pass directly to the Vite build process.';
Expand Down Expand Up @@ -28,6 +29,7 @@ export const builder: CommandBuilder = (yargs) =>

export const handler = async (argv: Arguments<BuildOptions>): Promise<void> => {
try {
handleCampaign(WHISPER_CAMPAIGN);
trackBuildEventWrapper();
await handleBuildCommand(argv);
} catch (error) {
Expand Down
15 changes: 10 additions & 5 deletions apps/cli/src/commands/create.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Arguments, CommandBuilder} from "yargs";
import * as prompts from "@clack/prompts";
import { replaceInFileList, doesFileExist, fetchZipAndDecompress, removeDirectory, renameDirectory, removeAll, FETCHED_ZIP_NAME_STRING, DECOMPRESSED_NAME_STRING } from "../util/fileUtil";
import { outputMessage, promisifiedExecWrapper, timeFunction } from "../util/util";
import { trackCreateEventWrapper, trackErrorWrapper, trackException } from "../analytics/usage-analytics";
import { outputMessage, promisifiedExecWrapper } from "../util/util";
import { trackCreateEventWrapper, trackErrorWrapper } from "../analytics/usage-analytics";
import { promptsCancel, runSpinnerGroup, spinnerItem } from "../util/promptUtil";
import { formatCodeSnippet, formatEmphasis, formatEmphasisStrong, formatErrorEmphasisStrong, formatErrorEmphasisWeak, formatSuccessEmphasis } from "../util/textUtil";
import { formatCodeSnippet, formatEmphasis, formatErrorEmphasisStrong, formatErrorEmphasisWeak, formatSuccessEmphasis } from "../util/textUtil";
import { WHISPER_CAMPAIGN, handleCampaign } from "../util/campaignUtil";

// START TYPES
type CreateOptions = {
Expand Down Expand Up @@ -40,7 +41,8 @@ const ARTIFACT_NAMES: (string) => string[] = (name: string) => {

const TEMPLATE_TO_URL_MAP = {
'default': ["https://github.com/pwa-builder/pwa-starter/archive/refs/heads/main.zip", "pwa-starter-main"],
'basic': ["https://github.com/pwa-builder/pwa-starter-basic/archive/refs/heads/main.zip", "pwa-starter-basic-main"]
'basic': ["https://github.com/pwa-builder/pwa-starter-basic/archive/refs/heads/main.zip", "pwa-starter-basic-main"],
'whisper': ["https://github.com/pwa-builder/pwa-whisper-starter/archive/refs/heads/main.zip", "pwa-whisper-starter-main"]
};

// END DEFAULTS
Expand All @@ -55,6 +57,7 @@ const TEMPLATE_LIST_OUTPUT_STRING: string = `Available templates:
1. ${formatEmphasis("default")} - Original PWA Starter template.
2. ${formatEmphasis("basic")} - Simplified PWA Starter with fewer dependencies
3. ${formatEmphasis("whisper")} - PWA Starter with transformers.js (set up to use Whisper) and Fluent UI
You can specify a template with the ${formatCodeSnippet('-t (--template)')} flag.
For example: ${formatCodeSnippet('pwa create -t="default"')}`;
Expand Down Expand Up @@ -89,7 +92,8 @@ const INVALID_TEMPLATE_ERROR_STRING: string = `Invalid template provided. Cancel
Valid template names:
1. ${formatErrorEmphasisStrong("default")} - Original PWA Starter template
2. ${formatErrorEmphasisStrong("basic")} - Simplified PWA Starter with fewer dependencies`;
2. ${formatErrorEmphasisStrong("basic")} - Simplified PWA Starter with fewer dependencies
3. ${formatErrorEmphasisStrong("whisper")} - PWA Starter with transformers.js (set up to use Whisper) and Fluent UI`;
// END ERROR STRINGS


Expand Down Expand Up @@ -118,6 +122,7 @@ export const builder: CommandBuilder<CreateOptions, CreateOptions> = (yargs) =>

export const handler = async (argv: Arguments<CreateOptions>): Promise<void> => {
try {
handleCampaign(WHISPER_CAMPAIGN);
await handleCreateCommand(argv);
} catch (error) {
trackErrorWrapper(error as Error);
Expand Down
Loading

0 comments on commit 49f7931

Please sign in to comment.