From 1af678eb2cad819fbc7fefc71ecf77efb33109f1 Mon Sep 17 00:00:00 2001 From: "Huu Le (Lee)" <39040748+leehuwuj@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:20:42 +0700 Subject: [PATCH] Feat: add local pdf file option (#441) --- create-app.ts | 2 + helpers/index.ts | 35 ++++++++++++------ helpers/types.ts | 1 + index.ts | 1 + questions.ts | 96 ++++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 111 insertions(+), 24 deletions(-) diff --git a/create-app.ts b/create-app.ts index 2725e3d9..231ace96 100644 --- a/create-app.ts +++ b/create-app.ts @@ -35,6 +35,7 @@ export async function createApp({ vectorDb, externalPort, postInstallAction, + contextFile, }: InstallAppArgs): Promise<void> { const root = path.resolve(appPath); @@ -77,6 +78,7 @@ export async function createApp({ vectorDb, externalPort, postInstallAction, + contextFile, }; if (frontend) { diff --git a/helpers/index.ts b/helpers/index.ts index 4c07c003..39630aa5 100644 --- a/helpers/index.ts +++ b/helpers/index.ts @@ -70,22 +70,32 @@ const copyTestData = async ( engine?: TemplateEngine, openAiKey?: string, vectorDb?: TemplateVectorDB, + contextFile?: string, // eslint-disable-next-line max-params ) => { if (engine === "context") { - const srcPath = path.join( - __dirname, - "..", - "templates", - "components", - "data", - ); const destPath = path.join(root, "data"); - console.log(`\nCopying test data to ${cyan(destPath)}\n`); - await copy("**", destPath, { - parents: true, - cwd: srcPath, - }); + if (contextFile) { + console.log(`\nCopying provided file to ${cyan(destPath)}\n`); + await fs.mkdir(destPath, { recursive: true }); + await fs.copyFile( + contextFile, + path.join(destPath, path.basename(contextFile)), + ); + } else { + const srcPath = path.join( + __dirname, + "..", + "templates", + "components", + "data", + ); + console.log(`\nCopying test data to ${cyan(destPath)}\n`); + await copy("**", destPath, { + parents: true, + cwd: srcPath, + }); + } } if (packageManager && engine === "context") { @@ -168,6 +178,7 @@ export const installTemplate = async ( props.engine, props.openAiKey, props.vectorDb, + props.contextFile, ); } else { // this is a frontend for a full-stack app, create .env file with model information diff --git a/helpers/types.ts b/helpers/types.ts index 0f28caad..8346babf 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -15,6 +15,7 @@ export interface InstallTemplateArgs { template: TemplateType; framework: TemplateFramework; engine: TemplateEngine; + contextFile?: string; ui: TemplateUI; eslint: boolean; customApiPath?: string; diff --git a/index.ts b/index.ts index fae221f2..0da8419c 100644 --- a/index.ts +++ b/index.ts @@ -240,6 +240,7 @@ async function run(): Promise<void> { vectorDb: program.vectorDb, externalPort: program.externalPort, postInstallAction: program.postInstallAction, + contextFile: program.contextFile, }); conf.set("preferences", preferences); diff --git a/questions.ts b/questions.ts index 90c87d42..989c9071 100644 --- a/questions.ts +++ b/questions.ts @@ -1,7 +1,8 @@ +import { execSync } from "child_process"; import ciInfo from "ci-info"; import fs from "fs"; import path from "path"; -import { blue, green } from "picocolors"; +import { blue, green, red } from "picocolors"; import prompts from "prompts"; import { InstallAppArgs } from "./create-app"; import { TemplateFramework } from "./helpers"; @@ -9,6 +10,22 @@ import { COMMUNITY_OWNER, COMMUNITY_REPO } from "./helpers/constant"; import { getRepoRootFolders } from "./helpers/repo"; export type QuestionArgs = Omit<InstallAppArgs, "appPath" | "packageManager">; +const MACOS_FILE_SELECTION_SCRIPT = ` +osascript -l JavaScript -e ' + a = Application.currentApplication(); + a.includeStandardAdditions = true; + a.chooseFile({ withPrompt: "Please select a file to process:" }).toString() +'`; + +const WINDOWS_FILE_SELECTION_SCRIPT = ` +Add-Type -AssemblyName System.Windows.Forms +$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog +$openFileDialog.InitialDirectory = [Environment]::GetFolderPath('Desktop') +$result = $openFileDialog.ShowDialog() +if ($result -eq 'OK') { + $openFileDialog.FileName +} +`; const defaults: QuestionArgs = { template: "streaming", @@ -55,6 +72,45 @@ const getVectorDbChoices = (framework: TemplateFramework) => { return displayedChoices; }; +const selectPDFFile = async () => { + // Popup to select a PDF file + try { + let selectedFilePath: string = ""; + switch (process.platform) { + case "win32": // Windows + selectedFilePath = execSync(WINDOWS_FILE_SELECTION_SCRIPT, { + shell: "powershell.exe", + }) + .toString() + .trim(); + break; + case "darwin": // MacOS + selectedFilePath = execSync(MACOS_FILE_SELECTION_SCRIPT) + .toString() + .trim(); + break; + default: // Unsupported OS + console.log(red("Unsupported OS error!")); + process.exit(1); + } + // Check is pdf file + if (!selectedFilePath.endsWith(".pdf")) { + console.log( + red("Unsupported file error! Please select a valid PDF file!"), + ); + process.exit(1); + } + return selectedFilePath; + } catch (error) { + console.log( + red( + "Got error when trying to select file! Please try again or select other options.", + ), + ); + process.exit(1); + } +}; + export const onPromptState = (state: any) => { if (state.aborted) { // If we don't re-enable the terminal cursor before exiting @@ -243,24 +299,40 @@ export const askQuestions = async ( if (ciInfo.isCI) { program.engine = getPrefOrDefault("engine"); } else { - const { engine } = await prompts( + let choices = [ + { + title: "No data, just a simple chat", + value: "simple", + }, + { title: "Use an example PDF", value: "exampleFile" }, + ]; + if (process.platform === "win32" || process.platform === "darwin") { + choices.push({ title: "Use a local PDF file", value: "localFile" }); + } + + const { dataSource } = await prompts( { type: "select", - name: "engine", + name: "dataSource", message: "Which data source would you like to use?", - choices: [ - { - title: "No data, just a simple chat", - value: "simple", - }, - { title: "Use an example PDF", value: "context" }, - ], + choices: choices, initial: 1, }, handlers, ); - program.engine = engine; - preferences.engine = engine; + switch (dataSource) { + case "simple": + program.engine = "simple"; + break; + case "exampleFile": + program.engine = "context"; + break; + case "localFile": + program.engine = "context"; + // If the user selected the "pdf" option, ask them to select a file + program.contextFile = await selectPDFFile(); + break; + } } if (program.engine !== "simple" && !program.vectorDb) { if (ciInfo.isCI) { -- GitLab