Feat: Improve first setup (#319)

* remove whisper model checking when setup

* fix landing page step

* refactor whisper

* refactor whisper options

* update workflow

* update test-enjoy-app.yml
This commit is contained in:
an-lee
2024-02-18 16:31:52 +08:00
committed by GitHub
parent 0131d5ad8c
commit 9a605b9f39
9 changed files with 46 additions and 151 deletions

View File

@@ -6,7 +6,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, macos-13-xlarge, windows-latest, ubuntu-latest]
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -18,3 +18,8 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
run: yarn publish:enjoy
- if: matrix.os == 'macos-latest'
env:
GITHUB_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
PACKAGE_OS_ARCH: arm64
run: yarn run publish:enjoy --arch=arm64

View File

@@ -10,7 +10,7 @@ on:
- "enjoy/**/*.js"
- "enjoy/**/*.mjs"
jobs:
test:
e2e:
timeout-minutes: 60
runs-on: ${{ matrix.os }}
strategy:
@@ -20,7 +20,7 @@ jobs:
[
macos-latest,
macos-13,
macos-13-xlarge,
macos-14,
windows-2019,
windows-latest,
ubuntu-20.04,
@@ -38,11 +38,11 @@ jobs:
run: |
brew update
brew install sdl2
- if: matrix.os == 'ubuntu-latest'
- if: startsWith(matrix.os, 'ubuntu')
name: Run tests with xvfb-run on ubuntu
run: |
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:enjoy
- if: matrix.os != 'ubuntu-latest'
- if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows')
name: Run tests
run: yarn test:enjoy
- uses: actions/upload-artifact@v4

1
.gitignore vendored
View File

@@ -115,6 +115,7 @@ package-lock.json
*/playwright-report/
*/blob-report/
*/playwright/.cache/
*/tmp/
# whisper models
ggml-*.bin

View File

@@ -19,6 +19,7 @@ declare global {
}
let electronApp: ElectronApplication;
const resultDir = path.join(process.cwd(), "test-results");
test.beforeAll(async () => {
// find the latest build in the out directory
@@ -28,8 +29,6 @@ test.beforeAll(async () => {
// set the CI environment variable to true
process.env.CI = "e2e";
const resultDir = path.join(process.cwd(), "test-results");
fs.ensureDirSync(resultDir);
process.env.SETTINGS_PATH = resultDir;
process.env.LIBRARY_PATH = resultDir;
@@ -40,7 +39,7 @@ test.beforeAll(async () => {
});
electronApp.on("window", async (page) => {
const filename = page.url()?.split("/").pop();
console.log(`Window opened: ${filename}`);
console.info(`Window opened: ${filename}`);
// capture errors
page.on("pageerror", (error) => {
@@ -48,7 +47,7 @@ test.beforeAll(async () => {
});
// capture console messages
page.on("console", (msg) => {
console.log(msg.text());
console.info(msg.text());
});
});
});
@@ -57,25 +56,26 @@ test.afterAll(async () => {
await electronApp.close();
});
let page: Page;
test("renders the first page", async () => {
page = await electronApp.firstWindow();
const page = await electronApp.firstWindow();
const title = await page.title();
expect(title).toBe("Enjoy");
});
test("validate whisper command", async () => {
page = await electronApp.firstWindow();
const page = await electronApp.firstWindow();
const res = await page.evaluate(() => {
return window.__ENJOY_APP__.whisper.check();
});
console.info(res.log);
expect(res.success).toBeTruthy();
const settings = fs.readJsonSync(path.join(resultDir, "settings.json"));
expect(settings.whisper.service).toBe("local");
});
test("valid ffmpeg command", async () => {
page = await electronApp.firstWindow();
const page = await electronApp.firstWindow();
const res = await page.evaluate(() => {
return window.__ENJOY_APP__.ffmpeg.check();
});

View File

@@ -8,7 +8,7 @@
"types": "./src/types.d.ts",
"scripts": {
"predev": "yarn run download",
"dev": "rimraf .vite && yarn run download && WEB_API_URL=http://localhost:3000 electron-forge start",
"dev": "rimraf .vite && yarn run download && WEB_API_URL=http://localhost:3000 SETTINGS_PATH=./tmp LIBRARY_PATH=./tmp electron-forge start",
"start": "rimraf .vite && yarn run download && electron-forge start",
"package": "rimraf .vite && yarn run download && electron-forge package",
"make": "rimraf .vite && yarn run download && electron-forge make",

View File

@@ -70,13 +70,8 @@ const whisperConfig = (): WhisperConfigType => {
) as WhisperConfigType["service"];
if (!service) {
if (model) {
settings.setSync("whisper.service", "local");
service = "local";
} else {
settings.setSync("whisper.service", "azure");
service = "azure";
}
settings.setSync("whisper.service", "local");
service = "local";
}
return {

View File

@@ -13,8 +13,7 @@ class Whipser {
private bundledModelsDir: string;
public config: WhisperConfigType;
constructor(config?: WhisperConfigType) {
this.config = config || settings.whisperConfig();
constructor() {
const customWhisperPath = path.join(
settings.libraryPath(),
"whisper",
@@ -26,26 +25,10 @@ class Whipser {
} else {
this.binMain = path.join(__dirname, "lib", "whisper", "main");
}
this.initialize();
}
currentModel() {
if (!this.config.availableModels) return;
let model: WhisperConfigType["availableModels"][0];
if (this.config.model) {
model = (this.config.availableModels || []).find(
(m) => m.name === this.config.model
);
}
if (!model) {
model = this.config.availableModels[0];
}
settings.setSync("whisper.model", model.name);
return model.savePath;
}
async initialize() {
initialize() {
const models = [];
const bundledModels = fs.readdirSync(this.bundledModelsDir);
@@ -74,44 +57,26 @@ class Whipser {
settings.setSync("whisper.availableModels", models);
settings.setSync("whisper.modelsPath", dir);
this.config = settings.whisperConfig();
}
const command = `"${this.binMain}" --help`;
logger.debug(`Checking whisper command: ${command}`);
return new Promise((resolve, reject) => {
exec(
command,
{
timeout: PROCESS_TIMEOUT,
},
(error, stdout, stderr) => {
if (error) {
logger.error("error", error);
}
currentModel() {
if (!this.config.availableModels) return;
if (stderr) {
logger.debug("stderr", stderr);
}
if (stdout) {
logger.debug("stdout", stdout);
}
const std = (stdout || stderr).toString()?.trim();
if (std.startsWith("usage:")) {
resolve(true);
} else {
reject(
error || new Error("Whisper check failed: unknown error").message
);
}
}
let model: WhisperConfigType["availableModels"][0];
if (this.config.model) {
model = (this.config.availableModels || []).find(
(m) => m.name === this.config.model
);
});
}
if (!model) {
model = this.config.availableModels[0];
}
settings.setSync("whisper.model", model.name);
return model.savePath;
}
async check() {
await this.initialize();
const model = this.currentModel();
logger.debug(`Checking whisper model: ${model}`);
@@ -262,12 +227,7 @@ class Whipser {
registerIpcHandlers() {
ipcMain.handle("whisper-config", async () => {
try {
await this.initialize();
return Object.assign({}, this.config, { ready: true });
} catch (_err) {
return Object.assign({}, this.config, { ready: false });
}
return this.config;
});
ipcMain.handle("whisper-set-model", async (event, model) => {
@@ -295,7 +255,7 @@ class Whipser {
ipcMain.handle("whisper-set-service", async (event, service) => {
if (service === "local") {
try {
await this.initialize();
await this.check();
settings.setSync("whisper.service", service);
this.config.service = service;
return this.config;

View File

@@ -8,18 +8,12 @@ import {
AlertDialogAction,
AlertDialogCancel,
Button,
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
ScrollArea,
toast,
Progress,
} from "@renderer/components/ui";
import { t } from "i18next";
import { InfoIcon, CheckCircle, DownloadIcon, XCircleIcon } from "lucide-react";
import { CheckCircle, DownloadIcon, XCircleIcon } from "lucide-react";
import { WHISPER_MODELS_OPTIONS } from "@/constants";
import { useState, useContext, useEffect } from "react";
import {
@@ -36,54 +30,6 @@ type ModelType = {
downloadState?: DownloadStateType;
};
export const WhisperModelOptionsPanel = () => {
const { EnjoyApp } = useContext(AppSettingsProviderContext);
const { whisperConfig, refreshWhisperConfig } = useContext(
AISettingsProviderContext
);
useEffect(() => {
refreshWhisperConfig();
}, []);
return (
<>
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>{t("whisperModel")}</CardTitle>
<CardDescription>
{t("chooseAIModelDependingOnYourHardware")}
</CardDescription>
</CardHeader>
<CardContent>
<WhisperModelOptions />
</CardContent>
<CardFooter>
<div className="text-xs flex items-start space-x-2">
<InfoIcon className="mr-1.5 w-4 h-4" />
<span className="flex-1 opacity-70">
{t("yourModelsWillBeDownloadedTo", {
path: whisperConfig.modelsPath,
})}
</span>
<Button
onClick={() => {
EnjoyApp.shell.openPath(whisperConfig.modelsPath);
}}
variant="outline"
size="sm"
>
{t("open")}
</Button>
</div>
</CardFooter>
</Card>
</>
);
};
export const WhisperModelOptions = () => {
const [selectingModel, setSelectingModel] = useState<ModelType | null>(null);
const [availableModels, setAvailableModels] = useState<ModelType[]>([]);

View File

@@ -2,11 +2,7 @@ import { t } from "i18next";
import { useState, useContext, useEffect } from "react";
import { Button, Progress } from "@renderer/components/ui";
import { Link } from "react-router-dom";
import {
LoginForm,
ChooseLibraryPathInput,
WhisperModelOptionsPanel,
} from "@renderer/components";
import { LoginForm, ChooseLibraryPathInput } from "@renderer/components";
import {
AppSettingsProviderContext,
AISettingsProviderContext,
@@ -21,7 +17,7 @@ export default () => {
AppSettingsProviderContext
);
const { whisperConfig } = useContext(AISettingsProviderContext);
const totalSteps = 4;
const totalSteps = 3;
useEffect(() => {
validateCurrentStep();
@@ -36,9 +32,6 @@ export default () => {
setCurrentStepValid(!!libraryPath);
break;
case 3:
setCurrentStepValid(true);
break;
case 4:
setCurrentStepValid(initialized);
break;
default:
@@ -68,10 +61,6 @@ export default () => {
subtitle: t("whereYourResourcesAreStored"),
},
3: {
title: t("AIModel"),
subtitle: t("chooseAIModelToDownload"),
},
4: {
title: t("finish"),
subtitle: t("youAreReadyToGo"),
},
@@ -91,8 +80,7 @@ export default () => {
<div className="flex-1 flex justify-center items-center">
{currentStep == 1 && <LoginForm />}
{currentStep == 2 && <ChooseLibraryPathInput />}
{currentStep == 3 && <WhisperModelOptionsPanel />}
{currentStep == 4 && (
{currentStep == 3 && (
<div className="flex justify-center items-center">
<CheckCircle2Icon className="text-green-500 w-24 h-24" />
</div>