Skip to content

Commit

Permalink
feat: hayde has AI now! it supports openAI and ollama for now
Browse files Browse the repository at this point in the history
  • Loading branch information
Sly777 committed Nov 7, 2023
1 parent 845b62f commit f088439
Show file tree
Hide file tree
Showing 24 changed files with 1,183 additions and 254 deletions.
3 changes: 3 additions & 0 deletions examples/with-ai.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["general", "AI"]
}
17 changes: 17 additions & 0 deletions examples/with-ollama-llama2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"plugins": [
{
"name": "general",
"options": {
"srcFolderLocation": "./src/components"
}
},
{
"name": "AI",
"options": {
"aiTool": "ollama",
"modelName": "llama2"
}
}
]
}
17 changes: 17 additions & 0 deletions examples/with-openai-gpt-4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"plugins": [
{
"name": "general",
"options": {
"srcFolderLocation": "./src/components"
}
},
{
"name": "AI",
"options": {
"aiTool": "openAI",
"modelName": "gpt-4"
}
}
]
}
34 changes: 20 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 0",
"start": "tsx ./src/index.ts",
"start:debug": "tsx ./src/index.ts --debug",
"start:dist": "node ./dist/index.js",
"watch": "yarn start --watch",
"lint": "yarn eslint ./src",
Expand All @@ -36,27 +37,30 @@
},
"dependencies": {
"cfonts": "3.2.0",
"commander": "11.0.0",
"commander": "11.1.0",
"dotenv": "16.3.1",
"emmet": "2.4.6",
"handlebars": "4.7.8",
"inquirer": "9.2.11",
"js-beautify": "1.14.9",
"node-html-parser": "6.1.9"
"langchain": "0.0.181",
"node-html-parser": "6.1.11",
"ora": "7.0.1"
},
"devDependencies": {
"@types/inquirer": "9.0.3",
"@types/js-beautify": "1.14.0",
"@types/node": "20.6.0",
"@typescript-eslint/eslint-plugin": "6.6.0",
"@typescript-eslint/parser": "6.6.0",
"@vercel/ncc": "0.38.0",
"eslint": "8.49.0",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-unicorn": "48.0.1",
"@types/inquirer": "9.0.6",
"@types/js-beautify": "1.14.2",
"@types/node": "20.8.10",
"@typescript-eslint/eslint-plugin": "6.10.0",
"@typescript-eslint/parser": "6.10.0",
"@vercel/ncc": "0.38.1",
"eslint": "8.53.0",
"eslint-plugin-import": "2.29.0",
"eslint-plugin-unicorn": "49.0.0",
"husky": "8.0.3",
"is-ci": "3.0.1",
"lint-staged": "14.0.1",
"tsx": "3.12.8",
"lint-staged": "15.0.2",
"tsx": "3.14.0",
"typescript": "5.2.2"
},
"keywords": [
Expand All @@ -74,6 +78,8 @@
"react-component-creator",
"builder.io",
"material-ui",
"chakra-ui"
"chakra-ui",
"ai-component",
"ai-helper"
]
}
17 changes: 14 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
[![Known Vulnerabilities](https://snyk.io/test/github/Sly777/hayde/badge.svg)](https://snyk.io/test/github/Sly777/hayde)
[![npm version](https://badge.fury.io/js/hayde.svg)](https://badge.fury.io/js/hayde)
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)

[![Quality gate](https://sonarcloud.io/api/project_badges/quality_gate?project=Sly777_hayde)](https://sonarcloud.io/summary/new_code?id=Sly777_hayde)

Hayde is a CLI tool that allows you to create React components quickly and easily. With Hayde, you can focus on writing code instead of setting up boilerplate files.
Expand All @@ -36,7 +35,8 @@ This will launch the CLI tool and guide you through the process of creating a ne
- **Customizable** 🪄 - Hayde is highly customizable. You can configure it to suit your needs and preferences.
- **Modular** 🧩 - Hayde is modular. You can use it with any React project (create-react-app, next.js, astro, ...etc.), regardless of its size or complexity. Also it's easy to extend Hayde with new features.
- **No Installation Required** 📦 - Hayde doesn't require any installation. You can use it right away without having to install anything.
- **Auto Import** 🤖 - Hayde automatically imports your components into your project if you want. You just need to add tags into target file and voila! You can check it on [BuilderIO](./src/features/builderIO/) integration.
- **Auto Import** ⏩ - Hayde automatically imports your components into your project if you want. You just need to add tags into target file and voila! You can check it on [BuilderIO](./src/features/builderIO/) integration.
- **AI Support** 🤖 - Hayde supports AI tools such as OpenAI (gpt-3.5 & gpt-4) and Ollama. You can create your components with AI easily! You can check it on [AI](./src/features/AI/) integration.

## Table of Contents

Expand Down Expand Up @@ -79,8 +79,17 @@ Here's an example of what the .hayde.json file might look like:
"chakraUI"
]
}
```

or if you want to use AI only with hayde, you can use `.hayde.json` file like below:

```json
{
"plugins": [
"general",
"AI"
]
}
```

This file sets the default options for Hayde to avoid having to enter them every time you run the tool. And when you create a new component, Hayde will create react component with Chakra UI and interface support on `./src` folder.
Expand Down Expand Up @@ -109,10 +118,12 @@ You can create React components with the following libraries, you just need to a
- [React.js](https://reactjs.org/) - [Source](./src/features/reactJS/)
- CSS and SCSS support - [Source](./src/features/css/)
- [TypeScript](https://www.typescriptlang.org/)
- [OpenAI](https://platform.openai.com)
- [Ollama](https://ollama.ai)

## NPM Scripts

For NPM Scripts, please check the [npm scripts](./docs/npm-scripts.md) page. You can use Hayde with npm scripts easily.
You can call any script you want before or after component creation! For NPM Scripts, please check the [npm scripts](./docs/npm-scripts.md) page.

## CLI Arguments

Expand Down
12 changes: 7 additions & 5 deletions src/cliTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import {
ScriptNames,
runScriptFromPackagejson,
} from "@/internalFeatures/runScriptFromPackage";
import { errorLog, log } from "./helper";
import { logger, publicLog } from "./helper";
import { getArgvOptions } from "./internalFeatures/argvLibrary";

const log = logger("CLI Tool", true);

/**
* A class representing a CLI tool for creating components with various options.
*/
Expand Down Expand Up @@ -56,7 +58,7 @@ export class CliTool {
require(`./features/${pluginName}/index.ts`) as PluginExport;

if (!runPlugin) {
errorLog(`Plugin "${pluginName}" does not have a runPlugin()`);
log(`Plugin "${pluginName}" does not have a runPlugin()`);
continue;
}

Expand Down Expand Up @@ -86,7 +88,7 @@ export class CliTool {

private finish() {
runScriptFromPackagejson(ScriptNames.postComponentCreate);
console.log("\n\nComponent created successfully.");
publicLog("Component created successfully.");
}

private async askQuestions() {
Expand All @@ -102,7 +104,7 @@ export class CliTool {
const pluginOptions = typeof plugin === "string" ? {} : plugin.options;

if (!initPlugin) {
errorLog(`Plugin "${pluginName}" does not have an initPlugin()`);
log(`Plugin "${pluginName}" does not have an initPlugin()`);
continue;
}

Expand All @@ -125,7 +127,7 @@ export class CliTool {
}

public async run() {
console.log(`\n${this.createdBy}\n\n`);
publicLog(`${this.createdBy}\n\n`);

const ArgvOptions = getArgvOptions();
log("CLI script options:", ArgvOptions);
Expand Down
44 changes: 44 additions & 0 deletions src/features/AI/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { PromptTemplate } from "langchain/prompts";

export function stripMarkdown(text: string) {
const markdownRegex = /(```[a-z]*\n[\S\s]*?\n```)/g;
return text.replaceAll(markdownRegex, (match, p1: string): string =>
p1.replaceAll(/```[a-z]*\n|```/g, "").trim()
);
}

export function extractCodeBlock(response: string) {
const codeBlockRegex =
/```(?:typescript|javascript|react)?\n([\S\s]*?)\n```/g;
const codeBlocks = [];
let match;

while ((match = codeBlockRegex.exec(response)) !== null) {
codeBlocks.push(match[1].trim()); // Trim each code block
}

return codeBlocks.length > 0 ? codeBlocks.join("\n\n") : response;
}

export function createQuestionPrompt() {
const questionTemplate = `
Create functional react component with these information on {tsOrJs}.
Just return {tsOrJs} code to me, don't return any additional information with code. I will handle it. I also don't need any information about how to import the component.
- component description: '{componentDescription}'
- component name: '{componentName}'
- it's using '{styleLibrary}' library for creating component.
If the library is none, dont use any library.
Please include error handling, loading states, and comments within the code for clarity. Prefer functional components with hooks for state management over class components. Ensure the code is modular and easily testable.
If it's typescript, Include type definitions for all props and state objects, and use async/await for asynchronous operations. If it's javascript, use PropTypes for all props and state objects, and use promises for asynchronous operations. For state management, use React Context if needed.
Before sending response, please verify the code that you created. If it's not correct, please send the correct code.
Component code:
`;
return PromptTemplate.fromTemplate(questionTemplate);
}
73 changes: 73 additions & 0 deletions src/features/AI/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
PluginInitParams,
PluginInitReturn,
PluginRunParams,
PluginRunReturn,
} from "../../creatorSettings/creatorSettings.type";
import { AITools, IPluginOptions, ISettings, OutAnswers } from "./interfaces";
import inquirer from "inquirer";
import { questions } from "./questions";
import { logger, publicLog } from "@/helper";
import { createFile } from "@/internalFeatures/fsLibrary";
import ora from "ora";
import { callAgent as openAICallAgent } from "./models/openAI/openai";
import { callAgent as ollamaCallAgent } from "./models/ollama/ollama";

export { questions } from "./questions";
export { defaultSettings } from "./interfaces";

export const pluginName = "AI";
const log = logger(pluginName);

export async function initPlugin({
options,
}: PluginInitParams<IPluginOptions, OutAnswers>): Promise<
PluginInitReturn<IPluginOptions>
> {
publicLog("Before starting, please make sure that you added required ENV variables to your .env file.");
const answers = (await inquirer.prompt(
questions,
options
)) as Required<ISettings>;

return {
answers,
};
}

export async function runPlugin({
allAnswers,
}: PluginRunParams<ISettings, OutAnswers>): Promise<PluginRunReturn> {
const aiAnswers = allAnswers.AI?.answers as Required<ISettings>;
const generalAnswers = allAnswers.general?.answers;

log("Answers:", aiAnswers);

console.log("\n");
const spinner = ora({
text: "Waiting for AI answer",
spinner: "point",
}).start();

const reqData = {
componentName: generalAnswers.componentName!,
componentDescription: aiAnswers.compDescription,
styleLibrary: aiAnswers.styleLibrary,
modelName: aiAnswers.modelName,
isTypescript: aiAnswers.isTS,
};
const aiTool = aiAnswers.aiTool;
let response;

if (aiTool === AITools.openAI) {
response = await openAICallAgent(reqData);
} else if (aiTool === AITools.ollama) {
response = await ollamaCallAgent(reqData);
}

log("AI response:", response);

spinner.succeed("AI answer received");
createFile(allAnswers, aiAnswers.isTS ? ".tsx" : ".jsx", response as string);
}
45 changes: 45 additions & 0 deletions src/features/AI/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { PluginInitReturn } from "@/creatorSettings/creatorSettings.type";
import { StyleLibrary } from "@/features/reactJS/interfaces";
import { IPluginOptions as GeneralOptions } from "@/features/general/interfaces";

export interface IPluginOptions {
modelName?: string;
compDescription?: string;
styleLibrary?: StyleLibrary;
isTS?: boolean;
aiTool?: AITools;
}

export interface ISettings extends IPluginOptions {
templateName: string;
templateFolder: string;
}

export type OutAnswers = {
AI: PluginInitReturn<ISettings>;
general: PluginInitReturn<GeneralOptions>;
};

export const defaultSettings: ISettings = {
templateName: "main",
templateFolder: "AI",
};

export enum AITools {
openAI = "openAI",
ollama = "ollama",
}

export interface IModelReturn {
callAgent: callAgentFn;
}

export type callAgentFn = (options: callAgentFnOptions) => Promise<string>;

export type callAgentFnOptions = {
componentName: string;
componentDescription: string;
styleLibrary: string;
modelName: string;
isTypescript: boolean;
};
Loading

0 comments on commit f088439

Please sign in to comment.