├── .gitignore ├── 01-shared ├── .gitignore ├── ApiDTO │ ├── ApiRequest.ts │ └── ApiResponse.ts ├── README.md ├── StorageDTO.ts ├── config.ts ├── jest.config.ts ├── package-lock.json ├── package.json ├── tests │ ├── ApiRequest.test.ts │ └── StorageDTO.test.ts ├── tsconfig.json └── types.ts ├── 02-global ├── .gitignore ├── icons │ ├── icon-128px.png │ ├── icon-16px.png │ ├── icon-260px.png │ ├── icon-32px.png │ ├── icon-48px.png │ └── icon.svg ├── manifest.json ├── package-lock.json └── package.json ├── 03-options ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.vue │ ├── components │ │ ├── ApiKey.vue │ │ ├── Footer.vue │ │ ├── Header.vue │ │ ├── Prompt.vue │ │ └── Prompts.vue │ ├── main.ts │ └── store.ts ├── tsconfig.config.json ├── tsconfig.json └── vite.config.ts ├── 04-background ├── .gitignore ├── 01-use-cases │ ├── GetData.ts │ ├── OpenAndComplete.ts │ ├── OpenOptions.ts │ ├── SetContextMenus.ts │ ├── SetData.ts │ └── ValidateSchema.ts ├── 02-ports │ └── output │ │ ├── Ai.ts │ │ ├── ContextMenus.ts │ │ ├── DTO.ts │ │ ├── Options.ts │ │ ├── Publisher.ts │ │ └── Storage.ts ├── 03-adapters │ ├── primary │ │ ├── ApiAdapter.ts │ │ ├── ChromeContextMenusAdapter.ts │ │ ├── ChromeOpenAndCompleteAdapter.ts │ │ ├── ChromeOptionsAdapter.ts │ │ └── ChromeStorageAdapter.ts │ └── secondary │ │ ├── ChromeContextMenus.ts │ │ ├── ChromeOptions.ts │ │ ├── ChromePublisher.ts │ │ ├── ChromeStorage.ts │ │ └── OpenAI.ts ├── README.md ├── config.ts ├── global.d.ts ├── jest.config.ts ├── package-lock.json ├── package.json ├── start.ts ├── tests │ └── ChromeStorage.test.ts └── tsconfig.json ├── 05-content ├── .gitignore ├── 01-use-cases │ ├── CompleteUseCase.ts │ ├── OpenUseCase.ts │ └── modal │ │ ├── Modal.ts │ │ ├── modal.html │ │ └── modal.scss ├── 02-ports │ └── output │ │ ├── BackgroundPort.ts │ │ └── Storage.ts ├── 03-adapters │ ├── primary │ │ └── ApiAdapter.ts │ └── secondary │ │ ├── ChromeBackgroundAdapter.ts │ │ └── SessionStorage.ts ├── package-lock.json ├── package.json ├── start.ts └── tsconfig.json ├── LICENSE ├── README.md ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/* 3 | dist 4 | -------------------------------------------------------------------------------- /01-shared/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /01-shared/ApiDTO/ApiRequest.ts: -------------------------------------------------------------------------------- 1 | import { DataDTO } from "../StorageDTO"; 2 | 3 | export class OpenRequest implements ApiRequest { 4 | public endpoint = Endpoint.OPEN_MODAL; 5 | public payload: {promptText: string; selectionText: string; }; 6 | 7 | constructor(promptText: string, selectionText: string) { 8 | this.payload = {promptText, selectionText}; 9 | }; 10 | 11 | static isOpenRequest(request: unknown): request is OpenRequest { 12 | return ApiRequest.isApiRequest(request) 13 | && request.endpoint === Endpoint.OPEN_MODAL 14 | && typeof request.payload === "object" 15 | && request.payload !== null 16 | && "promptText" in request.payload && typeof request.payload.promptText === "string" 17 | && "selectionText" in request.payload && typeof request.payload.selectionText === "string"; 18 | }; 19 | }; 20 | 21 | export class CompleteFromTabRequest implements ApiRequest { 22 | public endpoint = Endpoint.COMPLETE; 23 | public payload: {promptText: string; selectionText: string;}; 24 | 25 | constructor(promptText: string, selectionText: string) { 26 | this.payload = {promptText, selectionText}; 27 | }; 28 | 29 | static isCompleteFromTabRequest(request: unknown): request is CompleteFromTabRequest { 30 | return ApiRequest.isApiRequest(request) 31 | && request.endpoint === Endpoint.COMPLETE 32 | && typeof request.payload === "object" 33 | && request.payload !== null 34 | && "promptText" in request.payload && typeof request.payload.promptText === "string" 35 | && "selectionText" in request.payload && typeof request.payload.selectionText === "string"; 36 | }; 37 | }; 38 | 39 | export class CompleteRequest extends CompleteFromTabRequest implements ApiRequest { 40 | public payload: {promptText: string; selectionText: string; tabId: number;}; 41 | 42 | constructor(promptText: string, selectionText: string, tabId: number) { 43 | super(promptText, selectionText); 44 | this.payload = {promptText, selectionText, tabId}; 45 | }; 46 | 47 | static isCompleteRequest(request: unknown): request is CompleteRequest { 48 | return ApiRequest.isApiRequest(request) 49 | && request.endpoint === Endpoint.COMPLETE 50 | && typeof request.payload === "object" 51 | && request.payload !== null 52 | && "promptText" in request.payload && typeof request.payload.promptText === "string" 53 | && "selectionText" in request.payload && typeof request.payload.selectionText === "string" 54 | && "tabId" in request.payload && typeof request.payload.tabId === "number"; 55 | }; 56 | }; 57 | 58 | export class SaveDataRequest implements ApiRequest { 59 | public endpoint = Endpoint.SAVE_DATA; 60 | 61 | constructor(public payload : DataDTO) {}; 62 | 63 | static isSaveDataRequest(request: unknown): request is SaveDataRequest { 64 | return ApiRequest.isApiRequest(request) 65 | && request.endpoint === Endpoint.SAVE_DATA 66 | && DataDTO.isDataDTO(request.payload); 67 | }; 68 | }; 69 | 70 | export class ApiRequest { 71 | constructor( 72 | public endpoint: Endpoint, 73 | public payload: unknown, 74 | ) {}; 75 | 76 | static isApiRequest(request: unknown): request is ApiRequest { 77 | return typeof request === 'object' 78 | && request !== null 79 | && "endpoint" in request && typeof request.endpoint === "string" 80 | && Object.values(Endpoint).includes(request.endpoint as Endpoint) 81 | && "payload" in request; 82 | }; 83 | }; 84 | 85 | export enum Endpoint { 86 | OPEN_MODAL = "openModal", 87 | COMPLETE = "complete", 88 | GET_DATA = "getData", 89 | SAVE_DATA = "saveData", 90 | }; 91 | -------------------------------------------------------------------------------- /01-shared/ApiDTO/ApiResponse.ts: -------------------------------------------------------------------------------- 1 | import { DataDTO } from "../StorageDTO"; 2 | 3 | export class ApiResponse { 4 | constructor( 5 | public success: boolean, 6 | public data: unknown, 7 | ) {}; 8 | 9 | static isApiResponse(response: unknown): response is ApiResponse { 10 | return typeof response === 'object' 11 | && response !== null 12 | && "success" in response && typeof response.success === "boolean" 13 | && "data" in response; 14 | }; 15 | }; 16 | 17 | export class DataResponse extends ApiResponse { 18 | constructor( 19 | public success: boolean, 20 | public data: DataDTO, 21 | ) { 22 | super(success, data); 23 | }; 24 | 25 | static isDataResponse(response: unknown): response is DataResponse { 26 | return ApiResponse.isApiResponse(response) 27 | && DataDTO.isDataDTO(response.data); 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /01-shared/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This folder contains only: 3 | - DTO types shared among the different parts of the extension 4 | - Configuration values for the extension 5 | 6 | **Note:** the types here might come along type predicates. This is the only logic that might be here. And this is the reason why there are tests in this folder. -------------------------------------------------------------------------------- /01-shared/StorageDTO.ts: -------------------------------------------------------------------------------- 1 | export class DataDTO { 2 | public version: 1 = 1; 3 | 4 | constructor(public apiKey: string, public prompts: PromptDTO[]) {}; 5 | 6 | static isDataDTO(data: unknown): data is DataDTO { 7 | return typeof data === "object" 8 | && data !== null 9 | && "version" in data && data.version === 1 10 | && "apiKey" in data && typeof data.apiKey === "string" 11 | && "prompts" in data && Array.isArray(data.prompts) 12 | && data.prompts.every((prompt: any) => PromptDTO.isPromptDTO(prompt)); 13 | }; 14 | }; 15 | 16 | export class PromptDTO { 17 | constructor(public id: number, public name: string, public value: string) {}; 18 | 19 | static isPromptDTO(prompt: unknown): prompt is PromptDTO { 20 | return typeof prompt === "object" 21 | && prompt !== null 22 | && "id" in prompt && typeof prompt.id === "number" 23 | && "name" in prompt && typeof prompt.name === "string" 24 | && "value" in prompt && typeof prompt.value === "string"; 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /01-shared/config.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | prompt: { 3 | susbstitutionPlaceholder: "%SUBSTITUTION%", 4 | }, 5 | openai: { 6 | timeout: 2500, 7 | }, 8 | storage: { 9 | apiKeyKey: "apiKey", 10 | promptsKey: "prompts", 11 | }, 12 | ui: { 13 | color: { 14 | primary: "crimson", 15 | secondary: "lightsalmon", 16 | }, 17 | }, 18 | }; -------------------------------------------------------------------------------- /01-shared/jest.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /01-shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shared", 3 | "private": true, 4 | "scripts": { 5 | "test": "jest --watchAll" 6 | }, 7 | "devDependencies": { 8 | "@types/jest": "^29.5.1", 9 | "@types/node": "^18.15.13", 10 | "jest": "^29.5.0", 11 | "ts-jest": "^29.1.0", 12 | "ts-node": "^10.9.1", 13 | "typescript": "^5.0.4" 14 | }, 15 | "dependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /01-shared/tests/ApiRequest.test.ts: -------------------------------------------------------------------------------- 1 | import { ApiRequest, CompleteFromTabRequest, CompleteRequest, Endpoint, OpenRequest, SaveDataRequest } from "../ApiDTO/ApiRequest"; 2 | import { DataDTO } from "../StorageDTO"; 3 | 4 | describe('ApiRequest', () => { 5 | 6 | let request: any; 7 | 8 | test('ApiRequest', () => { 9 | request = new ApiRequest(Endpoint.COMPLETE, {}); 10 | expect(ApiRequest.isApiRequest(request)).toBe(true); 11 | request = { endpoint: Endpoint.COMPLETE, payload: {}, ok: "ok"}; 12 | expect(ApiRequest.isApiRequest(request)).toBe(true); 13 | request = { endpoint: Endpoint.COMPLETE, payload: 1}; 14 | expect(ApiRequest.isApiRequest(request)).toBe(true); 15 | request = { endpoint: 1, payload: {}}; 16 | expect(ApiRequest.isApiRequest(request)).toBe(false); 17 | }); 18 | 19 | test('OpenRequest', () => { 20 | request = new OpenRequest("toto", ""); 21 | expect(OpenRequest.isOpenRequest(request)).toBe(true); 22 | request = { endpoint: Endpoint.OPEN_MODAL, payload: {promptText: "toto", selectionText: "", ok: "ok"}, ok: "ok"}; 23 | expect(OpenRequest.isOpenRequest(request)).toBe(true); 24 | request = { endpoint: Endpoint.OPEN_MODAL, payload: {promptText: "toto", selectionText: 1}}; 25 | expect(OpenRequest.isOpenRequest(request)).toBe(false); 26 | request = { endpoint: Endpoint.OPEN_MODAL, payload: {promptText: 1, selectionText: ""}}; 27 | expect(OpenRequest.isOpenRequest(request)).toBe(false); 28 | request = { endpoint: Endpoint.SAVE_DATA, payload: {promptText: "toto", selectionText: ""}}; 29 | expect(OpenRequest.isOpenRequest(request)).toBe(false); 30 | }); 31 | 32 | test('CompleteFromTabRequest', () => { 33 | request = new CompleteFromTabRequest("toto", ""); 34 | expect(CompleteFromTabRequest.isCompleteFromTabRequest(request)).toBe(true); 35 | request = { endpoint: Endpoint.COMPLETE, payload: {promptText: "toto", selectionText: "", tabId: 0}, ok: "ok"}; 36 | expect(CompleteFromTabRequest.isCompleteFromTabRequest(request)).toBe(true); 37 | request = { endpoint: Endpoint.COMPLETE, payload: {promptText: "toto", selectionText: 1}}; 38 | expect(CompleteFromTabRequest.isCompleteFromTabRequest(request)).toBe(false); 39 | request = { endpoint: Endpoint.COMPLETE, payload: {promptText: 1, selectionText: ""}}; 40 | expect(CompleteFromTabRequest.isCompleteFromTabRequest(request)).toBe(false); 41 | request = { endpoint: Endpoint.SAVE_DATA, payload: {promptText: "toto", selectionText: ""}}; 42 | expect(CompleteFromTabRequest.isCompleteFromTabRequest(request)).toBe(false); 43 | }); 44 | 45 | test('CompleteRequest', () => { 46 | request = new CompleteRequest("toto", "", 0); 47 | expect(CompleteRequest.isCompleteRequest(request)).toBe(true); 48 | request = { endpoint: Endpoint.COMPLETE, payload: {promptText: "toto", selectionText: "", tabId: 0, ok: "ok"}, ok: "ok"}; 49 | expect(CompleteRequest.isCompleteRequest(request)).toBe(true); 50 | request = { endpoint: Endpoint.COMPLETE, payload: {promptText: "toto", selectionText: 1, tabId: 0}}; 51 | expect(CompleteRequest.isCompleteRequest(request)).toBe(false); 52 | request = { endpoint: Endpoint.COMPLETE, payload: {promptText: 1, selectionText: "", tabId: 0}}; 53 | expect(CompleteRequest.isCompleteRequest(request)).toBe(false); 54 | request = { endpoint: Endpoint.COMPLETE, payload: {promptText: "toto", selectionText: "", tabId: "ok"}}; 55 | expect(CompleteRequest.isCompleteRequest(request)).toBe(false); 56 | request = { endpoint: Endpoint.SAVE_DATA, payload: {promptText: "toto", selectionText: "", tabId: 0}}; 57 | expect(CompleteRequest.isCompleteRequest(request)).toBe(false); 58 | }); 59 | 60 | test('SaveDataRequest', () => { 61 | request = new SaveDataRequest(new DataDTO("", [])) 62 | expect(SaveDataRequest.isSaveDataRequest(request)).toBe(true); 63 | request = { endpoint: Endpoint.SAVE_DATA, payload: new DataDTO("", []), ok: "ok"}; 64 | expect(SaveDataRequest.isSaveDataRequest(request)).toBe(true); 65 | request = { endpoint: Endpoint.SAVE_DATA, payload: {data: 1}}; 66 | expect(SaveDataRequest.isSaveDataRequest(request)).toBe(false); 67 | request = { endpoint: Endpoint.OPEN_MODAL, payload: new DataDTO("", [])}; 68 | expect(SaveDataRequest.isSaveDataRequest(request)).toBe(false); 69 | }); 70 | 71 | }); 72 | -------------------------------------------------------------------------------- /01-shared/tests/StorageDTO.test.ts: -------------------------------------------------------------------------------- 1 | import { DataDTO, PromptDTO } from "../StorageDTO"; 2 | 3 | describe('Storage', () => { 4 | 5 | let prompt: any; 6 | 7 | test('PromptDTO', () => { 8 | prompt = new PromptDTO(0, "", ""); 9 | expect(PromptDTO.isPromptDTO(prompt)).toBe(true); 10 | prompt = { id: 0, name: "", value: "", ok: "ok"}; 11 | expect(PromptDTO.isPromptDTO(prompt)).toBe(true); 12 | prompt = { id: "ok", name: "", value: ""}; 13 | expect(PromptDTO.isPromptDTO(prompt)).toBe(false); 14 | prompt = { id: 0, name: "", value: 1}; 15 | expect(PromptDTO.isPromptDTO(prompt)).toBe(false); 16 | prompt = { id: 0, name: 1, value: ""}; 17 | expect(PromptDTO.isPromptDTO(prompt)).toBe(false); 18 | }); 19 | 20 | let data: any; 21 | 22 | test('DataDTO', () => { 23 | data = new DataDTO("", []); 24 | expect(DataDTO.isDataDTO(data)).toBe(true); 25 | data = { version: 1, apiKey: "", prompts: [], ok: "ok"}; 26 | expect(DataDTO.isDataDTO(data)).toBe(true); 27 | data = { version: 0, apiKey: "", prompts: []}; 28 | expect(DataDTO.isDataDTO(data)).toBe(false); 29 | data = { version: 1, apiKey: 1, prompts: []}; 30 | expect(DataDTO.isDataDTO(data)).toBe(false); 31 | data = { version: 1, apiKey: "", prompts: [1]}; 32 | expect(DataDTO.isDataDTO(data)).toBe(false); 33 | data = { version: 1, apiKey: "", prompts: [new PromptDTO(0, "", "")]}; 34 | expect(DataDTO.isDataDTO(data)).toBe(true); 35 | }); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /01-shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /01-shared/types.ts: -------------------------------------------------------------------------------- 1 | export class ContextMenuItem { 2 | constructor( 3 | public readonly id: string, 4 | public readonly title: string, 5 | public readonly type: chrome.contextMenus.ItemType, 6 | public readonly contexts: [chrome.contextMenus.ContextType, ...chrome.contextMenus.ContextType[]], 7 | ) {}; 8 | }; 9 | 10 | export enum PortName { 11 | COMPLETE = "complete", 12 | }; 13 | -------------------------------------------------------------------------------- /02-global/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /02-global/icons/icon-128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheeseDurger/gpt-anywhere/2528de71f8eea908b74c8d1f2aaad4f6e14b18c9/02-global/icons/icon-128px.png -------------------------------------------------------------------------------- /02-global/icons/icon-16px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheeseDurger/gpt-anywhere/2528de71f8eea908b74c8d1f2aaad4f6e14b18c9/02-global/icons/icon-16px.png -------------------------------------------------------------------------------- /02-global/icons/icon-260px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheeseDurger/gpt-anywhere/2528de71f8eea908b74c8d1f2aaad4f6e14b18c9/02-global/icons/icon-260px.png -------------------------------------------------------------------------------- /02-global/icons/icon-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheeseDurger/gpt-anywhere/2528de71f8eea908b74c8d1f2aaad4f6e14b18c9/02-global/icons/icon-32px.png -------------------------------------------------------------------------------- /02-global/icons/icon-48px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheeseDurger/gpt-anywhere/2528de71f8eea908b74c8d1f2aaad4f6e14b18c9/02-global/icons/icon-48px.png -------------------------------------------------------------------------------- /02-global/icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 25 | 47 | 55 | 57 | 58 | 59 | image/svg+xml 60 | 62 | 64 | 65 | 67 | Openclipart 68 | 69 | 70 | fttext 71 | 2011-01-31T02:06:51 72 | Originally uploaded by Danny Allen for OCAL 0.18 this icon is part of the flat theme 73 | https://openclipart.org/detail/114571/fttext-by-anonymous 74 | 75 | 76 | Anonymous 77 | 78 | 79 | 80 | 81 | flat 82 | icon 83 | theme 84 | 85 | 86 | 87 | 89 | 91 | 93 | 95 | 96 | 97 | 98 | gpt 109 | 110 | -------------------------------------------------------------------------------- /02-global/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "GPT Anywhere", 4 | "description": "Get GPT completions on any webpage.", 5 | "version": "1.2.0", 6 | "icons": { 7 | "16": "icons/icon-16px.png", 8 | "32": "icons/icon-32px.png", 9 | "48": "icons/icon-48px.png", 10 | "128": "icons/icon-128px.png" 11 | }, 12 | "action": { 13 | "default_title": "Go to this extension's Options to configure your GPT prompts" 14 | }, 15 | "permissions": [ 16 | "contextMenus", 17 | "storage" 18 | ], 19 | "options_page": "options/index.html", 20 | "background": { 21 | "service_worker": "background/background.js", 22 | "type": "module" 23 | }, 24 | "content_scripts": [ 25 | { 26 | "matches": [ 27 | "" 28 | ], 29 | "js": [ 30 | "content/start.js" 31 | ] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /02-global/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "global", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "global", 8 | "devDependencies": { 9 | "nodemon": "^2.0.20", 10 | "shx": "^0.3.4" 11 | } 12 | }, 13 | "node_modules/abbrev": { 14 | "version": "1.1.1", 15 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 16 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 17 | "dev": true 18 | }, 19 | "node_modules/anymatch": { 20 | "version": "3.1.3", 21 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 22 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 23 | "dev": true, 24 | "dependencies": { 25 | "normalize-path": "^3.0.0", 26 | "picomatch": "^2.0.4" 27 | }, 28 | "engines": { 29 | "node": ">= 8" 30 | } 31 | }, 32 | "node_modules/balanced-match": { 33 | "version": "1.0.2", 34 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 35 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 36 | "dev": true 37 | }, 38 | "node_modules/binary-extensions": { 39 | "version": "2.2.0", 40 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 41 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 42 | "dev": true, 43 | "engines": { 44 | "node": ">=8" 45 | } 46 | }, 47 | "node_modules/brace-expansion": { 48 | "version": "1.1.11", 49 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 50 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 51 | "dev": true, 52 | "dependencies": { 53 | "balanced-match": "^1.0.0", 54 | "concat-map": "0.0.1" 55 | } 56 | }, 57 | "node_modules/braces": { 58 | "version": "3.0.2", 59 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 60 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 61 | "dev": true, 62 | "dependencies": { 63 | "fill-range": "^7.0.1" 64 | }, 65 | "engines": { 66 | "node": ">=8" 67 | } 68 | }, 69 | "node_modules/chokidar": { 70 | "version": "3.5.3", 71 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 72 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 73 | "dev": true, 74 | "funding": [ 75 | { 76 | "type": "individual", 77 | "url": "https://paulmillr.com/funding/" 78 | } 79 | ], 80 | "dependencies": { 81 | "anymatch": "~3.1.2", 82 | "braces": "~3.0.2", 83 | "glob-parent": "~5.1.2", 84 | "is-binary-path": "~2.1.0", 85 | "is-glob": "~4.0.1", 86 | "normalize-path": "~3.0.0", 87 | "readdirp": "~3.6.0" 88 | }, 89 | "engines": { 90 | "node": ">= 8.10.0" 91 | }, 92 | "optionalDependencies": { 93 | "fsevents": "~2.3.2" 94 | } 95 | }, 96 | "node_modules/concat-map": { 97 | "version": "0.0.1", 98 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 99 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 100 | "dev": true 101 | }, 102 | "node_modules/debug": { 103 | "version": "3.2.7", 104 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 105 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 106 | "dev": true, 107 | "dependencies": { 108 | "ms": "^2.1.1" 109 | } 110 | }, 111 | "node_modules/fill-range": { 112 | "version": "7.0.1", 113 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 114 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 115 | "dev": true, 116 | "dependencies": { 117 | "to-regex-range": "^5.0.1" 118 | }, 119 | "engines": { 120 | "node": ">=8" 121 | } 122 | }, 123 | "node_modules/fs.realpath": { 124 | "version": "1.0.0", 125 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 126 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 127 | "dev": true 128 | }, 129 | "node_modules/fsevents": { 130 | "version": "2.3.2", 131 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 132 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 133 | "dev": true, 134 | "hasInstallScript": true, 135 | "optional": true, 136 | "os": [ 137 | "darwin" 138 | ], 139 | "engines": { 140 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 141 | } 142 | }, 143 | "node_modules/function-bind": { 144 | "version": "1.1.1", 145 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 146 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 147 | "dev": true 148 | }, 149 | "node_modules/glob": { 150 | "version": "7.2.3", 151 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 152 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 153 | "dev": true, 154 | "dependencies": { 155 | "fs.realpath": "^1.0.0", 156 | "inflight": "^1.0.4", 157 | "inherits": "2", 158 | "minimatch": "^3.1.1", 159 | "once": "^1.3.0", 160 | "path-is-absolute": "^1.0.0" 161 | }, 162 | "engines": { 163 | "node": "*" 164 | }, 165 | "funding": { 166 | "url": "https://github.com/sponsors/isaacs" 167 | } 168 | }, 169 | "node_modules/glob-parent": { 170 | "version": "5.1.2", 171 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 172 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 173 | "dev": true, 174 | "dependencies": { 175 | "is-glob": "^4.0.1" 176 | }, 177 | "engines": { 178 | "node": ">= 6" 179 | } 180 | }, 181 | "node_modules/has": { 182 | "version": "1.0.3", 183 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 184 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 185 | "dev": true, 186 | "dependencies": { 187 | "function-bind": "^1.1.1" 188 | }, 189 | "engines": { 190 | "node": ">= 0.4.0" 191 | } 192 | }, 193 | "node_modules/has-flag": { 194 | "version": "3.0.0", 195 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 196 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 197 | "dev": true, 198 | "engines": { 199 | "node": ">=4" 200 | } 201 | }, 202 | "node_modules/ignore-by-default": { 203 | "version": "1.0.1", 204 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 205 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 206 | "dev": true 207 | }, 208 | "node_modules/inflight": { 209 | "version": "1.0.6", 210 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 211 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 212 | "dev": true, 213 | "dependencies": { 214 | "once": "^1.3.0", 215 | "wrappy": "1" 216 | } 217 | }, 218 | "node_modules/inherits": { 219 | "version": "2.0.4", 220 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 221 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 222 | "dev": true 223 | }, 224 | "node_modules/interpret": { 225 | "version": "1.4.0", 226 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", 227 | "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", 228 | "dev": true, 229 | "engines": { 230 | "node": ">= 0.10" 231 | } 232 | }, 233 | "node_modules/is-binary-path": { 234 | "version": "2.1.0", 235 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 236 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 237 | "dev": true, 238 | "dependencies": { 239 | "binary-extensions": "^2.0.0" 240 | }, 241 | "engines": { 242 | "node": ">=8" 243 | } 244 | }, 245 | "node_modules/is-core-module": { 246 | "version": "2.11.0", 247 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 248 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 249 | "dev": true, 250 | "dependencies": { 251 | "has": "^1.0.3" 252 | }, 253 | "funding": { 254 | "url": "https://github.com/sponsors/ljharb" 255 | } 256 | }, 257 | "node_modules/is-extglob": { 258 | "version": "2.1.1", 259 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 260 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 261 | "dev": true, 262 | "engines": { 263 | "node": ">=0.10.0" 264 | } 265 | }, 266 | "node_modules/is-glob": { 267 | "version": "4.0.3", 268 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 269 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 270 | "dev": true, 271 | "dependencies": { 272 | "is-extglob": "^2.1.1" 273 | }, 274 | "engines": { 275 | "node": ">=0.10.0" 276 | } 277 | }, 278 | "node_modules/is-number": { 279 | "version": "7.0.0", 280 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 281 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 282 | "dev": true, 283 | "engines": { 284 | "node": ">=0.12.0" 285 | } 286 | }, 287 | "node_modules/minimatch": { 288 | "version": "3.1.2", 289 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 290 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 291 | "dev": true, 292 | "dependencies": { 293 | "brace-expansion": "^1.1.7" 294 | }, 295 | "engines": { 296 | "node": "*" 297 | } 298 | }, 299 | "node_modules/minimist": { 300 | "version": "1.2.8", 301 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 302 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 303 | "dev": true, 304 | "funding": { 305 | "url": "https://github.com/sponsors/ljharb" 306 | } 307 | }, 308 | "node_modules/ms": { 309 | "version": "2.1.3", 310 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 311 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 312 | "dev": true 313 | }, 314 | "node_modules/nodemon": { 315 | "version": "2.0.20", 316 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", 317 | "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", 318 | "dev": true, 319 | "dependencies": { 320 | "chokidar": "^3.5.2", 321 | "debug": "^3.2.7", 322 | "ignore-by-default": "^1.0.1", 323 | "minimatch": "^3.1.2", 324 | "pstree.remy": "^1.1.8", 325 | "semver": "^5.7.1", 326 | "simple-update-notifier": "^1.0.7", 327 | "supports-color": "^5.5.0", 328 | "touch": "^3.1.0", 329 | "undefsafe": "^2.0.5" 330 | }, 331 | "bin": { 332 | "nodemon": "bin/nodemon.js" 333 | }, 334 | "engines": { 335 | "node": ">=8.10.0" 336 | }, 337 | "funding": { 338 | "type": "opencollective", 339 | "url": "https://opencollective.com/nodemon" 340 | } 341 | }, 342 | "node_modules/nopt": { 343 | "version": "1.0.10", 344 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 345 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 346 | "dev": true, 347 | "dependencies": { 348 | "abbrev": "1" 349 | }, 350 | "bin": { 351 | "nopt": "bin/nopt.js" 352 | }, 353 | "engines": { 354 | "node": "*" 355 | } 356 | }, 357 | "node_modules/normalize-path": { 358 | "version": "3.0.0", 359 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 360 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 361 | "dev": true, 362 | "engines": { 363 | "node": ">=0.10.0" 364 | } 365 | }, 366 | "node_modules/once": { 367 | "version": "1.4.0", 368 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 369 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 370 | "dev": true, 371 | "dependencies": { 372 | "wrappy": "1" 373 | } 374 | }, 375 | "node_modules/path-is-absolute": { 376 | "version": "1.0.1", 377 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 378 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 379 | "dev": true, 380 | "engines": { 381 | "node": ">=0.10.0" 382 | } 383 | }, 384 | "node_modules/path-parse": { 385 | "version": "1.0.7", 386 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 387 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 388 | "dev": true 389 | }, 390 | "node_modules/picomatch": { 391 | "version": "2.3.1", 392 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 393 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 394 | "dev": true, 395 | "engines": { 396 | "node": ">=8.6" 397 | }, 398 | "funding": { 399 | "url": "https://github.com/sponsors/jonschlinkert" 400 | } 401 | }, 402 | "node_modules/pstree.remy": { 403 | "version": "1.1.8", 404 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 405 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 406 | "dev": true 407 | }, 408 | "node_modules/readdirp": { 409 | "version": "3.6.0", 410 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 411 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 412 | "dev": true, 413 | "dependencies": { 414 | "picomatch": "^2.2.1" 415 | }, 416 | "engines": { 417 | "node": ">=8.10.0" 418 | } 419 | }, 420 | "node_modules/rechoir": { 421 | "version": "0.6.2", 422 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 423 | "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", 424 | "dev": true, 425 | "dependencies": { 426 | "resolve": "^1.1.6" 427 | }, 428 | "engines": { 429 | "node": ">= 0.10" 430 | } 431 | }, 432 | "node_modules/resolve": { 433 | "version": "1.22.1", 434 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 435 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 436 | "dev": true, 437 | "dependencies": { 438 | "is-core-module": "^2.9.0", 439 | "path-parse": "^1.0.7", 440 | "supports-preserve-symlinks-flag": "^1.0.0" 441 | }, 442 | "bin": { 443 | "resolve": "bin/resolve" 444 | }, 445 | "funding": { 446 | "url": "https://github.com/sponsors/ljharb" 447 | } 448 | }, 449 | "node_modules/semver": { 450 | "version": "5.7.1", 451 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 452 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 453 | "dev": true, 454 | "bin": { 455 | "semver": "bin/semver" 456 | } 457 | }, 458 | "node_modules/shelljs": { 459 | "version": "0.8.5", 460 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", 461 | "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", 462 | "dev": true, 463 | "dependencies": { 464 | "glob": "^7.0.0", 465 | "interpret": "^1.0.0", 466 | "rechoir": "^0.6.2" 467 | }, 468 | "bin": { 469 | "shjs": "bin/shjs" 470 | }, 471 | "engines": { 472 | "node": ">=4" 473 | } 474 | }, 475 | "node_modules/shx": { 476 | "version": "0.3.4", 477 | "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", 478 | "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", 479 | "dev": true, 480 | "dependencies": { 481 | "minimist": "^1.2.3", 482 | "shelljs": "^0.8.5" 483 | }, 484 | "bin": { 485 | "shx": "lib/cli.js" 486 | }, 487 | "engines": { 488 | "node": ">=6" 489 | } 490 | }, 491 | "node_modules/simple-update-notifier": { 492 | "version": "1.1.0", 493 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", 494 | "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", 495 | "dev": true, 496 | "dependencies": { 497 | "semver": "~7.0.0" 498 | }, 499 | "engines": { 500 | "node": ">=8.10.0" 501 | } 502 | }, 503 | "node_modules/simple-update-notifier/node_modules/semver": { 504 | "version": "7.0.0", 505 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", 506 | "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", 507 | "dev": true, 508 | "bin": { 509 | "semver": "bin/semver.js" 510 | } 511 | }, 512 | "node_modules/supports-color": { 513 | "version": "5.5.0", 514 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 515 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 516 | "dev": true, 517 | "dependencies": { 518 | "has-flag": "^3.0.0" 519 | }, 520 | "engines": { 521 | "node": ">=4" 522 | } 523 | }, 524 | "node_modules/supports-preserve-symlinks-flag": { 525 | "version": "1.0.0", 526 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 527 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 528 | "dev": true, 529 | "engines": { 530 | "node": ">= 0.4" 531 | }, 532 | "funding": { 533 | "url": "https://github.com/sponsors/ljharb" 534 | } 535 | }, 536 | "node_modules/to-regex-range": { 537 | "version": "5.0.1", 538 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 539 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 540 | "dev": true, 541 | "dependencies": { 542 | "is-number": "^7.0.0" 543 | }, 544 | "engines": { 545 | "node": ">=8.0" 546 | } 547 | }, 548 | "node_modules/touch": { 549 | "version": "3.1.0", 550 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 551 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 552 | "dev": true, 553 | "dependencies": { 554 | "nopt": "~1.0.10" 555 | }, 556 | "bin": { 557 | "nodetouch": "bin/nodetouch.js" 558 | } 559 | }, 560 | "node_modules/undefsafe": { 561 | "version": "2.0.5", 562 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 563 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 564 | "dev": true 565 | }, 566 | "node_modules/wrappy": { 567 | "version": "1.0.2", 568 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 569 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 570 | "dev": true 571 | } 572 | }, 573 | "dependencies": { 574 | "abbrev": { 575 | "version": "1.1.1", 576 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 577 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 578 | "dev": true 579 | }, 580 | "anymatch": { 581 | "version": "3.1.3", 582 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 583 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 584 | "dev": true, 585 | "requires": { 586 | "normalize-path": "^3.0.0", 587 | "picomatch": "^2.0.4" 588 | } 589 | }, 590 | "balanced-match": { 591 | "version": "1.0.2", 592 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 593 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 594 | "dev": true 595 | }, 596 | "binary-extensions": { 597 | "version": "2.2.0", 598 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 599 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 600 | "dev": true 601 | }, 602 | "brace-expansion": { 603 | "version": "1.1.11", 604 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 605 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 606 | "dev": true, 607 | "requires": { 608 | "balanced-match": "^1.0.0", 609 | "concat-map": "0.0.1" 610 | } 611 | }, 612 | "braces": { 613 | "version": "3.0.2", 614 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 615 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 616 | "dev": true, 617 | "requires": { 618 | "fill-range": "^7.0.1" 619 | } 620 | }, 621 | "chokidar": { 622 | "version": "3.5.3", 623 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 624 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 625 | "dev": true, 626 | "requires": { 627 | "anymatch": "~3.1.2", 628 | "braces": "~3.0.2", 629 | "fsevents": "~2.3.2", 630 | "glob-parent": "~5.1.2", 631 | "is-binary-path": "~2.1.0", 632 | "is-glob": "~4.0.1", 633 | "normalize-path": "~3.0.0", 634 | "readdirp": "~3.6.0" 635 | } 636 | }, 637 | "concat-map": { 638 | "version": "0.0.1", 639 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 640 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 641 | "dev": true 642 | }, 643 | "debug": { 644 | "version": "3.2.7", 645 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 646 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 647 | "dev": true, 648 | "requires": { 649 | "ms": "^2.1.1" 650 | } 651 | }, 652 | "fill-range": { 653 | "version": "7.0.1", 654 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 655 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 656 | "dev": true, 657 | "requires": { 658 | "to-regex-range": "^5.0.1" 659 | } 660 | }, 661 | "fs.realpath": { 662 | "version": "1.0.0", 663 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 664 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 665 | "dev": true 666 | }, 667 | "fsevents": { 668 | "version": "2.3.2", 669 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 670 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 671 | "dev": true, 672 | "optional": true 673 | }, 674 | "function-bind": { 675 | "version": "1.1.1", 676 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 677 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 678 | "dev": true 679 | }, 680 | "glob": { 681 | "version": "7.2.3", 682 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 683 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 684 | "dev": true, 685 | "requires": { 686 | "fs.realpath": "^1.0.0", 687 | "inflight": "^1.0.4", 688 | "inherits": "2", 689 | "minimatch": "^3.1.1", 690 | "once": "^1.3.0", 691 | "path-is-absolute": "^1.0.0" 692 | } 693 | }, 694 | "glob-parent": { 695 | "version": "5.1.2", 696 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 697 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 698 | "dev": true, 699 | "requires": { 700 | "is-glob": "^4.0.1" 701 | } 702 | }, 703 | "has": { 704 | "version": "1.0.3", 705 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 706 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 707 | "dev": true, 708 | "requires": { 709 | "function-bind": "^1.1.1" 710 | } 711 | }, 712 | "has-flag": { 713 | "version": "3.0.0", 714 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 715 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 716 | "dev": true 717 | }, 718 | "ignore-by-default": { 719 | "version": "1.0.1", 720 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 721 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 722 | "dev": true 723 | }, 724 | "inflight": { 725 | "version": "1.0.6", 726 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 727 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 728 | "dev": true, 729 | "requires": { 730 | "once": "^1.3.0", 731 | "wrappy": "1" 732 | } 733 | }, 734 | "inherits": { 735 | "version": "2.0.4", 736 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 737 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 738 | "dev": true 739 | }, 740 | "interpret": { 741 | "version": "1.4.0", 742 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", 743 | "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", 744 | "dev": true 745 | }, 746 | "is-binary-path": { 747 | "version": "2.1.0", 748 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 749 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 750 | "dev": true, 751 | "requires": { 752 | "binary-extensions": "^2.0.0" 753 | } 754 | }, 755 | "is-core-module": { 756 | "version": "2.11.0", 757 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 758 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 759 | "dev": true, 760 | "requires": { 761 | "has": "^1.0.3" 762 | } 763 | }, 764 | "is-extglob": { 765 | "version": "2.1.1", 766 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 767 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 768 | "dev": true 769 | }, 770 | "is-glob": { 771 | "version": "4.0.3", 772 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 773 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 774 | "dev": true, 775 | "requires": { 776 | "is-extglob": "^2.1.1" 777 | } 778 | }, 779 | "is-number": { 780 | "version": "7.0.0", 781 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 782 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 783 | "dev": true 784 | }, 785 | "minimatch": { 786 | "version": "3.1.2", 787 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 788 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 789 | "dev": true, 790 | "requires": { 791 | "brace-expansion": "^1.1.7" 792 | } 793 | }, 794 | "minimist": { 795 | "version": "1.2.8", 796 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 797 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 798 | "dev": true 799 | }, 800 | "ms": { 801 | "version": "2.1.3", 802 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 803 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 804 | "dev": true 805 | }, 806 | "nodemon": { 807 | "version": "2.0.20", 808 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", 809 | "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", 810 | "dev": true, 811 | "requires": { 812 | "chokidar": "^3.5.2", 813 | "debug": "^3.2.7", 814 | "ignore-by-default": "^1.0.1", 815 | "minimatch": "^3.1.2", 816 | "pstree.remy": "^1.1.8", 817 | "semver": "^5.7.1", 818 | "simple-update-notifier": "^1.0.7", 819 | "supports-color": "^5.5.0", 820 | "touch": "^3.1.0", 821 | "undefsafe": "^2.0.5" 822 | } 823 | }, 824 | "nopt": { 825 | "version": "1.0.10", 826 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 827 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 828 | "dev": true, 829 | "requires": { 830 | "abbrev": "1" 831 | } 832 | }, 833 | "normalize-path": { 834 | "version": "3.0.0", 835 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 836 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 837 | "dev": true 838 | }, 839 | "once": { 840 | "version": "1.4.0", 841 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 842 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 843 | "dev": true, 844 | "requires": { 845 | "wrappy": "1" 846 | } 847 | }, 848 | "path-is-absolute": { 849 | "version": "1.0.1", 850 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 851 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 852 | "dev": true 853 | }, 854 | "path-parse": { 855 | "version": "1.0.7", 856 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 857 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 858 | "dev": true 859 | }, 860 | "picomatch": { 861 | "version": "2.3.1", 862 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 863 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 864 | "dev": true 865 | }, 866 | "pstree.remy": { 867 | "version": "1.1.8", 868 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 869 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 870 | "dev": true 871 | }, 872 | "readdirp": { 873 | "version": "3.6.0", 874 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 875 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 876 | "dev": true, 877 | "requires": { 878 | "picomatch": "^2.2.1" 879 | } 880 | }, 881 | "rechoir": { 882 | "version": "0.6.2", 883 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 884 | "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", 885 | "dev": true, 886 | "requires": { 887 | "resolve": "^1.1.6" 888 | } 889 | }, 890 | "resolve": { 891 | "version": "1.22.1", 892 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 893 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 894 | "dev": true, 895 | "requires": { 896 | "is-core-module": "^2.9.0", 897 | "path-parse": "^1.0.7", 898 | "supports-preserve-symlinks-flag": "^1.0.0" 899 | } 900 | }, 901 | "semver": { 902 | "version": "5.7.1", 903 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 904 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 905 | "dev": true 906 | }, 907 | "shelljs": { 908 | "version": "0.8.5", 909 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", 910 | "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", 911 | "dev": true, 912 | "requires": { 913 | "glob": "^7.0.0", 914 | "interpret": "^1.0.0", 915 | "rechoir": "^0.6.2" 916 | } 917 | }, 918 | "shx": { 919 | "version": "0.3.4", 920 | "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", 921 | "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", 922 | "dev": true, 923 | "requires": { 924 | "minimist": "^1.2.3", 925 | "shelljs": "^0.8.5" 926 | } 927 | }, 928 | "simple-update-notifier": { 929 | "version": "1.1.0", 930 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", 931 | "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", 932 | "dev": true, 933 | "requires": { 934 | "semver": "~7.0.0" 935 | }, 936 | "dependencies": { 937 | "semver": { 938 | "version": "7.0.0", 939 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", 940 | "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", 941 | "dev": true 942 | } 943 | } 944 | }, 945 | "supports-color": { 946 | "version": "5.5.0", 947 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 948 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 949 | "dev": true, 950 | "requires": { 951 | "has-flag": "^3.0.0" 952 | } 953 | }, 954 | "supports-preserve-symlinks-flag": { 955 | "version": "1.0.0", 956 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 957 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 958 | "dev": true 959 | }, 960 | "to-regex-range": { 961 | "version": "5.0.1", 962 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 963 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 964 | "dev": true, 965 | "requires": { 966 | "is-number": "^7.0.0" 967 | } 968 | }, 969 | "touch": { 970 | "version": "3.1.0", 971 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 972 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 973 | "dev": true, 974 | "requires": { 975 | "nopt": "~1.0.10" 976 | } 977 | }, 978 | "undefsafe": { 979 | "version": "2.0.5", 980 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 981 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 982 | "dev": true 983 | }, 984 | "wrappy": { 985 | "version": "1.0.2", 986 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 987 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 988 | "dev": true 989 | } 990 | } 991 | } 992 | -------------------------------------------------------------------------------- /02-global/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "global", 3 | "private": true, 4 | "scripts": { 5 | "init": "shx mkdir -p ../dist", 6 | "build": "npm run init && shx cp -r manifest.json icons/ ../dist/", 7 | "dev": "nodemon -e js,ts,html,css,json,vue,ico,png --exec \"npm run build\"" 8 | }, 9 | "devDependencies": { 10 | "nodemon": "^2.0.20", 11 | "shx": "^0.3.4" 12 | }, 13 | "dependencies": {} 14 | } 15 | -------------------------------------------------------------------------------- /03-options/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /03-options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | GPT Anywhere - Options 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /03-options/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "options", 3 | "private": true, 4 | "scripts": { 5 | "debug": "vite", 6 | "build": "run-p type-check build-only", 7 | "preview": "vite preview", 8 | "build-only": "vite build", 9 | "type-check": "vue-tsc --noEmit", 10 | "dev": "nodemon --watch ./ --watch ../01-shared -e js,ts,html,css,json,vue,ico,png --exec \"npm run build\"" 11 | }, 12 | "dependencies": { 13 | "bootstrap": "^5.3.0-alpha1", 14 | "pinia": "^2.0.33", 15 | "vue": "^3.2.45" 16 | }, 17 | "devDependencies": { 18 | "@types/bootstrap": "^5.2.6", 19 | "@types/node": "^18.15.13", 20 | "@vitejs/plugin-vue": "^4.0.0", 21 | "@vue/tsconfig": "^0.1.3", 22 | "chrome-types": "^0.1.171", 23 | "nodemon": "^2.0.20", 24 | "npm-run-all": "^4.1.5", 25 | "typescript": "^5.0.4", 26 | "vite": "^4.0.0", 27 | "vue-tsc": "^1.0.12" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /03-options/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheeseDurger/gpt-anywhere/2528de71f8eea908b74c8d1f2aaad4f6e14b18c9/03-options/public/favicon.ico -------------------------------------------------------------------------------- /03-options/src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /03-options/src/components/ApiKey.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | -------------------------------------------------------------------------------- /03-options/src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03-options/src/components/Header.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03-options/src/components/Prompt.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 104 | -------------------------------------------------------------------------------- /03-options/src/components/Prompts.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 31 | -------------------------------------------------------------------------------- /03-options/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import { createPinia } from 'pinia' 3 | import App from './App.vue'; 4 | 5 | import "bootstrap/dist/css/bootstrap.min.css"; 6 | 7 | const pinia = createPinia(); 8 | const app = createApp(App); 9 | 10 | app.use(pinia); 11 | app.mount('#app'); 12 | 13 | import "bootstrap/dist/js/bootstrap.bundle.min.js"; 14 | -------------------------------------------------------------------------------- /03-options/src/store.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { ref, watch } from "vue"; 3 | import { DataDTO, PromptDTO } from "../../01-shared/StorageDTO"; 4 | import { ApiRequest, Endpoint, SaveDataRequest } from "../../01-shared/ApiDTO/ApiRequest"; 5 | import { DataResponse } from "../../01-shared/ApiDTO/ApiResponse"; 6 | 7 | export const useStorageStore = defineStore('storage', () => { 8 | 9 | // Store state 10 | const apiKey = ref(""); 11 | const prompts = ref([]); 12 | 13 | // Store state watchers 14 | watch( 15 | apiKey, 16 | async (newApiKey: string) => { 17 | saveData(newApiKey, prompts.value); 18 | }, 19 | ); 20 | watch( 21 | prompts, 22 | async (newPrompts: PromptDTO[]) => { 23 | saveData(apiKey.value, newPrompts); 24 | }, 25 | { deep: true }, 26 | ); 27 | 28 | 29 | // Store actions 30 | async function load() { 31 | 32 | let request: ApiRequest = { endpoint: Endpoint.GET_DATA, payload: undefined }; 33 | let response: unknown = await chrome.runtime.sendMessage(request); 34 | 35 | // Guard clause: response is not a DataResponse 36 | if (!DataResponse.isDataResponse(response)) return; 37 | 38 | // Guard clause: response is not successful 39 | if (!response.success) return; 40 | 41 | apiKey.value = response.data.apiKey; 42 | prompts.value = response.data.prompts; 43 | }; 44 | 45 | return { apiKey, prompts, load }; 46 | 47 | }); 48 | 49 | /** 50 | * Sends a message to the background script to save the data in storage. 51 | * @param apiKey 52 | * @param prompts 53 | */ 54 | async function saveData(apiKey: string, prompts: PromptDTO[]) { 55 | const data = new DataDTO(apiKey, prompts); 56 | const request: SaveDataRequest = new SaveDataRequest(data); 57 | await chrome.runtime.sendMessage(request); 58 | }; 59 | -------------------------------------------------------------------------------- /03-options/tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /03-options/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | }, 9 | "lib": [ 10 | "ES2017", 11 | "DOM", 12 | "DOM.Iterable" 13 | ], 14 | }, 15 | 16 | "references": [ 17 | { 18 | "path": "./tsconfig.config.json" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /03-options/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url'; 2 | 3 | import { defineConfig } from 'vite'; 4 | import vue from '@vitejs/plugin-vue'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | base: "/options", 9 | build: { 10 | outDir: "../dist/options", 11 | emptyOutDir: true, 12 | }, 13 | plugins: [vue()], 14 | resolve: { 15 | alias: { 16 | '@': fileURLToPath(new URL('../01-shared', import.meta.url)) 17 | } 18 | }, 19 | server: { 20 | fs: { 21 | // Allow serving files from one level up to the project root 22 | allow: ['..'], 23 | }, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /04-background/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | __*.ts 3 | -------------------------------------------------------------------------------- /04-background/01-use-cases/GetData.ts: -------------------------------------------------------------------------------- 1 | import { DataDTO } from "../../01-shared/StorageDTO"; 2 | import { DataResponse } from "../../01-shared/ApiDTO/ApiResponse"; 3 | import { storageFactory } from "../02-ports/output/Storage"; 4 | 5 | export class GetData { 6 | public async handle(): Promise { 7 | const data: DataDTO = await storageFactory().get(); 8 | const response: DataResponse = new DataResponse(true, data); 9 | return response; 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /04-background/01-use-cases/OpenAndComplete.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { config } from "../../01-shared/config"; 3 | import { DataDTO, PromptDTO } from "../../01-shared/StorageDTO"; 4 | import { CompleteRequest, OpenRequest } from "../../01-shared/ApiDTO/ApiRequest"; 5 | import { AiPort, aiFactory } from "../02-ports/output/Ai"; 6 | import { PublisherPort, publisherFactory } from "../02-ports/output/Publisher"; 7 | import { storageFactory } from "../02-ports/output/Storage"; 8 | 9 | /** 10 | * @description publish a completion from 11 | */ 12 | export class OpenAndCompleteUseCase { 13 | 14 | /** 15 | * Open a port to target tab and stream completion 16 | * 17 | * @description 18 | * 1. Build final prompt text 19 | * 2. Call API method to get completion 20 | */ 21 | public async handleFromContextMenu(menuItemId: number, selectionText: string, tabId: number): Promise { 22 | 23 | const data: DataDTO = await storageFactory().get(); 24 | const index: number = data.prompts.findLastIndex((prompt: PromptDTO) => prompt.id === menuItemId); 25 | 26 | // Guard clause: prompt must be found 27 | if (index === -1) { 28 | console.error("ERROR: prompt not found"); 29 | return; 30 | }; 31 | 32 | const promptText: string = this.buildFinalPrompt(data.prompts[index].value, selectionText); 33 | 34 | const request = new CompleteRequest( 35 | promptText, 36 | selectionText, 37 | tabId, 38 | ); 39 | await this.handleFromAPI(request); 40 | }; 41 | 42 | /** 43 | * Open a port to target tab and stream completion 44 | */ 45 | public async handleFromAPI(request: CompleteRequest): Promise { 46 | 47 | // Open modal 48 | const openRequest: OpenRequest = new OpenRequest( 49 | request.payload.promptText, 50 | request.payload.selectionText, 51 | ); 52 | await publisherFactory().openModal(request.payload.tabId, openRequest); 53 | 54 | // Build prompt 55 | const data: DataDTO = await storageFactory().get(); 56 | const prompt: string = this.buildFinalPrompt( 57 | request.payload.promptText, 58 | request.payload.selectionText, 59 | ); 60 | 61 | // Retrieve completion and send to modal 62 | try { 63 | const ai: AiPort = aiFactory(data.apiKey); 64 | const reader: ReadableStreamDefaultReader = await ai.getCompletion(prompt); 65 | const publisher: PublisherPort = publisherFactory(); 66 | await publisher.publish(request.payload.tabId, reader); 67 | } catch (error) { 68 | console.error("ERROR: error from OpenAI servers\n", error); 69 | } 70 | 71 | }; 72 | 73 | /** 74 | * Build final prompt 75 | * @param promptText 76 | * @param selectedText 77 | * @returns final prompt 78 | */ 79 | private buildFinalPrompt(promptText: string, selectionText: string): string { 80 | return promptText.replaceAll(config.prompt.susbstitutionPlaceholder, selectionText); 81 | }; 82 | 83 | }; 84 | -------------------------------------------------------------------------------- /04-background/01-use-cases/OpenOptions.ts: -------------------------------------------------------------------------------- 1 | import { optionsFactory } from "../02-ports/output/Options"; 2 | 3 | export class OpenOptionsUseCase { 4 | 5 | public async handle(): Promise { 6 | await optionsFactory().open(); 7 | }; 8 | 9 | }; -------------------------------------------------------------------------------- /04-background/01-use-cases/SetContextMenus.ts: -------------------------------------------------------------------------------- 1 | import { DataDTO } from "../../01-shared/StorageDTO"; 2 | import { contextMenusFactory } from "../02-ports/output/ContextMenus"; 3 | import { storageFactory } from "../02-ports/output/Storage"; 4 | 5 | export class SetContextMenusUseCase { 6 | 7 | public async handle(): Promise { 8 | const data: DataDTO = await storageFactory().get(); 9 | await contextMenusFactory().update(data.prompts); 10 | }; 11 | 12 | }; 13 | -------------------------------------------------------------------------------- /04-background/01-use-cases/SetData.ts: -------------------------------------------------------------------------------- 1 | import { DataDTO } from "../../01-shared/StorageDTO"; 2 | import { storageFactory } from "../02-ports/output/Storage"; 3 | 4 | export class SetData { 5 | public async handle(data: DataDTO): Promise { 6 | await storageFactory().save(data); 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /04-background/01-use-cases/ValidateSchema.ts: -------------------------------------------------------------------------------- 1 | import { DataDTO } from "../../01-shared/StorageDTO"; 2 | import { storageFactory, StoragePort } from "../02-ports/output/Storage"; 3 | 4 | export class ValidateSchemaUseCase { 5 | 6 | public async handle(): Promise { 7 | const storage: StoragePort = storageFactory(); 8 | const unvalidatedData: unknown = await storage.get(); 9 | const validatedData: DataDTO = storage.validate(unvalidatedData); 10 | await storage.save(validatedData); 11 | }; 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /04-background/02-ports/output/Ai.ts: -------------------------------------------------------------------------------- 1 | import { config } from "../../config"; 2 | import { OpenAIAdapter } from "../../03-adapters/secondary/OpenAI"; 3 | 4 | export interface AiPort { 5 | getCompletion: (prompt: string) => Promise< ReadableStreamDefaultReader >; 6 | }; 7 | 8 | export function aiFactory(apiKey: string): AiPort { 9 | return new OpenAIAdapter(apiKey, config.openai.endpoint, config.openai.model); 10 | }; 11 | -------------------------------------------------------------------------------- /04-background/02-ports/output/ContextMenus.ts: -------------------------------------------------------------------------------- 1 | import { PromptDTO } from "../../../01-shared/StorageDTO"; 2 | import { ChromeContextMenusAdapter } from "../../03-adapters/secondary/ChromeContextMenus"; 3 | 4 | export interface ContextMenusPort { 5 | update: (prompts: PromptDTO[]) => void; 6 | }; 7 | 8 | export function contextMenusFactory(): ContextMenusPort { 9 | return new ChromeContextMenusAdapter(); 10 | }; 11 | -------------------------------------------------------------------------------- /04-background/02-ports/output/DTO.ts: -------------------------------------------------------------------------------- 1 | export interface Model { 2 | name: string; 3 | maxTokens: number; 4 | }; 5 | -------------------------------------------------------------------------------- /04-background/02-ports/output/Options.ts: -------------------------------------------------------------------------------- 1 | import { ChromeOptionsAdapter } from "../../03-adapters/secondary/ChromeOptions"; 2 | 3 | export interface OptionsPort { 4 | open: () => Promise; 5 | }; 6 | 7 | export function optionsFactory() { 8 | return new ChromeOptionsAdapter(); 9 | }; 10 | -------------------------------------------------------------------------------- /04-background/02-ports/output/Publisher.ts: -------------------------------------------------------------------------------- 1 | import { OpenRequest } from "../../../01-shared/ApiDTO/ApiRequest"; 2 | import { ChromePublisherAdapter } from "../../03-adapters/secondary/ChromePublisher"; 3 | 4 | export interface PublishRequest { 5 | reader: ReadableStreamDefaultReader; 6 | location: chrome.tabs.Tab; 7 | }; 8 | 9 | export interface PublisherPort { 10 | publish: (tabId: number, reader: ReadableStreamDefaultReader) => Promise; 11 | openModal: (tabId: number, request: OpenRequest) => Promise; 12 | }; 13 | 14 | export function publisherFactory(): PublisherPort { 15 | return new ChromePublisherAdapter(); 16 | }; 17 | -------------------------------------------------------------------------------- /04-background/02-ports/output/Storage.ts: -------------------------------------------------------------------------------- 1 | import { DataDTO } from "../../../01-shared/StorageDTO"; 2 | import { ChromeStorageAdapter } from "../../03-adapters/secondary/ChromeStorage"; 3 | 4 | export interface StoragePort { 5 | get: () => Promise; 6 | save: (data: DataDTO) => Promise; 7 | validate: (data: unknown) => DataDTO; 8 | }; 9 | 10 | export function storageFactory(): StoragePort { 11 | return new ChromeStorageAdapter(); 12 | }; 13 | -------------------------------------------------------------------------------- /04-background/03-adapters/primary/ApiAdapter.ts: -------------------------------------------------------------------------------- 1 | import { OpenAndCompleteUseCase } from "../../01-use-cases/OpenAndComplete"; 2 | import { ApiRequest, CompleteFromTabRequest, CompleteRequest, Endpoint, SaveDataRequest } from "../../../01-shared/ApiDTO/ApiRequest"; 3 | import { ApiResponse, DataResponse } from "../../../01-shared/ApiDTO/ApiResponse"; 4 | import { GetData } from "../../01-use-cases/GetData"; 5 | import { SetData } from "../../01-use-cases/SetData"; 6 | 7 | export class ApiAdapter { 8 | 9 | /** 10 | * Register message handlers for the extension's API 11 | */ 12 | public init() { 13 | /** 14 | * The callback of `.onMessage` should return `true` in order to keep 15 | * the internal messaging channel open so that sendResponse() 16 | * can work asynchronously. 17 | */ 18 | chrome.runtime.onMessage.addListener(this.route); 19 | }; 20 | 21 | private route( 22 | request: ApiRequest, 23 | sender: chrome.runtime.MessageSender, 24 | sendResponse: (ApiResponse: ApiResponse) => void, 25 | ): boolean { 26 | 27 | switch (request.endpoint) { 28 | 29 | case Endpoint.COMPLETE: 30 | 31 | // Guard clause 32 | if (!CompleteFromTabRequest.isCompleteFromTabRequest(request)) { 33 | console.error("ERROR: payload malformed"); 34 | sendResponse(new ApiResponse(false, {message: "ERROR: payload malformed"})); 35 | return false; 36 | }; 37 | 38 | // Guard clause 39 | if (sender.tab?.id === undefined) { 40 | console.error("ERROR: tab id undefined"); 41 | sendResponse(new ApiResponse(false, {message: "ERROR: tab id undefined"})); 42 | return false; 43 | }; 44 | 45 | const completeRequest = new CompleteRequest( 46 | request.payload.promptText, 47 | request.payload.selectionText, 48 | sender.tab.id 49 | ); 50 | new OpenAndCompleteUseCase().handleFromAPI(completeRequest); 51 | return false; 52 | break; 53 | 54 | case Endpoint.GET_DATA: 55 | new GetData().handle().then( (response: DataResponse) => sendResponse(response) ); 56 | return true; 57 | break; 58 | 59 | case Endpoint.SAVE_DATA: 60 | 61 | // Guard clause 62 | if (!SaveDataRequest.isSaveDataRequest(request)) { 63 | console.error("ERROR: request malformed"); 64 | return false; 65 | } 66 | 67 | new SetData().handle(request.payload); 68 | return false; 69 | break; 70 | 71 | default: 72 | console.log("STOPPED in switch"); 73 | return false; 74 | break; 75 | } 76 | }; 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /04-background/03-adapters/primary/ChromeContextMenusAdapter.ts: -------------------------------------------------------------------------------- 1 | import { SetContextMenusUseCase } from "../../01-use-cases/SetContextMenus"; 2 | 3 | export class ChromeContextMenusAdapter { 4 | 5 | /** 6 | * Register Chrome Context Menus 7 | */ 8 | public init(): void { 9 | this.onBrowserEvents(new SetContextMenusUseCase().handle); 10 | }; 11 | 12 | private onBrowserEvents(callback: () => Promise): void { 13 | chrome.runtime.onInstalled.addListener(callback); 14 | chrome.storage.sync.onChanged.addListener(callback); 15 | }; 16 | 17 | }; 18 | -------------------------------------------------------------------------------- /04-background/03-adapters/primary/ChromeOpenAndCompleteAdapter.ts: -------------------------------------------------------------------------------- 1 | import { OpenAndCompleteUseCase } from "../../01-use-cases/OpenAndComplete"; 2 | 3 | export class ChromeOpenAndCompleteAdapter { 4 | 5 | /** 6 | * Register a handler on Chrome Context Menu click 7 | * The handler streams a completion to the tab where the Context Menu was clicked 8 | */ 9 | public init(): void { 10 | 11 | chrome.contextMenus.onClicked.addListener(this.complete); 12 | 13 | }; 14 | 15 | private async complete(info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab | undefined): Promise { 16 | // Guard clause: 17 | // - Tabs might be undefined (eg. click outside a tab) 18 | // - Tabs might not have ids (eg. tabs without ids) 19 | if (tab?.id === undefined) return; 20 | 21 | const menuItemId: number = typeof info.menuItemId === "string" ? parseInt(info.menuItemId) : info.menuItemId; 22 | 23 | await new OpenAndCompleteUseCase().handleFromContextMenu( 24 | menuItemId, 25 | info.selectionText ?? "", 26 | tab.id 27 | ); 28 | 29 | } 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /04-background/03-adapters/primary/ChromeOptionsAdapter.ts: -------------------------------------------------------------------------------- 1 | import { OpenOptionsUseCase } from "../../01-use-cases/OpenOptions"; 2 | 3 | export class ChromeOptionsAdapter { 4 | public init(): void { 5 | this.onBrowserEvents(new OpenOptionsUseCase().handle); 6 | }; 7 | 8 | private onBrowserEvents = (callback: () => void): void => { 9 | chrome.runtime.onInstalled.addListener(async (details) => { 10 | if (details.reason === "install") callback(); 11 | }); 12 | 13 | chrome.action.onClicked.addListener(callback); 14 | }; 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /04-background/03-adapters/primary/ChromeStorageAdapter.ts: -------------------------------------------------------------------------------- 1 | import { ValidateSchemaUseCase } from "../../01-use-cases/ValidateSchema"; 2 | 3 | export class ChromeStorageAdapter { 4 | 5 | /** 6 | * Ensure data schema is up-to-date 7 | */ 8 | public init(): void { 9 | this.onBrowserEvents(new ValidateSchemaUseCase().handle); 10 | }; 11 | 12 | private onBrowserEvents = (callback: () => void): void => { 13 | chrome.runtime.onInstalled.addListener(async (details) => { 14 | if (details.reason === "install" || details.reason === "update") callback(); 15 | }); 16 | }; 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /04-background/03-adapters/secondary/ChromeContextMenus.ts: -------------------------------------------------------------------------------- 1 | import { PromptDTO } from "../../../01-shared/StorageDTO"; 2 | import { ContextMenusPort } from "../../02-ports/output/ContextMenus"; 3 | 4 | export class ChromeContextMenusAdapter implements ContextMenusPort { 5 | /** 6 | * Update 1 context menu for each prompt 7 | * @param prompts 8 | */ 9 | public update(prompts: PromptDTO[]): void { 10 | 11 | chrome.contextMenus.removeAll(); 12 | 13 | prompts.forEach((prompt: PromptDTO, index: number) => { 14 | chrome.contextMenus.create({ 15 | id: index.toString(), 16 | title: this.title(prompt.name), 17 | type: 'normal', 18 | contexts: ['all'], 19 | }); 20 | }); 21 | 22 | }; 23 | 24 | private title(promptName: string): string { 25 | return "Generate: " + promptName; 26 | }; 27 | 28 | }; 29 | -------------------------------------------------------------------------------- /04-background/03-adapters/secondary/ChromeOptions.ts: -------------------------------------------------------------------------------- 1 | import { OptionsPort } from "../../02-ports/output/Options"; 2 | 3 | export class ChromeOptionsAdapter implements OptionsPort { 4 | 5 | public async open(): Promise { 6 | await chrome.runtime.openOptionsPage(); 7 | }; 8 | 9 | }; 10 | -------------------------------------------------------------------------------- /04-background/03-adapters/secondary/ChromePublisher.ts: -------------------------------------------------------------------------------- 1 | import { config } from "../../../01-shared/config"; 2 | import { PortName } from "../../../01-shared/types"; 3 | import { OpenRequest } from "../../../01-shared/ApiDTO/ApiRequest"; 4 | import { PublisherPort } from "../../02-ports/output/Publisher"; 5 | 6 | export class ChromePublisherAdapter implements PublisherPort { 7 | 8 | /** 9 | * Send request to content script to open the modal 10 | * @param tabId tab id to send the request to 11 | * @param request request to be sent 12 | */ 13 | public async openModal(tabId: number, request: OpenRequest): Promise { 14 | await chrome.tabs.sendMessage(tabId, request); 15 | }; 16 | 17 | /** 18 | * Post messages on web page. An error message will be posted 19 | * if OpenAI is too slow to send messages. 20 | * @param reader 21 | * @param port 22 | */ 23 | public async publish(tabId: number, reader: ReadableStreamDefaultReader): Promise { 24 | 25 | const port: chrome.runtime.Port = chrome.tabs.connect(tabId, { name: PortName.COMPLETE }); 26 | 27 | // Publish message from GPT to web page 28 | while (true) { 29 | const timeout = new Promise((resolve, reject) => setTimeout(resolve, config.openai.timeout, "")); 30 | const result: ReadableStreamReadResult | string = await Promise.race([reader.read(), timeout]); 31 | 32 | // If timeout, then post timeout message and stop 33 | if (typeof result === "string") { 34 | console.error("ERROR: timeout from OpenAI servers\n"); 35 | port.postMessage("ERROR: timeout from OpenAI servers\n"); 36 | break; 37 | } 38 | 39 | // If stream is closed, then stop 40 | if (result.done) break; 41 | 42 | // Else post stream message 43 | port.postMessage(result.value); 44 | } 45 | 46 | port.disconnect(); 47 | 48 | }; 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /04-background/03-adapters/secondary/ChromeStorage.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { DataDTO } from "../../../01-shared/StorageDTO"; 3 | import { StoragePort } from "../../02-ports/output/Storage"; 4 | 5 | /** 6 | * Get data from storage 7 | * @returns data from sync storage 8 | */ 9 | export class ChromeStorageAdapter implements StoragePort { 10 | 11 | public async get(): Promise { 12 | const response = await chrome.storage.sync.get(); 13 | if (DataDTO.isDataDTO(response)) return response; 14 | else return new DataDTO("", []); 15 | }; 16 | 17 | public async save(data: DataDTO): Promise { 18 | await chrome.storage.sync.set(data); 19 | }; 20 | 21 | public validate(data: unknown): DataDTO { 22 | 23 | const index = schemas.findLastIndex( schema => schema.safeParse(data).success ); 24 | 25 | // Guard clause : return empty data object if no schema validated 26 | if (index === -1) return new DataDTO("", []); 27 | 28 | for (let i = index; i < migrations.length; i++) { 29 | const transform = migrations[i].safeParse(data); 30 | if (transform.success) data = transform.data; 31 | else return new DataDTO("", []); 32 | } 33 | return data as DataDTO; 34 | }; 35 | 36 | }; 37 | 38 | 39 | /** 40 | * Array containing the storage schema history 41 | * 42 | * @description this array contains the schemas sorted from version 0 to the latest version 43 | * For instance: `schemas[2]` is the storage schema version 2 44 | * 45 | * @example const isDataV1: boolean = schemas[1].safeParse(data).success; 46 | */ 47 | const schemas = [ 48 | 49 | z.object({ 50 | apiKey: z.string().catch(""), 51 | prompts: z.array( 52 | z.object({ 53 | name: z.string().catch(""), 54 | value: z.string().catch(""), 55 | }) 56 | ).catch([]), 57 | }), 58 | 59 | z.object({ 60 | version: z.literal(1), 61 | apiKey: z.string().catch(""), 62 | prompts: z.array( 63 | z.object({ 64 | id: z.number().catch( Math.floor(10**15 * Math.random()) ), 65 | name: z.string().catch(""), 66 | value: z.string().catch(""), 67 | }) 68 | ).catch([]), 69 | }) as z.ZodType, 70 | 71 | ]; 72 | 73 | /** 74 | * Array containing the functions to migrate from 1 schema version to the next 75 | * 76 | * @summary This array contains the schemas migration functions 77 | * sorted from the 1st migration function (migrate from version 0 to version 1) 78 | * to the latest migration function (migrate from version n-1 to version n) 79 | * 80 | * @example let dataV1 = migrations[0].parse(dataV0); 81 | */ 82 | const migrations = [ 83 | 84 | // Migrate from schemas[0] to schemas[1] 85 | schemas[0].transform( schema => { 86 | return { 87 | version: 1, 88 | apiKey: schema.apiKey, 89 | prompts: schema.prompts.map( (prompt, index: number) => { 90 | return { 91 | id: index, 92 | name: prompt.name, 93 | value: prompt.value, 94 | }; 95 | }), 96 | }; 97 | }), 98 | 99 | ]; 100 | -------------------------------------------------------------------------------- /04-background/03-adapters/secondary/OpenAI.ts: -------------------------------------------------------------------------------- 1 | // import { encode, decode } from "gpt-3-encoder"; 2 | import { Model } from "../../02-ports/output/DTO"; 3 | import { AiPort } from "../../02-ports/output/Ai"; 4 | import { config } from "../../../01-shared/config"; 5 | 6 | export class OpenAIAdapter implements AiPort { 7 | 8 | private readonly apiKey: string; 9 | private readonly endpoint: string; 10 | private readonly model: Model; 11 | 12 | constructor(apiKey: string, endpoint: string, model: Model) { 13 | this.apiKey = apiKey; 14 | this.endpoint = endpoint; 15 | this.model = model; 16 | }; 17 | 18 | /** 19 | * Opens a completion stream from GPT and returns a reader for it 20 | * @param prompt 21 | * @returns reader for GPT completion stream 22 | * 23 | * @todo add precise token count with gpt-3-encoder 24 | * Currently waiting for gpt-3-encoder to merge PR #33 to have a precise token count 25 | * In the mean time, we estimate the token count by dividing the number of char 26 | * by 4 and adding a 10% margin. 27 | * @link https://github.com/latitudegames/GPT-3-Encoder/issues/32 28 | * @link https://github.com/latitudegames/GPT-3-Encoder/pull/33 29 | */ 30 | public async getCompletion(prompt: string): Promise> { 31 | 32 | // const promptTokens: number = encode(promptText).length; 33 | // const completionTokens = this.model.sharedTokens - encode(promptText).length; 34 | // Guard clause: check max tokens 35 | // if (completionTokens <= 0) return this.getErrorStream(`ERROR: prompt is about ${promptTokens} tokens. This is above ${this.model.maxTokens} max tokens for prompt + completion\n`); 36 | const promptTokens: number = Math.round( 1.1 * prompt.length / 4 ); 37 | const completionTokens: number = this.model.maxTokens - promptTokens - 200; 38 | 39 | // Initialize timeout 40 | const controller: AbortController = new AbortController(); 41 | const timeoutId = setTimeout(() => controller.abort(), config.openai.timeout); 42 | 43 | let response: Response; 44 | try { 45 | response = await fetch(this.endpoint, { 46 | method: 'POST', 47 | headers: { 48 | 'Content-Type': 'application/json', 49 | Authorization: "Bearer " + this.apiKey, 50 | }, 51 | body: JSON.stringify({ 52 | model: this.model.name, 53 | prompt: prompt, 54 | max_tokens: completionTokens, 55 | stop: "\n\nµµµ", 56 | stream: true, 57 | }), 58 | signal: controller.signal, // timeout 59 | }); 60 | } catch (error) { 61 | console.error("ERROR: timeout from OpenAI servers\n", error); 62 | return this.getErrorStream("ERROR: timeout from OpenAI servers\n"); 63 | } 64 | 65 | // Clear timeout 66 | clearTimeout(timeoutId); 67 | 68 | if (response.body === null) { 69 | console.error("ERROR: response.body is null\n"); 70 | return this.getErrorStream("ERROR: error from OpenAI servers\n"); 71 | } 72 | 73 | const reader: ReadableStreamDefaultReader = response.body 74 | .pipeThrough(new TextDecoderStream()) 75 | .pipeThrough(this.openAiJSON()) 76 | .pipeThrough(this.openAiText()) 77 | .getReader(); 78 | 79 | return reader; 80 | 81 | }; 82 | 83 | /** 84 | * Transform a stream from string to JSON 85 | * @returns `TransformStream` to be used as argument of `ReadableStream.pipeThrough()` 86 | */ 87 | private openAiJSON(): TransformStream { 88 | return new TransformStream({ 89 | /** 90 | * @description transform 1 SSE string event into multiple JSON events 91 | */ 92 | transform(chunkString: string, controller: TransformStreamDefaultController) { 93 | 94 | console.log("OpenAI SSE:\n", chunkString); 95 | 96 | // Guard clause: input is not a string 97 | if (typeof chunkString !== "string") { 98 | console.error("ERROR: OpenAI returned a non string\n:", chunkString); 99 | controller.enqueue(new OpenAIJSONResponse(`ERROR: OpenAI returned "${JSON.stringify(chunkString)}"`)); 100 | controller.terminate(); 101 | return; 102 | } 103 | 104 | const messages: string[] = chunkString 105 | .replace(/\n\n$/g, "") // Remove last 2 ending newlines 106 | .split("\n\n"); // Split every 2 newlines (each message is separated by 2 newlines) 107 | 108 | for (const message of messages) { 109 | // Guard clause: close stream if last stream message 110 | if (message === "data: [DONE]") { 111 | controller.terminate(); 112 | break; 113 | } 114 | 115 | // Guard clause: don't process SSE comments 116 | if (message.charAt(0) === ":") continue; 117 | 118 | // Guard clause: don't process empty stream message 119 | if (message === "data: ") continue; 120 | 121 | // Remove start of message 122 | const jsonString: string = message.replace(/^data: /g, ""); 123 | let result: IObject | Array; 124 | 125 | try { 126 | result = JSON.parse(jsonString) as IObject | Array; // JSONs are objects or arrays 127 | } catch (error) { 128 | // Don't process not valid JSON 129 | console.error("ERROR: json is not valid:\n", jsonString, "\nContinuing without processing..."); 130 | continue; 131 | } 132 | 133 | // Guard clause : don't process arrays 134 | if (Array.isArray(result)) { 135 | console.error("ERROR: json is an array:\n", result, "\nContinuing without processing..."); 136 | continue; 137 | } 138 | 139 | controller.enqueue(result); 140 | }; 141 | 142 | }, 143 | }); 144 | }; 145 | 146 | /** 147 | * Extract OpenAI completion from JSON stream 148 | * @returns `TransformStream` to be used as argument of `ReadableStream.pipeThrough()` 149 | */ 150 | private openAiText(): TransformStream { 151 | return new TransformStream({ 152 | transform(chunkJSON: IObject, controller: TransformStreamDefaultController) { 153 | 154 | // Guard clause: return "Error" if error from OpenAI 155 | if(chunkJSON.error !== undefined) { 156 | console.error("ERROR: OpenAI response has .error property:\n", chunkJSON) 157 | controller.enqueue(`ERROR: OpenAI returned "${JSON.parse(chunkJSON.error)}"`); 158 | controller.terminate(); 159 | return; 160 | } 161 | 162 | const text: any = chunkJSON?.choices?.[0]?.text; 163 | 164 | // Guard clause: return "Error" if malformed JSON 165 | if (typeof text !== "string") { 166 | try { 167 | console.error("ERROR: OpenAI response doesn't have .choices[0].text property:\n", chunkJSON) 168 | controller.enqueue(`ERROR: OpenAI returned malformed json "${JSON.stringify(chunkJSON)}"`); 169 | } catch(error) { 170 | console.error("ERROR: OpenAI returned malformed json:\n", chunkJSON); 171 | controller.enqueue(`ERROR: OpenAI returned malformed json`); 172 | } 173 | controller.terminate(); 174 | return; 175 | } 176 | 177 | controller.enqueue(text); 178 | 179 | }, 180 | }); 181 | }; 182 | 183 | /** 184 | * Get a stream that sends only 1 string message stating an error 185 | * @param message message streamed 186 | * @returns stream 187 | */ 188 | private getErrorStream(message: string): ReadableStreamDefaultReader { 189 | return new ReadableStream({ 190 | start(controller: ReadableStreamDefaultController) { 191 | controller.enqueue(message); 192 | controller.close(); 193 | }, 194 | }).getReader(); 195 | }; 196 | 197 | 198 | } 199 | 200 | class OpenAIJSONResponse { 201 | public readonly choices: {text: string}[]; 202 | 203 | constructor(message: string) { 204 | this.choices = [{ text: message }]; 205 | }; 206 | 207 | }; 208 | 209 | /** 210 | * @interface IObject 211 | * @description default `object` type does not allow properties 212 | */ 213 | interface IObject extends Object { 214 | [key: string]: any; 215 | }; 216 | -------------------------------------------------------------------------------- /04-background/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This folder hosts the background script. It is coded in hexagonal architecture. 3 | 4 | # Folder structure 5 | ``` 6 | . 7 | ├── 01-use-cases # Hosts the script logic 8 | ├── 02-ports # Acts as an interface between the use cases and the adapters 9 | ├── 03-adapters # Adapters for input (primary adapters) and output (secondary adapters) 10 | └── ... 11 | ``` 12 | The flow is : 13 | ```mermaid 14 | flowchart LR 15 | A(start.ts) --> B(%primary adapter%.ts) 16 | B --> C(%use case%.ts) 17 | C --> B 18 | C --> D(%output port%.ts) 19 | D --> C 20 | D --> E(%secondary adapter%.ts) 21 | E --> D 22 | ``` 23 | -------------------------------------------------------------------------------- /04-background/config.ts: -------------------------------------------------------------------------------- 1 | import { Model } from "./02-ports/output/DTO"; 2 | 3 | export const config = { 4 | openai: { 5 | endpoint: "https://api.openai.com/v1/completions", 6 | model: { 7 | name: "text-davinci-003", 8 | maxTokens: 4097, 9 | } as Model, 10 | }, 11 | storage: { 12 | apiKeyKey: "apiKey", 13 | promptsKey: "prompts", 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /04-background/global.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview adds type definition for methods that haven't been typed in vanilla typescript 3 | */ 4 | declare global { 5 | 6 | interface Array { 7 | /** 8 | * Get index of the last element matching a condition 9 | * @param callbackFn A function applied to each element, and returning `true` if the element matches the condition 10 | * @param thisArg A value to use as this when executing callbackFn 11 | * @returns The index of the last element matching the condition (-1 if no element matches the condition) 12 | * 13 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLastIndex Array.prototype.findLastIndex()} 14 | */ 15 | findLastIndex( 16 | callbackFn: (element: T, index: number, array: T[]) => boolean, 17 | thisArg?: any 18 | ): number; 19 | }; 20 | 21 | interface String { 22 | /** 23 | * Replace all instances of pattern in a string 24 | * @param pattern to search for 25 | * @param replacement fo pattern 26 | * @returns a new string with all instances of pattern replaced 27 | * 28 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll String.prototype.replaceAll()} 29 | */ 30 | replaceAll(pattern: string, replacement : string): string; 31 | }; 32 | 33 | }; 34 | 35 | export {}; 36 | -------------------------------------------------------------------------------- /04-background/jest.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /04-background/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "background", 3 | "private": true, 4 | "scripts": { 5 | "build": "esbuild start.ts --bundle --target=chrome58,firefox57,safari11,edge18 --outfile=../dist/background/background.js", 6 | "dev": "npm run build -- --watch", 7 | "test": "npx jest --watchAll" 8 | }, 9 | "devDependencies": { 10 | "@types/jest": "^29.5.1", 11 | "@types/node": "^18.15.13", 12 | "jest": "^29.5.0", 13 | "ts-jest": "^29.1.0", 14 | "ts-node": "^10.9.1", 15 | "typescript": "^5.0.4", 16 | "chrome-types": "^0.1.171", 17 | "esbuild": "^0.17.7" 18 | }, 19 | "dependencies": { 20 | "gpt-3-encoder": "^1.1.4", 21 | "zod": "^3.21.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /04-background/start.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { ChromeOptionsAdapter } from "./03-adapters/primary/ChromeOptionsAdapter"; 3 | import { ChromeContextMenusAdapter } from "./03-adapters/primary/ChromeContextMenusAdapter"; 4 | import { ChromeOpenAndCompleteAdapter } from "./03-adapters/primary/ChromeOpenAndCompleteAdapter"; 5 | import { ChromeStorageAdapter } from "./03-adapters/primary/ChromeStorageAdapter"; 6 | import { ApiAdapter } from "./03-adapters/primary/ApiAdapter"; 7 | 8 | new ChromeStorageAdapter().init(); 9 | new ChromeOptionsAdapter().init(); 10 | new ChromeContextMenusAdapter().init(); 11 | new ChromeOpenAndCompleteAdapter().init(); 12 | new ApiAdapter().init(); -------------------------------------------------------------------------------- /04-background/tests/ChromeStorage.test.ts: -------------------------------------------------------------------------------- 1 | import { ChromeStorageAdapter } from "../03-adapters/secondary/ChromeStorage"; 2 | 3 | describe("Validate:", () => { 4 | 5 | const storage = new ChromeStorageAdapter(); 6 | let data: any, result: any; 7 | const latestVersion = 1; 8 | 9 | describe("from version 0:", () => { 10 | 11 | test("empty object", () => { 12 | data = {}; 13 | result = { 14 | version: latestVersion, 15 | apiKey: "", 16 | prompts: [], 17 | }; 18 | expect(storage.validate(data)).toStrictEqual(result); 19 | }); 20 | 21 | test("object with missing properties", () => { 22 | data = { 23 | apiKey: 2, 24 | }; 25 | result = { 26 | version: latestVersion, 27 | apiKey: "", 28 | prompts: [], 29 | }; 30 | expect(storage.validate(data)).toStrictEqual(result); 31 | }); 32 | 33 | test("array with bad element type", () => { 34 | data = { 35 | apiKey: 2, 36 | prompts: [ 37 | "toto", 38 | ], 39 | }; 40 | result = { 41 | version: latestVersion, 42 | apiKey: "", 43 | prompts: [], 44 | }; 45 | expect(storage.validate(data)).toStrictEqual(result); 46 | }); 47 | 48 | test("array with bad element", () => { 49 | data = { 50 | apiKey: 2, 51 | prompts: [{name: 3, value: 5}], 52 | }; 53 | result = { 54 | version: latestVersion, 55 | apiKey: "", 56 | prompts: [{id: 0, name: "", value: ""}], 57 | }; 58 | expect(storage.validate(data)).toStrictEqual(result); 59 | }); 60 | 61 | test("array with good and bad element", () => { 62 | data = { 63 | apiKey: "2", 64 | prompts: [{name: "3", value: "5"}, "toto"], 65 | }; 66 | result = { 67 | version: latestVersion, 68 | apiKey: "2", 69 | prompts: [], 70 | }; 71 | expect(storage.validate(data)).toStrictEqual(result); 72 | }); 73 | 74 | test("array with good elements", () => { 75 | data = { 76 | apiKey: "2", 77 | prompts: [{name: "3", value: "5"}, {name: "yes", value: "no"}], 78 | }; 79 | result = { 80 | version: latestVersion, 81 | apiKey: "2", 82 | prompts: [{id: 0, name: "3", value: "5"}, {id: 1, name: "yes", value: "no"}], 83 | }; 84 | expect(storage.validate(data)).toStrictEqual(result); 85 | }); 86 | 87 | }); 88 | 89 | describe("from version 1:", () => { 90 | /** 91 | * @todo to implment at next schema version 92 | */ 93 | }); 94 | 95 | }); 96 | -------------------------------------------------------------------------------- /04-background/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /05-content/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .parcel-cache 3 | __*.ts 4 | -------------------------------------------------------------------------------- /05-content/01-use-cases/CompleteUseCase.ts: -------------------------------------------------------------------------------- 1 | import { OpenRequest } from "../../01-shared/ApiDTO/ApiRequest"; 2 | import { storageFactory } from "../02-ports/output/Storage"; 3 | import { Modal } from "./modal/Modal"; 4 | 5 | export class CompleteUseCase { 6 | public async handle(reader: ReadableStreamDefaultReader): Promise { 7 | 8 | const request: OpenRequest = storageFactory().retrieveRequest(); 9 | const modal = new Modal(); 10 | 11 | while (true) { 12 | const result: ReadableStreamReadResult = await reader.read(); 13 | 14 | // If stream is closed, then stop 15 | if (result.done) break; 16 | 17 | // Else post stream message 18 | else modal.addText(result.value); 19 | } 20 | 21 | modal.stopText(); 22 | 23 | }; 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /05-content/01-use-cases/OpenUseCase.ts: -------------------------------------------------------------------------------- 1 | import { OpenRequest } from "../../01-shared/ApiDTO/ApiRequest"; 2 | import { storageFactory } from "../02-ports/output/Storage"; 3 | import { Modal } from "./modal/Modal"; 4 | 5 | export class OpenUseCase { 6 | handle(request: OpenRequest) { 7 | storageFactory().storeRequest(request); 8 | new Modal().open(); 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /05-content/01-use-cases/modal/Modal.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import popupHtml from "bundle-text:./modal.html"; 3 | import { CompleteFromTabRequest, OpenRequest } from "../../../01-shared/ApiDTO/ApiRequest"; 4 | import { backgroundFactory } from "../../02-ports/output/BackgroundPort"; 5 | import { storageFactory } from "../../02-ports/output/Storage"; 6 | 7 | // Unique ID for shadow root host for avoiding collision 8 | // The same ID is used in the SCSS code 9 | const shadowRootHostId: string = "gpt-anywhere-shadow-root-7179470057"; 10 | 11 | export class Modal { 12 | 13 | private readonly host: HTMLElement; 14 | private readonly shadow: ShadowRoot; 15 | private readonly wrapper: HTMLElement; 16 | private readonly content: HTMLElement; 17 | private readonly cursor: HTMLElement; 18 | private readonly success: HTMLElement; 19 | private readonly animationMs: number = 400; 20 | 21 | constructor() { 22 | 23 | const existingShadowRoot: HTMLElement | null = document.getElementById(shadowRootHostId); 24 | 25 | // If modal already exists: get the existing modal 26 | // Else: create a new modal 27 | if (existingShadowRoot !== null && existingShadowRoot.shadowRoot !== null) { 28 | this.host = existingShadowRoot; 29 | this.shadow = existingShadowRoot.shadowRoot; 30 | } else { 31 | 32 | // Create the div that will host the shadow DOM 33 | this.host = document.createElement("div"); 34 | this.host.setAttribute("id", shadowRootHostId); 35 | document.body.appendChild(this.host); 36 | 37 | // Create the shadow root 38 | this.shadow = this.host.attachShadow( { mode: "open" } ); 39 | 40 | // Append popup HTML to shadow root 41 | const documentFragment: DocumentFragment = document.createRange().createContextualFragment(popupHtml); 42 | this.shadow.appendChild(documentFragment); 43 | 44 | } 45 | 46 | this.wrapper = this.shadow.querySelector("#wrapper") ?? (() => {throw new Error("Error : wrapper element not found")})(); 47 | this.content = this.shadow.querySelector("#completion") ?? (() => {throw new Error("Error : content element not found")})(); 48 | this.cursor = this.shadow.querySelector("#cursor") ?? (() => {throw new Error("Error : cursor element not found")})(); 49 | this.success = this.shadow.querySelector("#success") ?? (() => {throw new Error("Error : success element not found")})(); 50 | 51 | }; 52 | 53 | /** 54 | * Opens an empty modal, or empty the existing modal 55 | * @returns this 56 | */ 57 | public open(): void { 58 | 59 | this.content.innerText = ""; 60 | 61 | // Check if modal is open to avoid setting event listeners multiple times 62 | if (!this.wrapper.classList.contains("modal-open")) { 63 | 64 | // Register button handlers 65 | this.shadow.querySelectorAll("#regenerate").forEach(element => { 66 | element.addEventListener("click", this.regenerate); 67 | }); 68 | this.shadow.querySelectorAll("#copy-and-close").forEach(element => { 69 | element.addEventListener("click", this.copyAndClose); 70 | }); 71 | this.shadow.querySelectorAll("#overlay, #close").forEach(element => { 72 | element.addEventListener("click", this.close); 73 | }); 74 | 75 | this.wrapper.classList.add("modal-open"); 76 | } 77 | 78 | }; 79 | 80 | public addText(text: string): void { 81 | this.cursor.hidden = false; 82 | this.content.innerText += text; 83 | }; 84 | 85 | private copy = async (): Promise => { 86 | await navigator.clipboard.writeText(this.content.innerText); 87 | this.displaySuccess(); 88 | setTimeout(this.hideSuccess, this.animationMs); 89 | }; 90 | 91 | public close = (): void => { 92 | 93 | // Unregister button handlers 94 | this.shadow.querySelectorAll("#regenerate").forEach(element => { 95 | element.removeEventListener("click", this.regenerate); 96 | }); 97 | this.shadow.querySelectorAll("#copy-and-close").forEach(element => { 98 | element.removeEventListener("click", this.copyAndClose); 99 | }); 100 | this.shadow.querySelectorAll("#overlay, #close").forEach(element => { 101 | element.removeEventListener("click", this.close); 102 | }); 103 | 104 | this.wrapper.classList.remove("modal-open"); 105 | this.stopText(); 106 | this.host.remove(); 107 | }; 108 | 109 | public copyAndClose = (): void => { 110 | this.copy(); 111 | setTimeout(() => { 112 | this.close(); 113 | }, this.animationMs + 100); 114 | }; 115 | 116 | public stopText(): void { 117 | this.cursor.hidden = true; 118 | }; 119 | 120 | private displaySuccess(): void { 121 | if (this.success?.hidden !== undefined) this.success.hidden = false; 122 | }; 123 | 124 | private hideSuccess(): void { 125 | // success element might have been destroyed by close() method 126 | if (this.success?.hidden !== undefined) this.success.hidden = true; 127 | }; 128 | 129 | private regenerate = async (): Promise => { 130 | const openRequest: OpenRequest = storageFactory().retrieveRequest(); 131 | const request = new CompleteFromTabRequest(openRequest.payload.promptText, openRequest.payload.selectionText); 132 | backgroundFactory().complete(request); 133 | }; 134 | 135 | }; 136 | -------------------------------------------------------------------------------- /05-content/01-use-cases/modal/modal.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 17 |
18 | 21 | -------------------------------------------------------------------------------- /05-content/01-use-cases/modal/modal.scss: -------------------------------------------------------------------------------- 1 | :host(#gpt-anywhere-shadow-root-7179470057) { 2 | #wrapper { 3 | 4 | color: black; 5 | font-family: "Courier New", monospace; 6 | text-align: center; 7 | position: fixed; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; 11 | left: 0; 12 | opacity: 0; 13 | transition: opacity 0.15s linear; 14 | z-index: -1; 15 | 16 | #modal { 17 | 18 | width: 800px; 19 | margin: 30px auto; 20 | max-width: calc(100vw - 20px); 21 | 22 | #modal-content { 23 | width: 100%; 24 | background-color: white; 25 | border-radius: 6px; 26 | box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); 27 | text-align: left; 28 | padding: 20px; 29 | box-sizing: border-box; 30 | 31 | #modal-content-inner { 32 | margin-bottom: 20px; 33 | max-height: calc(100vh - 230px); 34 | overflow: auto; 35 | } 36 | 37 | } 38 | 39 | } 40 | 41 | #overlay { 42 | background: rgba(0, 0, 0, 0.6); 43 | height: 100vh; 44 | width: 100%; 45 | position: fixed; 46 | top: 0; 47 | right: 0; 48 | bottom: 0; 49 | left: 0; 50 | } 51 | 52 | } 53 | 54 | #copy-and-close, #close, #regenerate { 55 | color: white; 56 | font-weight: 600; 57 | display: inline-block; 58 | border: none; 59 | cursor: pointer; 60 | border-radius: 4px; 61 | font-size: 16px; 62 | height: 40px; 63 | background-color: crimson; 64 | padding: 0 20px; 65 | &:hover { 66 | background-color: lightsalmon; 67 | } 68 | } 69 | 70 | #wrapper.modal-open { 71 | opacity: 1; 72 | z-index: 2147483647; /* max integer */ 73 | 74 | #modal { 75 | position: relative; 76 | } 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /05-content/02-ports/output/BackgroundPort.ts: -------------------------------------------------------------------------------- 1 | import { CompleteFromTabRequest } from "../../../01-shared/ApiDTO/ApiRequest"; 2 | import { ChromeBackgroundAdapter } from "../../03-adapters/secondary/ChromeBackgroundAdapter"; 3 | 4 | export interface BackgroundPort { 5 | complete: (request: CompleteFromTabRequest) => void; 6 | }; 7 | 8 | export function backgroundFactory(): BackgroundPort { 9 | return new ChromeBackgroundAdapter(); 10 | }; 11 | -------------------------------------------------------------------------------- /05-content/02-ports/output/Storage.ts: -------------------------------------------------------------------------------- 1 | import { OpenRequest } from "../../../01-shared/ApiDTO/ApiRequest"; 2 | import { SessionStorage } from "../../03-adapters/secondary/SessionStorage"; 3 | 4 | export interface Storage { 5 | storeRequest(request: OpenRequest): void; 6 | retrieveRequest(): OpenRequest; 7 | }; 8 | 9 | export function storageFactory(): Storage { 10 | return new SessionStorage(); 11 | } -------------------------------------------------------------------------------- /05-content/03-adapters/primary/ApiAdapter.ts: -------------------------------------------------------------------------------- 1 | import { CompleteUseCase } from "../../01-use-cases/CompleteUseCase"; 2 | import { PortName } from "../../../01-shared/types"; 3 | import { OpenRequest } from "../../../01-shared/ApiDTO/ApiRequest"; 4 | import { OpenUseCase } from "../../01-use-cases/OpenUseCase"; 5 | 6 | export class ApiAdapter { 7 | 8 | /** 9 | * Register message handlers for the extension's API. 10 | */ 11 | public init(): void { 12 | 13 | // Register function to open Modal 14 | chrome.runtime.onMessage.addListener(this.open); 15 | 16 | // Register function to print completion 17 | // 18 | // The callback of `.onMessage` should return `true` to keep 19 | // the internal messaging channel open so that sendResponse() 20 | // can work asynchronously. 21 | chrome.runtime.onConnect.addListener(this.complete); 22 | 23 | }; 24 | 25 | private open = (request: unknown, sender: chrome.runtime.MessageSender, sendResponse: (response: any) => void): boolean => { 26 | 27 | // Guard clause: request is an open request 28 | if (!OpenRequest.isOpenRequest(request)) return false; 29 | 30 | new OpenUseCase().handle(request); 31 | return false; 32 | }; 33 | 34 | private complete = async (port: chrome.runtime.Port): Promise => { 35 | 36 | // Guard clause: port opened from extension 37 | if (port?.sender?.id !== chrome.runtime.id) { 38 | console.error("Port opened from wrong extension"); 39 | return; 40 | }; 41 | 42 | // Guard clause: right port name 43 | if (port.name !== PortName.COMPLETE) { 44 | console.error("Port opened with wrong name"); 45 | return; 46 | }; 47 | 48 | const reader: ReadableStreamDefaultReader = new CompletionReader(port); 49 | await new CompleteUseCase().handle(reader); 50 | 51 | }; 52 | 53 | }; 54 | 55 | class CompletionReader extends ReadableStreamDefaultReader { 56 | constructor(port: chrome.runtime.Port) { 57 | super(new ReadableStream({ 58 | start(controller) { 59 | port.onMessage.addListener((message: string) => { 60 | controller.enqueue(message); 61 | }); 62 | port.onDisconnect.addListener( () => { 63 | controller.close(); 64 | }); 65 | }, 66 | })); 67 | }; 68 | } -------------------------------------------------------------------------------- /05-content/03-adapters/secondary/ChromeBackgroundAdapter.ts: -------------------------------------------------------------------------------- 1 | import { CompleteFromTabRequest } from "../../../01-shared/ApiDTO/ApiRequest"; 2 | import { BackgroundPort } from "../../02-ports/output/BackgroundPort"; 3 | 4 | export class ChromeBackgroundAdapter implements BackgroundPort { 5 | 6 | public complete(request: CompleteFromTabRequest): void { 7 | chrome.runtime.sendMessage(request); 8 | }; 9 | 10 | }; 11 | -------------------------------------------------------------------------------- /05-content/03-adapters/secondary/SessionStorage.ts: -------------------------------------------------------------------------------- 1 | import { OpenRequest } from "../../../01-shared/ApiDTO/ApiRequest"; 2 | import { Storage } from "../../02-ports/output/Storage"; 3 | 4 | export class SessionStorage implements Storage { 5 | 6 | public storeRequest(request: OpenRequest): void { 7 | sessionStorage.promptText = request.payload.promptText; 8 | sessionStorage.selectionText = request.payload.selectionText; 9 | }; 10 | 11 | public retrieveRequest(): OpenRequest { 12 | const promptText: string = sessionStorage.promptText + ""; 13 | const selectionText: string = sessionStorage.selectionText + ""; 14 | return new OpenRequest( 15 | promptText, 16 | selectionText, 17 | ); 18 | }; 19 | 20 | } -------------------------------------------------------------------------------- /05-content/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "content", 3 | "private": true, 4 | "browserslist": "> 0.5%, last 2 versions, not dead", 5 | "source": "start.ts", 6 | "scripts": { 7 | "debug": "parcel serve tests/index.html --public-url / --dist-dir dist", 8 | "build": "parcel build --no-optimize --no-source-maps --dist-dir ../dist/content", 9 | "dev": "nodemon --watch ./ --watch ../01-shared -e js,ts,html,css,json,vue,ico,png --exec \"npm run build\"" 10 | }, 11 | "devDependencies": { 12 | "@parcel/transformer-inline-string": "^2.8.3", 13 | "@parcel/transformer-sass": "^2.8.3", 14 | "@types/node": "^18.15.13", 15 | "chrome-types": "^0.1.171", 16 | "nodemon": "^2.0.20", 17 | "parcel": "^2.8.3", 18 | "ts-node": "^10.9.1", 19 | "typescript": "^5.0.4" 20 | }, 21 | "dependencies": {} 22 | } 23 | -------------------------------------------------------------------------------- /05-content/start.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { ApiAdapter } from "./03-adapters/primary/ApiAdapter"; 3 | 4 | new ApiAdapter().init(); 5 | -------------------------------------------------------------------------------- /05-content/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | GPT Anywhere is a browser extension (Chrome for now), that allows the user to input GPT completions anywhere on a website. 3 | 4 | Typical use cases are: 5 | - Social media 6 | - Email 7 | - User support 8 | - Etc 9 | 10 | # Folder structure 11 | ``` 12 | . 13 | ├── 01-shared # Shared types and config files 14 | ├── 02-options # Chrome extension's global files (manifest.json, icons, etc.) 15 | ├── 03-options # Options script 16 | ├── 04-background # Background script 17 | ├── 05-content # Content script 18 | └── ... 19 | ``` 20 | For reference on `options`, `background` and `content`, see [Chrome Extension](https://developer.chrome.com/docs/extensions/) documentation. 21 | 22 | # Build 23 | To build the Chrome extension: 24 | 1. Run `npm run build` 25 | 2. Load the extension from `./dist` folder 26 | 27 | # Architecture 28 | The extension saves the user's api key and prompts in the browser synced storage. 29 | ```mermaid 30 | flowchart BT 31 | Storage([Local storage]) --- Background[Background script] 32 | OpenAI([OpenAI]) --- Background 33 | Background ----|API calls| Options[Options page] 34 | Background ----|API calls| Page1(Page 1) 35 | Background ----|API calls| Page2(Page 2) 36 | Background ----|API calls| Page3(Page ...) 37 | Background ----|API calls| PageN(Page N) 38 | ``` 39 | The background script exposes an API called by the Options page and every browsed page. 40 | Scripts are communicating through this API with `runtime.sendMessage()` and `runtime.connect()`. 41 | 42 | # License 43 | [Unlicense](LICENSE) 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gpt-anywhere", 3 | "version": "1.0.0", 4 | "description": "GPT Anywhere is a Chrome extension that allows the user to use GPT on any website.", 5 | "scripts": { 6 | "build:global": "cd ./02-global && npm run build", 7 | "build:options": "cd ./03-options && npm run build", 8 | "build:background": "cd ./04-background && npm run build", 9 | "build:content": "cd ./05-content && npm run build", 10 | "build": "run-p build:*", 11 | "dev:global": "cd ./02-global && npm run dev", 12 | "dev:options": "cd ./03-options && npm run dev", 13 | "dev:background": "cd ./04-background && npm run dev", 14 | "dev:content": "cd ./05-content && npm run dev", 15 | "dev": "run-p dev:*" 16 | }, 17 | "nodemonConfig": { 18 | "ignore": [ 19 | "dist/" 20 | ] 21 | }, 22 | "license": "unlicense", 23 | "devDependencies": { 24 | "npm-run-all": "^4.1.5" 25 | } 26 | } 27 | --------------------------------------------------------------------------------