Skip to content

Commit

Permalink
[CLI] Whisper Starter added to CLI (#4729)
Browse files Browse the repository at this point in the history
fixes #4728 
<!-- Link to relevant issue (for ex: "fixes #1234") which will
automatically close the issue once the PR is merged -->

## PR Type
<!-- Please uncomment one ore more that apply to this PR -->

<!-- - Bugfix -->
Feature 
<!-- - Code style update (formatting) -->
<!-- - Refactoring (no functional changes, no api changes) -->
<!-- - Build or CI related changes -->
<!-- - Documentation content changes -->
<!-- - Sample app changes -->
<!-- - Other... Please describe: -->

## Describe the new behavior?
Whisper starter has been added as a starter option

## PR Checklist

- [x ] Test: run `npm run test` and ensure that all tests pass
- [ x] Target main branch (or an appropriate release branch if
appropriate for a bug fix)
- [ x] Ensure that your contribution follows [standard accessibility
guidelines](https://docs.microsoft.com/en-us/microsoft-edge/accessibility/design).
Use tools like https://webhint.io/ to validate your changes.


## Additional Information

---------

Co-authored-by: Zach Teutsch <[email protected]>
  • Loading branch information
jgw96 and zateutsch committed May 20, 2024
1 parent f624c42 commit a819e74
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 26 deletions.
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.16",
"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
23 changes: 15 additions & 8 deletions apps/cli/src/analytics/usage-analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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');

Expand All @@ -14,7 +15,8 @@ export interface CreateEventData {
export interface PWABuilderData {
user: {
id: string
}
},
campaignMap?: CampaignMap
}

export function initAnalytics(): void {
Expand Down Expand Up @@ -81,18 +83,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 Down
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
2 changes: 2 additions & 0 deletions apps/cli/src/commands/start.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Arguments, CommandBuilder } from "yargs";
import { outputError, isDirectoryTemplate, spawnWrapper } from "../util/util";
import { trackErrorWrapper, trackStartEventWrapper } from "../analytics/usage-analytics";
import { WHISPER_CAMPAIGN, handleCampaign } from "../util/campaignUtil";

const COMMAND_DESCRIPTION_STRING: string = 'Run the PWA Starter on a Vite dev server.';
const VITEARGS_DESCRIPTION_STRING: string = 'Arguments to pass directly to the Vite start process.';
Expand Down Expand Up @@ -29,6 +30,7 @@ export const builder: CommandBuilder<StartOptions, StartOptions> = (yargs) =>

export const handler = (argv: Arguments<StartOptions>): void => {
try {
handleCampaign(WHISPER_CAMPAIGN);
trackStartEventWrapper();
handleStartCommand(argv);
} catch (error) {
Expand Down
73 changes: 73 additions & 0 deletions apps/cli/src/util/campaignUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import os from 'os';
import * as fs from 'fs';
import { doesFileExist } from '../util/fileUtil';
import { PWABuilderData, createUserDataAndWrite } from '../analytics/usage-analytics';
import { outputMessage } from './util';
import { formatCodeSnippet, formatEmphasis, formatEmphasisStrong } from './textUtil';
import * as prompts from "@clack/prompts";

const whisperDisplayText: string = formatCodeSnippet(`${formatEmphasis("Want to start using AI on the web?")} We just added a new starter template!
The new whisper template includes a Whisper transcription model to get you started with building AI-empowered progressive web apps!
Run ${formatEmphasisStrong("pwa create -t=whisper myWhisperPWA")} to pull the new template.
`);

export var WHISPER_CAMPAIGN: Campaign = {
name: "whisper",
displayed: false,
displayText: whisperDisplayText
}

export type Campaign = {
name: string,
displayed: boolean,
displayText: string
}

export type CampaignMap = {
[key: string] : Campaign
}


export function handleCampaign(campaign: Campaign) {
const pwabuilderDataFilePath: string = os.homedir() + "/.pwabuilder";
var userData: PWABuilderData;
if(doesFileExist(pwabuilderDataFilePath)) {
userData = JSON.parse(fs.readFileSync(pwabuilderDataFilePath, {encoding: 'utf-8'}));
} else {
userData = createUserDataAndWrite(pwabuilderDataFilePath);
}

handleShowCampaign(userData, campaign);
}

function doesCampaignExist(userData: PWABuilderData, campaignKey: string) {
return userData.campaignMap && campaignKey in userData.campaignMap;
}

function addCampaign(userData: PWABuilderData, campaign: Campaign): PWABuilderData {
if(doesCampaignExist(userData, campaign.name)) {
return userData;
}

if(!userData.campaignMap) {
userData.campaignMap = {};
}
userData.campaignMap[campaign.name] = campaign;

return userData;
}

function handleShowCampaign(userData: PWABuilderData, campaign: Campaign) {
var userDataWithCampaign: PWABuilderData = addCampaign(userData, campaign);
if(userDataWithCampaign.campaignMap && !userDataWithCampaign.campaignMap[campaign.name].displayed) {
outputMessage(campaign.displayText);
campaign.displayed = true;
userDataWithCampaign.campaignMap[campaign.name] = campaign;
rewritePWABuilderDataFile(userDataWithCampaign)
}
}

function rewritePWABuilderDataFile(userData: PWABuilderData) {
const pwabuilderDataFilePath: string = os.homedir() + "/.pwabuilder";
fs.writeFileSync(pwabuilderDataFilePath, JSON.stringify(userData), {encoding: 'utf-8'});
}

0 comments on commit a819e74

Please sign in to comment.